Merge topic 'find-boost-1.82'

5cbbe55de8 FindBoost: Add support for Boost 1.82

Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !8514
diff --git a/.clang-tidy b/.clang-tidy
index c790467..1b776e1 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -15,6 +15,7 @@
 -misc-no-recursion,\
 -misc-non-private-member-variables-in-classes,\
 -misc-static-assert,\
+-misc-use-anonymous-namespace,\
 modernize-*,\
 -modernize-avoid-c-arrays,\
 -modernize-macro-to-enum,\
diff --git a/.codespellrc b/.codespellrc
index a8be487..00c6c52 100644
--- a/.codespellrc
+++ b/.codespellrc
@@ -1,9 +1,8 @@
 [codespell]
 check-filenames =
 check-hidden =
-count =
 # Disable warnings about binary files
 quiet-level = 2
 builtin = clear,rare,en-GB_to_en-US
-skip = */.git,*/build,*/Copyright.txt,*/doxygen.config,*/Modules/Internal/CPack/NSIS.template.in,*/Source/CursesDialog/form/*,*/Source/kwsys/*,*/Tests/RunCMake/CPack/tests/DMG_SLA/German.*,*/Tests/RunCMake/ParseImplicitData/*.input,*/Utilities/cm*
-ignore-words-list = aci,ake,ans,ba,cconfiguration,conly,dependees,dne,dum,earch,ect,filetest,fo,helpfull,hiden,isnt,keypair,nd,ned,nin,nknown,ot,pard,seh,ser,te,upto,varn,vas,wee
+skip = */.git,*/build,*/Copyright.txt,*/CTestCustom.cmake.in,*/doxygen.config,*/Modules/Internal/CPack/NSIS.template.in,*/Source/CursesDialog/form/*,*/Source/kwsys/*,*/Tests/RunCMake/CPack/tests/DMG_SLA/German.*,*/Tests/RunCMake/ParseImplicitData/*.input,*/Tests/StringFileTest/test.utf8,*.pfx,*/Utilities/cm*
+ignore-words-list = aci,ags,ake,ans,ba,ccompiler,cconfiguration,certi,conly,dependees,dne,dum,earch,ect,filetest,fo,helpfull,hiden,isnt,keypair,nd,ned,nin,nknown,ot,pard,seh,ser,te,upto,varn,vas,wee
diff --git a/.gitignore b/.gitignore
index f57271f..3b9e52a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,27 +1,30 @@
-# Common build directories
-build*/
+/CMakeUserPresets.json
 
-# Exclude MacOS Finder files.
+# Common build directories
+/build*/
+
+# MacOS Finder files.
 .DS_Store
 
-*.user*
-
+# Python compile output.
 *.pyc
 
-Help/_generated
-Testing
-CMakeUserPresets.json
-
-# Visual Studio work directory
-.vs/
-# Visual Studio build directory
-out/
-
-# Visual Studio Code
-.vscode/
-.cache/
+# See Utilities/Sphinx/tutorial_archive.cmake
+/Help/_generated
 
 # CLion work directory
-.idea/
+/.idea/
 # CLion build directories
-cmake-build-*/
+/cmake-build-*/
+
+# QtCreator files.
+/CMakeLists.txt.user*
+
+# Visual Studio Code
+/.vscode/
+/.cache/
+
+# Visual Studio work directory
+/.vs/
+# Visual Studio build directory
+/out/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 768a902..6e0d01a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -55,7 +55,7 @@
 
 p:doc-package:
     extends:
-        - .fedora37_sphinx_package
+        - .fedora38_sphinx_package
         - .cmake_prep_doc_linux
         - .linux_x86_64_tags
         - .cmake_doc_artifacts
@@ -103,26 +103,27 @@
         - .linux_x86_64_tags
         - .run_automatically
 
-l:tidy-fedora37:
+l:tidy-fedora38:
     extends:
-        - .fedora37_tidy
+        - .fedora38_tidy
         - .cmake_build_linux
         - .linux_x86_64_tags
         - .run_automatically
 
-l:sphinx-fedora37:
+l:sphinx-fedora38:
     extends:
-        - .fedora37_sphinx
+        - .fedora38_sphinx
         - .cmake_build_linux
+        - .cmake_sphinx_artifacts
         - .linux_x86_64_tags
         - .run_automatically
     variables:
         CMAKE_CI_JOB_CONTINUOUS: "true"
         CMAKE_CI_JOB_HELP: "true"
 
-l:clang-analyzer-fedora37:
+l:clang-analyzer-fedora38:
     extends:
-        - .fedora37_clang_analyzer
+        - .fedora38_clang_analyzer
         - .cmake_build_linux
         - .linux_x86_64_tags
         - .run_automatically
@@ -194,9 +195,9 @@
     variables:
         CMAKE_CI_JOB_NIGHTLY: "true"
 
-t:fedora37-ninja-clang:
+t:fedora38-ninja-clang:
     extends:
-        - .fedora37_ninja_clang
+        - .fedora38_ninja_clang
         - .cmake_test_linux_release
         - .linux_x86_64_tags
         - .run_dependent
@@ -204,9 +205,9 @@
     variables:
         CMAKE_CI_JOB_NIGHTLY: "true"
 
-t:fedora37-makefiles-clang:
+t:fedora38-ninja-multi-clang:
     extends:
-        - .fedora37_makefiles_clang
+        - .fedora38_ninja_multi_clang
         - .cmake_test_linux_release
         - .linux_x86_64_tags
         - .run_dependent
@@ -214,17 +215,27 @@
     variables:
         CMAKE_CI_JOB_NIGHTLY: "true"
 
-t:fedora37-makefiles:
+t:fedora38-makefiles-clang:
     extends:
-        - .fedora37_makefiles
+        - .fedora38_makefiles_clang
+        - .cmake_test_linux_release
+        - .linux_x86_64_tags
+        - .run_dependent
+        - .needs_centos6_x86_64
+    variables:
+        CMAKE_CI_JOB_NIGHTLY: "true"
+
+t:fedora38-makefiles:
+    extends:
+        - .fedora38_makefiles
         - .cmake_test_linux_release
         - .linux_x86_64_tags
         - .run_dependent
         - .needs_centos6_x86_64
 
-t:fedora37-makefiles-nospace:
+t:fedora38-makefiles-nospace:
     extends:
-        - .fedora37_makefiles
+        - .fedora38_makefiles
         - .cmake_test_linux_release
         - .linux_x86_64_tags
         - .cmake_junit_artifacts
@@ -232,7 +243,7 @@
         - .needs_centos6_x86_64
     variables:
         GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake-ci"
-        CMAKE_CI_BUILD_NAME: fedora37_makefiles_nospace
+        CMAKE_CI_BUILD_NAME: fedora38_makefiles_nospace
         CMAKE_CI_JOB_NIGHTLY: "true"
 
 t:nvhpc22.11-ninja:
@@ -333,29 +344,9 @@
     variables:
         CMAKE_CI_JOB_NIGHTLY: "true"
 
-t:linux-clang-cxx-modules-ninja:
+b:fedora38-ninja:
     extends:
-        - .clang_cxx_modules_ninja
-        - .cmake_test_linux_release
-        - .linux_x86_64_tags
-        - .run_dependent
-        - .needs_centos6_x86_64
-    variables:
-        CMAKE_CI_JOB_NIGHTLY: "true"
-
-t:linux-clang-cxx-modules-ninja-multi:
-    extends:
-        - .clang_cxx_modules_ninja_multi
-        - .cmake_test_linux_release
-        - .linux_x86_64_tags
-        - .run_dependent
-        - .needs_centos6_x86_64
-    variables:
-        CMAKE_CI_JOB_NIGHTLY: "true"
-
-b:fedora37-ninja:
-    extends:
-        - .fedora37_ninja
+        - .fedora38_ninja
         - .cmake_build_linux
         - .cmake_build_artifacts
         - .linux_x86_64_tags
@@ -390,40 +381,40 @@
     variables:
         CMAKE_CI_JOB_NIGHTLY: "true"
 
-b:fedora37-extdeps:
+b:fedora38-extdeps:
     extends:
-        - .fedora37_extdeps
+        - .fedora38_extdeps
         - .cmake_build_linux_standalone
         - .linux_x86_64_tags
         - .run_manually
     variables:
         CMAKE_CI_JOB_NIGHTLY: "true"
 
-t:fedora37-ninja:
+t:fedora38-ninja:
     extends:
-        - .fedora37_ninja
+        - .fedora38_ninja
         - .cmake_test_linux
         - .linux_x86_64_tags_x11
         - .cmake_test_artifacts
         - .run_dependent
     dependencies:
-        - b:fedora37-ninja
+        - b:fedora38-ninja
     needs:
-        - b:fedora37-ninja
+        - b:fedora38-ninja
     variables:
         CMAKE_CI_JOB_CONTINUOUS: "true"
 
-t:fedora37-ninja-multi:
+t:fedora38-ninja-multi:
     extends:
-        - .fedora37_ninja_multi
+        - .fedora38_ninja_multi
         - .cmake_test_linux_external
         - .linux_x86_64_tags
         - .cmake_junit_artifacts
         - .run_dependent
     dependencies:
-        - t:fedora37-ninja
+        - t:fedora38-ninja
     needs:
-        - t:fedora37-ninja
+        - t:fedora38-ninja
 
 t:intel2016-makefiles:
     extends:
@@ -663,6 +654,13 @@
         CMAKE_CI_BUILD_NAME: intel2021.8.0_makefiles
         CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2023.0.0-el8
 
+t:intel2021.9.0-makefiles:
+    extends:
+        - .cmake_test_linux_intelclassic_makefiles
+    variables:
+        CMAKE_CI_BUILD_NAME: intel2021.9.0_makefiles
+        CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2023.1.0-el8
+
 t:oneapi2021.1.1-makefiles:
     extends:
         - .cmake_test_linux_inteloneapi_makefiles
@@ -726,6 +724,13 @@
         CMAKE_CI_BUILD_NAME: oneapi2023.0.0_makefiles
         CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2023.0.0-el8
 
+t:oneapi2023.1.0-makefiles:
+    extends:
+        - .cmake_test_linux_inteloneapi_makefiles
+    variables:
+        CMAKE_CI_BUILD_NAME: oneapi2023.1.0_makefiles
+        CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2023.1.0-el8
+
 b:linux-x86_64-package:
     extends:
         - .linux_package
@@ -776,9 +781,9 @@
 
 ## Sanitizer builds
 
-b:fedora37-asan:
+b:fedora38-asan:
     extends:
-        - .fedora37_asan
+        - .fedora38_asan
         - .cmake_build_linux
         - .cmake_build_artifacts
         - .linux_x86_64_tags
@@ -786,16 +791,16 @@
     variables:
         CMAKE_CI_JOB_NIGHTLY: "true"
 
-t:fedora37-asan:
+t:fedora38-asan:
     extends:
-        - .fedora37_asan
+        - .fedora38_asan
         - .cmake_memcheck_linux
         - .linux_x86_64_tags
         - .run_dependent
     dependencies:
-        - b:fedora37-asan
+        - b:fedora38-asan
     needs:
-        - b:fedora37-asan
+        - b:fedora38-asan
     variables:
         CMAKE_CI_JOB_NIGHTLY: "true"
 
@@ -809,7 +814,7 @@
         - .macos_x86_64_tags
         - .run_manually
     variables:
-        CMAKE_CI_JOB_CONTINUOUS: "true"
+        CMAKE_CI_NO_MR: "true"
 
 b:macos-arm64-ninja:
     extends:
@@ -819,7 +824,7 @@
         - .macos_arm64_tags
         - .run_manually
     variables:
-        CMAKE_CI_NO_MR: "true"
+        CMAKE_CI_JOB_CONTINUOUS: "true"
 
 t:macos-x86_64-ninja:
     extends:
@@ -833,8 +838,7 @@
     needs:
         - b:macos-x86_64-ninja
     variables:
-        CMAKE_CI_JOB_CONTINUOUS: "true"
-        CMAKE_CI_JOB_NIGHTLY_NINJA: "true"
+        CMAKE_CI_NO_MR: "true"
 
 t:macos-arm64-ninja:
     extends:
@@ -848,7 +852,8 @@
     needs:
         - b:macos-arm64-ninja
     variables:
-        CMAKE_CI_NO_MR: "true"
+        CMAKE_CI_JOB_CONTINUOUS: "true"
+        CMAKE_CI_JOB_NIGHTLY_NINJA: "true"
 
 b:macos-x86_64-makefiles:
     extends:
@@ -869,17 +874,17 @@
     needs:
         - b:macos-x86_64-makefiles
 
-t:macos-x86_64-ninja-multi:
+t:macos-arm64-ninja-multi:
     extends:
-        - .macos_x86_64_ninja_multi
+        - .macos_arm64_ninja_multi
         - .cmake_test_macos_external
-        - .macos_x86_64_tags_ext
+        - .macos_arm64_tags_ext
         - .cmake_junit_artifacts
         - .run_dependent
     dependencies:
-        - t:macos-x86_64-ninja
+        - t:macos-arm64-ninja
     needs:
-        - t:macos-x86_64-ninja
+        - t:macos-arm64-ninja
     variables:
         CMAKE_CI_JOB_NIGHTLY: "true"
 
@@ -894,6 +899,8 @@
         - t:macos-x86_64-ninja
     needs:
         - t:macos-x86_64-ninja
+    variables:
+        CMAKE_CI_NO_MR: "true"
 
 t:macos-arm64-xcode:
     extends:
@@ -906,15 +913,41 @@
         - t:macos-arm64-ninja
     needs:
         - t:macos-arm64-ninja
+
+t:macos-x86_64-ninja-ub:
+    extends:
+        - .macos_x86_64_ninja_ub
+        - .cmake_test_macos_external
+        - .macos_x86_64_tags_ext
+        - .cmake_junit_artifacts
+        - .run_dependent
+    dependencies:
+        - t:macos-x86_64-ninja
+    needs:
+        - t:macos-x86_64-ninja
     variables:
-        CMAKE_CI_NO_MR: "true"
+        CMAKE_CI_JOB_NIGHTLY: "true"
+
+t:macos-arm64-xcode-ub:
+    extends:
+        - .macos_arm64_xcode_ub
+        - .cmake_test_macos_external
+        - .macos_arm64_tags_ext
+        - .cmake_junit_artifacts
+        - .run_dependent
+    dependencies:
+        - t:macos-arm64-ninja
+    needs:
+        - t:macos-arm64-ninja
+    variables:
+        CMAKE_CI_JOB_NIGHTLY: "true"
 
 b:macos-package:
     extends:
         - .macos_package
         - .cmake_build_macos
         - .cmake_release_artifacts
-        - .macos_x86_64_tags_package
+        - .macos_arm64_tags_package
         - .run_only_for_package
     dependencies:
         - p:doc-package
@@ -937,7 +970,7 @@
         - .macos10.10_package
         - .cmake_build_macos
         - .cmake_release_artifacts
-        - .macos_x86_64_tags_package
+        - .macos_arm64_tags_package
         - .run_only_for_package
     dependencies:
         - p:doc-package
@@ -1022,7 +1055,7 @@
 t:windows-vs2022-x64-nmake:
     extends:
         - .windows_vs2022_x64_nmake
-        - .cmake_test_windows_nmake
+        - .cmake_test_windows_external
         - .windows_x86_64_tags_concurrent_vs2022
         - .cmake_junit_artifacts
         - .run_dependent
@@ -1036,7 +1069,7 @@
 t:windows-vs2022-x64-jom:
     extends:
         - .windows_vs2022_x64_jom
-        - .cmake_test_windows_jom
+        - .cmake_test_windows_external
         - .windows_x86_64_tags_concurrent_vs2022
         - .cmake_junit_artifacts
         - .run_dependent
@@ -1050,7 +1083,7 @@
 t:windows-borland5.5:
     extends:
         - .windows_borland5.5
-        - .cmake_test_windows_borland
+        - .cmake_test_windows_external
         - .windows_x86_64_tags_concurrent
         - .cmake_junit_artifacts
         - .run_dependent
@@ -1064,7 +1097,7 @@
 t:windows-borland5.8:
     extends:
         - .windows_borland5.8
-        - .cmake_test_windows_borland
+        - .cmake_test_windows_external
         - .windows_x86_64_tags_concurrent
         - .cmake_junit_artifacts
         - .run_dependent
@@ -1075,7 +1108,7 @@
     variables:
         CMAKE_CI_JOB_NIGHTLY: "true"
 
-t:windows-clang15.0-cl-ninja:
+t:windows-clang16.0-cl-ninja:
     extends:
         - .windows_clang_ninja
         - .cmake_test_windows_external
@@ -1087,10 +1120,10 @@
     needs:
         - t:windows-vs2022-x64-ninja
     variables:
-        CMAKE_CI_BUILD_NAME: windows_clang15.0_cl_ninja
+        CMAKE_CI_BUILD_NAME: windows_clang16.0_cl_ninja
         CMAKE_CI_JOB_NIGHTLY: "true"
 
-t:windows-clang15.0-cl-nmake:
+t:windows-clang16.0-cl-nmake:
     extends:
         - .windows_clang_nmake
         - .cmake_test_windows_external
@@ -1102,10 +1135,10 @@
     needs:
         - t:windows-vs2022-x64-ninja
     variables:
-        CMAKE_CI_BUILD_NAME: windows_clang15.0_cl_nmake
+        CMAKE_CI_BUILD_NAME: windows_clang16.0_cl_nmake
         CMAKE_CI_JOB_NIGHTLY: "true"
 
-t:windows-clang15.0-gnu-ninja:
+t:windows-clang16.0-gnu-ninja:
     extends:
         - .windows_clang_ninja
         - .cmake_test_windows_external
@@ -1117,10 +1150,10 @@
     needs:
         - t:windows-vs2022-x64-ninja
     variables:
-        CMAKE_CI_BUILD_NAME: windows_clang15.0_gnu_ninja
+        CMAKE_CI_BUILD_NAME: windows_clang16.0_gnu_ninja
         CMAKE_CI_JOB_NIGHTLY: "true"
 
-t:windows-clang15.0-gnu-nmake:
+t:windows-clang16.0-gnu-nmake:
     extends:
         - .windows_clang_nmake
         - .cmake_test_windows_external
@@ -1132,7 +1165,37 @@
     needs:
         - t:windows-vs2022-x64-ninja
     variables:
-        CMAKE_CI_BUILD_NAME: windows_clang15.0_gnu_nmake
+        CMAKE_CI_BUILD_NAME: windows_clang16.0_gnu_nmake
+        CMAKE_CI_JOB_NIGHTLY: "true"
+
+t:windows-intel2021.9.0-ninja:
+    extends:
+        - .windows_intelclassic_ninja
+        - .cmake_test_windows_external
+        - .windows_x86_64_tags_concurrent
+        - .cmake_junit_artifacts
+        - .run_dependent
+    dependencies:
+        - t:windows-vs2022-x64-ninja
+    needs:
+        - t:windows-vs2022-x64-ninja
+    variables:
+        CMAKE_CI_BUILD_NAME: windows_intel2021.9.0_ninja
+        CMAKE_CI_JOB_NIGHTLY: "true"
+
+t:windows-oneapi2023.1.0-ninja:
+    extends:
+        - .windows_inteloneapi_ninja
+        - .cmake_test_windows_external
+        - .windows_x86_64_tags_concurrent
+        - .cmake_junit_artifacts
+        - .run_dependent
+    dependencies:
+        - t:windows-vs2022-x64-ninja
+    needs:
+        - t:windows-vs2022-x64-ninja
+    variables:
+        CMAKE_CI_BUILD_NAME: windows_oneapi2023.1.0_ninja
         CMAKE_CI_JOB_NIGHTLY: "true"
 
 t:mingw_osdn_io-mingw_makefiles:
@@ -1166,7 +1229,7 @@
 t:windows-msvc-v71-nmake:
     extends:
         - .windows_msvc_v71_nmake
-        - .cmake_test_windows_msvc
+        - .cmake_test_windows_external
         - .windows_x86_64_tags_concurrent
         - .cmake_junit_artifacts
         - .run_dependent
@@ -1180,7 +1243,7 @@
 t:windows-openwatcom1.9:
     extends:
         - .windows_openwatcom1.9
-        - .cmake_test_windows_openwatcom
+        - .cmake_test_windows_external
         - .windows_x86_64_tags_concurrent
         - .cmake_junit_artifacts
         - .run_dependent
diff --git a/.gitlab/.gitignore b/.gitlab/.gitignore
index 10d03ca..852dfa6 100644
--- a/.gitlab/.gitignore
+++ b/.gitlab/.gitignore
@@ -2,7 +2,9 @@
 /5.15.1-0-202009071110*
 /bcc*
 /cmake*
+/intel
 /ispc*
+/innosetup
 /jom
 /llvm*
 /mingw
diff --git a/.gitlab/artifacts.yml b/.gitlab/artifacts.yml
index 41f24ed..6c4cc0d 100644
--- a/.gitlab/artifacts.yml
+++ b/.gitlab/artifacts.yml
@@ -91,6 +91,15 @@
             junit:
                 - build/junit.xml
 
+.cmake_sphinx_artifacts:
+    artifacts:
+        expire_in: 1d
+        when: always
+        paths:
+            # Take the sphinx logs.
+            - build/build-*.log
+            - build/linkcheck/output.*
+
 .cmake_test_artifacts:
     artifacts:
         expire_in: 1d
diff --git a/.gitlab/ci/borland-env.ps1 b/.gitlab/ci/borland-env.ps1
new file mode 100755
index 0000000..b3b532e
--- /dev/null
+++ b/.gitlab/ci/borland-env.ps1
@@ -0,0 +1,3 @@
+Invoke-Expression -Command .gitlab/ci/borland.ps1
+$pwdpath = $pwd.Path
+Set-Item -Force -Path "env:PATH" -Value "$pwdpath\.gitlab\bcc\bin;$env:PATH"
diff --git a/.gitlab/ci/clang.ps1 b/.gitlab/ci/clang.ps1
index 29db93d..1fc8d8e 100755
--- a/.gitlab/ci/clang.ps1
+++ b/.gitlab/ci/clang.ps1
@@ -1,10 +1,10 @@
 $erroractionpreference = "stop"
 
-if ("$env:CMAKE_CI_BUILD_NAME".Contains("clang15.0")) {
-    # LLVM/Clang 15.0
-    # https://github.com/llvm/llvm-project/releases/tag/llvmorg-15.0.4
-    $filename = "llvm-15.0.4-win-x86_64-1"
-    $sha256sum = "9AA305084C20C27972E103E7B18AAC3F755E0534542AF62FC2F2BF5DDD3C4E1F"
+if ("$env:CMAKE_CI_BUILD_NAME".Contains("clang16.0")) {
+    # LLVM/Clang 16.0
+    # https://github.com/llvm/llvm-project/releases/tag/llvmorg-16.0.0
+    $filename = "llvm-16.0.0-win-x86_64-1"
+    $sha256sum = "13F48356BA5892A82E8BB25EB283FDDAA8F23A0F209B6BF6525D2C5E1285B950"
 } else {
     throw ('unknown CMAKE_CI_BUILD_NAME: ' + "$env:CMAKE_CI_BUILD_NAME")
 }
diff --git a/.gitlab/ci/configure_common.cmake b/.gitlab/ci/configure_common.cmake
index ed3d18d..c207a74 100644
--- a/.gitlab/ci/configure_common.cmake
+++ b/.gitlab/ci/configure_common.cmake
@@ -2,7 +2,7 @@
   # Launchers do not work during bootstrap: no ctest available.
   set(CTEST_USE_LAUNCHERS "OFF" CACHE BOOL "")
   # We configure by bootstrapping, so skip the BootstrapTest.
-  set(CMAKE_SKIP_BOOTSTRAP_TEST ON CACHE BOOL "")
+  set(CMake_TEST_BOOTSTRAP OFF CACHE BOOL "")
 else()
   set(CTEST_USE_LAUNCHERS "ON" CACHE BOOL "")
 endif()
diff --git a/.gitlab/ci/configure_debian10_aarch64_ninja.cmake b/.gitlab/ci/configure_debian10_aarch64_ninja.cmake
index 7407959..dff0db1 100644
--- a/.gitlab/ci/configure_debian10_aarch64_ninja.cmake
+++ b/.gitlab/ci/configure_debian10_aarch64_ninja.cmake
@@ -25,6 +25,22 @@
 set(CMake_TEST_FindGSL "ON" CACHE BOOL "")
 set(CMake_TEST_FindGTest "ON" CACHE BOOL "")
 set(CMake_TEST_FindGTK2 "ON" CACHE BOOL "")
+set(CMake_TEST_FindHDF5 "ON" CACHE BOOL "")
+set(CMake_TEST_FindHDF5_MPICH_C_COMPILER "/usr/bin/h5pcc.mpich" CACHE FILEPATH "")
+set(CMake_TEST_FindHDF5_MPICH_C_COMPILER_EXPLICIT "ON" CACHE BOOL "")
+# set(CMake_TEST_FindHDF5_MPICH_CXX_COMPILER "/usr/bin/h5c++.mpich" CACHE FILEPATH "") # h5c++.mpich does not exist
+# set(CMake_TEST_FindHDF5_MPICH_CXX_COMPILER_EXPLICIT "ON" CACHE BOOL "")
+set(CMake_TEST_FindHDF5_MPICH_Fortran_COMPILER "/usr/bin/h5pfc.mpich" CACHE FILEPATH "")
+set(CMake_TEST_FindHDF5_MPICH_Fortran_COMPILER_EXPLICIT "ON" CACHE BOOL "")
+set(CMake_TEST_FindHDF5_OpenMPI_C_COMPILER "/usr/bin/h5pcc.openmpi" CACHE FILEPATH "")
+set(CMake_TEST_FindHDF5_OpenMPI_C_COMPILER_EXPLICIT "ON" CACHE BOOL "")
+# set(CMake_TEST_FindHDF5_OpenMPI_CXX_COMPILER "/usr/bin/h5c++.openmpi" CACHE FILEPATH "") # h5c++.openmpi does not exist
+# set(CMake_TEST_FindHDF5_OpenMPI_CXX_COMPILER_EXPLICIT "ON" CACHE BOOL "")
+set(CMake_TEST_FindHDF5_OpenMPI_Fortran_COMPILER "/usr/bin/h5pfc.openmpi" CACHE FILEPATH "")
+set(CMake_TEST_FindHDF5_OpenMPI_Fortran_COMPILER_EXPLICIT "ON" CACHE BOOL "")
+set(CMake_TEST_FindHDF5_Serial_C_COMPILER "/usr/bin/h5cc" CACHE FILEPATH "")
+set(CMake_TEST_FindHDF5_Serial_CXX_COMPILER "/usr/bin/h5c++" CACHE FILEPATH "")
+set(CMake_TEST_FindHDF5_Serial_Fortran_COMPILER "/usr/bin/h5fc" CACHE FILEPATH "")
 set(CMake_TEST_FindIconv "ON" CACHE BOOL "")
 set(CMake_TEST_FindICU "ON" CACHE BOOL "")
 set(CMake_TEST_FindImageMagick "ON" CACHE BOOL "")
@@ -65,6 +81,7 @@
 set(CMake_TEST_FindSDL "ON" CACHE BOOL "")
 set(CMake_TEST_FindSQLite3 "ON" CACHE BOOL "")
 set(CMake_TEST_FindTIFF "ON" CACHE BOOL "")
+set(CMake_TEST_FindwxWidgets "ON" CACHE BOOL "")
 set(CMake_TEST_FindX11 "ON" CACHE BOOL "")
 set(CMake_TEST_FindXalanC "ON" CACHE BOOL "")
 set(CMake_TEST_FindXercesC "ON" CACHE BOOL "")
diff --git a/.gitlab/ci/configure_debian10_ninja.cmake b/.gitlab/ci/configure_debian10_ninja.cmake
index e8d6d55..211a2a7 100644
--- a/.gitlab/ci/configure_debian10_ninja.cmake
+++ b/.gitlab/ci/configure_debian10_ninja.cmake
@@ -29,6 +29,22 @@
 set(CMake_TEST_FindGSL "ON" CACHE BOOL "")
 set(CMake_TEST_FindGTest "ON" CACHE BOOL "")
 set(CMake_TEST_FindGTK2 "ON" CACHE BOOL "")
+set(CMake_TEST_FindHDF5 "ON" CACHE BOOL "")
+set(CMake_TEST_FindHDF5_MPICH_C_COMPILER "/usr/bin/h5pcc.mpich" CACHE FILEPATH "")
+set(CMake_TEST_FindHDF5_MPICH_C_COMPILER_EXPLICIT "ON" CACHE BOOL "")
+# set(CMake_TEST_FindHDF5_MPICH_CXX_COMPILER "/usr/bin/h5c++.mpich" CACHE FILEPATH "") # h5c++.mpich does not exist
+# set(CMake_TEST_FindHDF5_MPICH_CXX_COMPILER_EXPLICIT "ON" CACHE BOOL "")
+set(CMake_TEST_FindHDF5_MPICH_Fortran_COMPILER "/usr/bin/h5pfc.mpich" CACHE FILEPATH "")
+set(CMake_TEST_FindHDF5_MPICH_Fortran_COMPILER_EXPLICIT "ON" CACHE BOOL "")
+set(CMake_TEST_FindHDF5_OpenMPI_C_COMPILER "/usr/bin/h5pcc.openmpi" CACHE FILEPATH "")
+set(CMake_TEST_FindHDF5_OpenMPI_C_COMPILER_EXPLICIT "ON" CACHE BOOL "")
+# set(CMake_TEST_FindHDF5_OpenMPI_CXX_COMPILER "/usr/bin/h5c++.openmpi" CACHE FILEPATH "") # h5c++.openmpi does not exist
+# set(CMake_TEST_FindHDF5_OpenMPI_CXX_COMPILER_EXPLICIT "ON" CACHE BOOL "")
+set(CMake_TEST_FindHDF5_OpenMPI_Fortran_COMPILER "/usr/bin/h5pfc.openmpi" CACHE FILEPATH "")
+set(CMake_TEST_FindHDF5_OpenMPI_Fortran_COMPILER_EXPLICIT "ON" CACHE BOOL "")
+set(CMake_TEST_FindHDF5_Serial_C_COMPILER "/usr/bin/h5cc" CACHE FILEPATH "")
+set(CMake_TEST_FindHDF5_Serial_CXX_COMPILER "/usr/bin/h5c++" CACHE FILEPATH "")
+set(CMake_TEST_FindHDF5_Serial_Fortran_COMPILER "/usr/bin/h5fc" CACHE FILEPATH "")
 set(CMake_TEST_FindIconv "ON" CACHE BOOL "")
 set(CMake_TEST_FindICU "ON" CACHE BOOL "")
 set(CMake_TEST_FindImageMagick "ON" CACHE BOOL "")
@@ -71,6 +87,7 @@
 set(CMake_TEST_FindSDL "ON" CACHE BOOL "")
 set(CMake_TEST_FindSQLite3 "ON" CACHE BOOL "")
 set(CMake_TEST_FindTIFF "ON" CACHE BOOL "")
+set(CMake_TEST_FindwxWidgets "ON" CACHE BOOL "")
 set(CMake_TEST_FindX11 "ON" CACHE BOOL "")
 set(CMake_TEST_FindXalanC "ON" CACHE BOOL "")
 set(CMake_TEST_FindXercesC "ON" CACHE BOOL "")
diff --git a/.gitlab/ci/configure_extdeps_common.cmake b/.gitlab/ci/configure_extdeps_common.cmake
index 2c7d328..c06592c 100644
--- a/.gitlab/ci/configure_extdeps_common.cmake
+++ b/.gitlab/ci/configure_extdeps_common.cmake
@@ -1,5 +1,5 @@
 set(CMAKE_USE_SYSTEM_LIBRARIES ON CACHE BOOL "")
-set(CMAKE_SKIP_BOOTSTRAP_TEST ON CACHE BOOL "")
+set(CMake_TEST_BOOTSTRAP OFF CACHE BOOL "")
 set(CMake_TEST_Qt6 OFF CACHE BOOL "")
 
 include("${CMAKE_CURRENT_LIST_DIR}/configure_common.cmake")
diff --git a/.gitlab/ci/configure_fedora37_asan.cmake b/.gitlab/ci/configure_fedora37_asan.cmake
deleted file mode 100644
index 363e953..0000000
--- a/.gitlab/ci/configure_fedora37_asan.cmake
+++ /dev/null
@@ -1,4 +0,0 @@
-set(CMAKE_C_FLAGS "-fsanitize=address" CACHE STRING "")
-set(CMAKE_CXX_FLAGS "-fsanitize=address" CACHE STRING "")
-
-include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora37_common.cmake")
diff --git a/.gitlab/ci/configure_fedora37_clang_analyzer.cmake b/.gitlab/ci/configure_fedora37_clang_analyzer.cmake
deleted file mode 100644
index f4c4cdd..0000000
--- a/.gitlab/ci/configure_fedora37_clang_analyzer.cmake
+++ /dev/null
@@ -1 +0,0 @@
-include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora37_common.cmake")
diff --git a/.gitlab/ci/configure_fedora37_makefiles.cmake b/.gitlab/ci/configure_fedora37_makefiles.cmake
deleted file mode 100644
index 725cc46..0000000
--- a/.gitlab/ci/configure_fedora37_makefiles.cmake
+++ /dev/null
@@ -1,86 +0,0 @@
-set(CMake_TEST_CTestUpdate_BZR "ON" CACHE BOOL "")
-set(CMake_TEST_CTestUpdate_GIT "ON" CACHE BOOL "")
-set(CMake_TEST_CTestUpdate_HG "ON" CACHE BOOL "")
-set(CMake_TEST_CTestUpdate_SVN "ON" CACHE BOOL "")
-if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "")
-  set(CMake_TEST_CTestUpdate_P4 "ON" CACHE BOOL "")
-endif()
-
-set(CMake_TEST_FindALSA "ON" CACHE BOOL "")
-set(CMake_TEST_FindBLAS "All;static=1;Generic" CACHE STRING "")
-set(CMake_TEST_FindBoost "ON" CACHE BOOL "")
-set(CMake_TEST_FindBoost_Python "ON" CACHE BOOL "")
-set(CMake_TEST_FindBZip2 "ON" CACHE BOOL "")
-set(CMake_TEST_FindCups "ON" CACHE BOOL "")
-set(CMake_TEST_FindCURL "ON" CACHE BOOL "")
-set(CMake_TEST_FindDevIL "ON" CACHE BOOL "")
-set(CMake_TEST_FindDoxygen_Dot "ON" CACHE BOOL "")
-set(CMake_TEST_FindDoxygen "ON" CACHE BOOL "")
-set(CMake_TEST_FindEXPAT "ON" CACHE BOOL "")
-set(CMake_TEST_FindFontconfig "ON" CACHE BOOL "")
-set(CMake_TEST_FindFreetype "ON" CACHE BOOL "")
-set(CMake_TEST_FindGDAL "ON" CACHE BOOL "")
-set(CMake_TEST_FindGIF "ON" CACHE BOOL "")
-set(CMake_TEST_FindGit "ON" CACHE BOOL "")
-set(CMake_TEST_FindGLEW "ON" CACHE BOOL "")
-set(CMake_TEST_FindGLUT "ON" CACHE BOOL "")
-set(CMake_TEST_FindGnuTLS "ON" CACHE BOOL "")
-set(CMake_TEST_FindGSL "ON" CACHE BOOL "")
-set(CMake_TEST_FindGTest "ON" CACHE BOOL "")
-set(CMake_TEST_FindGTK2 "ON" CACHE BOOL "")
-set(CMake_TEST_FindIconv "ON" CACHE BOOL "")
-set(CMake_TEST_FindICU "ON" CACHE BOOL "")
-set(CMake_TEST_FindImageMagick "ON" CACHE BOOL "")
-set(CMake_TEST_FindIntl "ON" CACHE BOOL "")
-set(CMake_TEST_FindJNI "ON" CACHE BOOL "")
-set(CMake_TEST_FindJPEG "ON" CACHE BOOL "")
-set(CMake_TEST_FindJsonCpp "ON" CACHE BOOL "")
-set(CMake_TEST_FindLAPACK "All;static=1;Generic" CACHE STRING "")
-set(CMake_TEST_FindLibArchive "ON" CACHE BOOL "")
-set(CMake_TEST_FindLibinput "ON" CACHE BOOL "")
-set(CMake_TEST_FindLibLZMA "ON" CACHE BOOL "")
-set(CMake_TEST_FindLibUV "ON" CACHE BOOL "")
-set(CMake_TEST_FindLibXml2 "ON" CACHE BOOL "")
-set(CMake_TEST_FindLibXslt "ON" CACHE BOOL "")
-set(CMake_TEST_FindMPI_C "ON" CACHE BOOL "")
-set(CMake_TEST_FindMPI_CXX "ON" CACHE BOOL "")
-set(CMake_TEST_FindMPI_Fortran "ON" CACHE BOOL "")
-set(CMake_TEST_FindMPI "ON" CACHE BOOL "")
-set(CMake_TEST_FindODBC "ON" CACHE BOOL "")
-set(CMake_TEST_FindOpenACC "ON" CACHE BOOL "")
-set(CMake_TEST_FindOpenAL "ON" CACHE BOOL "")
-set(CMake_TEST_FindOpenGL "ON" CACHE BOOL "")
-set(CMake_TEST_FindOpenMP_C "ON" CACHE BOOL "")
-set(CMake_TEST_FindOpenMP_CXX "ON" CACHE BOOL "")
-set(CMake_TEST_FindOpenMP_Fortran "ON" CACHE BOOL "")
-set(CMake_TEST_FindOpenMP "ON" CACHE BOOL "")
-set(CMake_TEST_FindOpenSP "ON" CACHE BOOL "")
-set(CMake_TEST_FindOpenSSL "ON" CACHE BOOL "")
-set(CMake_TEST_FindPatch "ON" CACHE BOOL "")
-set(CMake_TEST_FindPNG "ON" CACHE BOOL "")
-set(CMake_TEST_FindPostgreSQL "ON" CACHE BOOL "")
-set(CMake_TEST_FindProtobuf "ON" CACHE BOOL "")
-set(CMake_TEST_FindProtobuf_gRPC "ON" CACHE BOOL "")
-set(CMake_TEST_FindPython "ON" CACHE BOOL "")
-set(CMake_TEST_FindPython_NumPy "ON" CACHE BOOL "")
-set(CMake_TEST_FindPython_PyPy "ON" CACHE BOOL "")
-set(CMake_TEST_FindRuby "ON" CACHE BOOL "")
-set(CMake_TEST_FindRuby_RVM "ON" CACHE BOOL "")
-set(CMake_TEST_FindSDL "ON" CACHE BOOL "")
-set(CMake_TEST_FindSQLite3 "ON" CACHE BOOL "")
-set(CMake_TEST_FindTIFF "ON" CACHE BOOL "")
-set(CMake_TEST_FindX11 "ON" CACHE BOOL "")
-set(CMake_TEST_FindXalanC "ON" CACHE BOOL "")
-set(CMake_TEST_FindXercesC "ON" CACHE BOOL "")
-set(CMake_TEST_Fortran_SUBMODULES "ON" CACHE BOOL "")
-set(CMake_TEST_IPO_WORKS_C "ON" CACHE BOOL "")
-set(CMake_TEST_IPO_WORKS_CXX "ON" CACHE BOOL "")
-set(CMake_TEST_IPO_WORKS_Fortran "ON" CACHE BOOL "")
-if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "")
-  set(CMake_TEST_ISPC "ON" CACHE STRING "")
-endif()
-set(CMake_TEST_Qt5 "ON" CACHE BOOL "")
-set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "")
-set(CMake_TEST_UseSWIG "ON" CACHE BOOL "")
-
-include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake")
diff --git a/.gitlab/ci/configure_fedora37_makefiles_clang.cmake b/.gitlab/ci/configure_fedora37_makefiles_clang.cmake
deleted file mode 100644
index 7b82a9a..0000000
--- a/.gitlab/ci/configure_fedora37_makefiles_clang.cmake
+++ /dev/null
@@ -1 +0,0 @@
-include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora37_common_clang.cmake")
diff --git a/.gitlab/ci/configure_fedora37_ninja.cmake b/.gitlab/ci/configure_fedora37_ninja.cmake
deleted file mode 100644
index 5b40677..0000000
--- a/.gitlab/ci/configure_fedora37_ninja.cmake
+++ /dev/null
@@ -1,14 +0,0 @@
-set(CMake_TEST_GUI "ON" CACHE BOOL "")
-if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "")
-  set(CMake_TEST_ISPC "ON" CACHE STRING "")
-endif()
-set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "")
-
-# "Release" flags without "-DNDEBUG" so we get assertions.
-set(CMAKE_C_FLAGS_RELEASE "-O3" CACHE STRING "")
-set(CMAKE_CXX_FLAGS_RELEASE "-O3" CACHE STRING "")
-
-# Cover compilation with C++11 only and not higher standards.
-set(CMAKE_CXX_STANDARD "11" CACHE STRING "")
-
-include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora37_common.cmake")
diff --git a/.gitlab/ci/configure_fedora37_ninja_clang.cmake b/.gitlab/ci/configure_fedora37_ninja_clang.cmake
deleted file mode 100644
index 7b82a9a..0000000
--- a/.gitlab/ci/configure_fedora37_ninja_clang.cmake
+++ /dev/null
@@ -1 +0,0 @@
-include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora37_common_clang.cmake")
diff --git a/.gitlab/ci/configure_fedora37_tidy.cmake b/.gitlab/ci/configure_fedora37_tidy.cmake
deleted file mode 100644
index f8eb9ab..0000000
--- a/.gitlab/ci/configure_fedora37_tidy.cmake
+++ /dev/null
@@ -1,5 +0,0 @@
-set(CMake_RUN_CLANG_TIDY ON CACHE BOOL "")
-set(CMake_USE_CLANG_TIDY_MODULE ON CACHE BOOL "")
-set(CMake_CLANG_TIDY_MODULE "$ENV{CI_PROJECT_DIR}/Utilities/ClangTidyModule/build/libcmake-clang-tidy-module.so" CACHE FILEPATH "")
-
-include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora37_common.cmake")
diff --git a/.gitlab/ci/configure_fedora38_asan.cmake b/.gitlab/ci/configure_fedora38_asan.cmake
new file mode 100644
index 0000000..8eae500
--- /dev/null
+++ b/.gitlab/ci/configure_fedora38_asan.cmake
@@ -0,0 +1,4 @@
+set(CMAKE_C_FLAGS "-fsanitize=address" CACHE STRING "")
+set(CMAKE_CXX_FLAGS "-fsanitize=address" CACHE STRING "")
+
+include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora38_common.cmake")
diff --git a/.gitlab/ci/configure_fedora38_clang_analyzer.cmake b/.gitlab/ci/configure_fedora38_clang_analyzer.cmake
new file mode 100644
index 0000000..c11eef1
--- /dev/null
+++ b/.gitlab/ci/configure_fedora38_clang_analyzer.cmake
@@ -0,0 +1,3 @@
+set(configure_no_sccache 1)
+
+include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora38_common.cmake")
diff --git a/.gitlab/ci/configure_fedora37_common.cmake b/.gitlab/ci/configure_fedora38_common.cmake
similarity index 100%
rename from .gitlab/ci/configure_fedora37_common.cmake
rename to .gitlab/ci/configure_fedora38_common.cmake
diff --git a/.gitlab/ci/configure_fedora37_common_clang.cmake b/.gitlab/ci/configure_fedora38_common_clang.cmake
similarity index 100%
rename from .gitlab/ci/configure_fedora37_common_clang.cmake
rename to .gitlab/ci/configure_fedora38_common_clang.cmake
diff --git a/.gitlab/ci/configure_fedora37_extdeps.cmake b/.gitlab/ci/configure_fedora38_extdeps.cmake
similarity index 100%
rename from .gitlab/ci/configure_fedora37_extdeps.cmake
rename to .gitlab/ci/configure_fedora38_extdeps.cmake
diff --git a/.gitlab/ci/configure_fedora38_makefiles.cmake b/.gitlab/ci/configure_fedora38_makefiles.cmake
new file mode 100644
index 0000000..c2f9982
--- /dev/null
+++ b/.gitlab/ci/configure_fedora38_makefiles.cmake
@@ -0,0 +1,100 @@
+set(CMake_TEST_CTestUpdate_BZR "ON" CACHE BOOL "")
+set(CMake_TEST_CTestUpdate_GIT "ON" CACHE BOOL "")
+set(CMake_TEST_CTestUpdate_HG "ON" CACHE BOOL "")
+set(CMake_TEST_CTestUpdate_SVN "ON" CACHE BOOL "")
+if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "")
+  set(CMake_TEST_CTestUpdate_P4 "ON" CACHE BOOL "")
+endif()
+
+set(CMake_TEST_FindALSA "ON" CACHE BOOL "")
+set(CMake_TEST_FindBLAS "All;static=1;Generic" CACHE STRING "")
+set(CMake_TEST_FindBoost "ON" CACHE BOOL "")
+set(CMake_TEST_FindBoost_Python "ON" CACHE BOOL "")
+set(CMake_TEST_FindBZip2 "ON" CACHE BOOL "")
+set(CMake_TEST_FindCups "ON" CACHE BOOL "")
+set(CMake_TEST_FindCURL "ON" CACHE BOOL "")
+set(CMake_TEST_FindDevIL "ON" CACHE BOOL "")
+set(CMake_TEST_FindDoxygen_Dot "ON" CACHE BOOL "")
+set(CMake_TEST_FindDoxygen "ON" CACHE BOOL "")
+set(CMake_TEST_FindEXPAT "ON" CACHE BOOL "")
+set(CMake_TEST_FindFontconfig "ON" CACHE BOOL "")
+set(CMake_TEST_FindFreetype "ON" CACHE BOOL "")
+set(CMake_TEST_FindGDAL "ON" CACHE BOOL "")
+set(CMake_TEST_FindGIF "ON" CACHE BOOL "")
+set(CMake_TEST_FindGit "ON" CACHE BOOL "")
+set(CMake_TEST_FindGLEW "ON" CACHE BOOL "")
+set(CMake_TEST_FindGLUT "ON" CACHE BOOL "")
+set(CMake_TEST_FindGnuTLS "ON" CACHE BOOL "")
+set(CMake_TEST_FindGSL "ON" CACHE BOOL "")
+set(CMake_TEST_FindGTest "ON" CACHE BOOL "")
+set(CMake_TEST_FindGTK2 "ON" CACHE BOOL "")
+set(CMake_TEST_FindHDF5 "ON" CACHE BOOL "")
+set(CMake_TEST_FindHDF5_MPICH_C_COMPILER "/usr/lib64/mpich/bin/h5pcc" CACHE FILEPATH "")
+# set(CMake_TEST_FindHDF5_MPICH_CXX_COMPILER "/usr/lib64/mpich/bin/h5pc++" CACHE FILEPATH "") # h5pc++ does not exist
+set(CMake_TEST_FindHDF5_MPICH_ENVMOD "PATH=path_list_prepend:/usr/lib64/mpich/bin;LD_LIBRARY_PATH=path_list_prepend:/usr/lib64/mpich/lib" CACHE STRING "")
+set(CMake_TEST_FindHDF5_MPICH_Fortran_COMPILER "/usr/lib64/mpich/bin/h5pfc" CACHE FILEPATH "")
+set(CMake_TEST_FindHDF5_OpenMPI_C_COMPILER "/usr/lib64/openmpi/bin/h5pcc" CACHE FILEPATH "")
+set(CMake_TEST_FindHDF5_OpenMPI_ENVMOD "PATH=path_list_prepend:/usr/lib64/openmpi/bin;LD_LIBRARY_PATH=path_list_prepend:/usr/lib64/openmpi/lib" CACHE STRING "")
+# set(CMake_TEST_FindHDF5_OpenMPI_CXX_COMPILER "/usr/lib64/openmpi/bin/h5pc++" CACHE FILEPATH "") # h5pc++ does not exist
+set(CMake_TEST_FindHDF5_OpenMPI_Fortran_COMPILER "/usr/lib64/openmpi/bin/h5pfc" CACHE FILEPATH "")
+set(CMake_TEST_FindHDF5_Serial_C_COMPILER "/usr/bin/h5cc" CACHE FILEPATH "")
+set(CMake_TEST_FindHDF5_Serial_CXX_COMPILER "/usr/bin/h5c++" CACHE FILEPATH "")
+set(CMake_TEST_FindHDF5_Serial_Fortran_COMPILER "/usr/bin/h5fc" CACHE FILEPATH "")
+set(CMake_TEST_FindIconv "ON" CACHE BOOL "")
+set(CMake_TEST_FindICU "ON" CACHE BOOL "")
+set(CMake_TEST_FindImageMagick "ON" CACHE BOOL "")
+set(CMake_TEST_FindIntl "ON" CACHE BOOL "")
+set(CMake_TEST_FindJNI "ON" CACHE BOOL "")
+set(CMake_TEST_FindJPEG "ON" CACHE BOOL "")
+set(CMake_TEST_FindJsonCpp "ON" CACHE BOOL "")
+set(CMake_TEST_FindLAPACK "All;static=1;Generic" CACHE STRING "")
+set(CMake_TEST_FindLibArchive "ON" CACHE BOOL "")
+set(CMake_TEST_FindLibinput "ON" CACHE BOOL "")
+set(CMake_TEST_FindLibLZMA "ON" CACHE BOOL "")
+set(CMake_TEST_FindLibUV "ON" CACHE BOOL "")
+set(CMake_TEST_FindLibXml2 "ON" CACHE BOOL "")
+set(CMake_TEST_FindLibXslt "ON" CACHE BOOL "")
+set(CMake_TEST_FindMPI_C "ON" CACHE BOOL "")
+set(CMake_TEST_FindMPI_CXX "ON" CACHE BOOL "")
+set(CMake_TEST_FindMPI_Fortran "ON" CACHE BOOL "")
+set(CMake_TEST_FindMPI_ENVMOD "PATH=path_list_prepend:/usr/lib64/mpich/bin;LD_LIBRARY_PATH=path_list_prepend:/usr/lib64/mpich/lib" CACHE STRING "")
+set(CMake_TEST_FindMPI "ON" CACHE BOOL "")
+set(CMake_TEST_FindODBC "ON" CACHE BOOL "")
+set(CMake_TEST_FindOpenACC "ON" CACHE BOOL "")
+set(CMake_TEST_FindOpenAL "ON" CACHE BOOL "")
+set(CMake_TEST_FindOpenGL "ON" CACHE BOOL "")
+set(CMake_TEST_FindOpenMP_C "ON" CACHE BOOL "")
+set(CMake_TEST_FindOpenMP_CXX "ON" CACHE BOOL "")
+set(CMake_TEST_FindOpenMP_Fortran "ON" CACHE BOOL "")
+set(CMake_TEST_FindOpenMP "ON" CACHE BOOL "")
+set(CMake_TEST_FindOpenSP "ON" CACHE BOOL "")
+set(CMake_TEST_FindOpenSSL "ON" CACHE BOOL "")
+set(CMake_TEST_FindPatch "ON" CACHE BOOL "")
+set(CMake_TEST_FindPNG "ON" CACHE BOOL "")
+set(CMake_TEST_FindPostgreSQL "ON" CACHE BOOL "")
+set(CMake_TEST_FindProtobuf "ON" CACHE BOOL "")
+set(CMake_TEST_FindProtobuf_gRPC "ON" CACHE BOOL "")
+set(CMake_TEST_FindPython "ON" CACHE BOOL "")
+set(CMake_TEST_FindPython_NumPy "ON" CACHE BOOL "")
+set(CMake_TEST_FindPython_PyPy "ON" CACHE BOOL "")
+set(CMake_TEST_FindRuby "ON" CACHE BOOL "")
+set(CMake_TEST_FindRuby_RVM "ON" CACHE BOOL "")
+set(CMake_TEST_FindSDL "ON" CACHE BOOL "")
+set(CMake_TEST_FindSQLite3 "ON" CACHE BOOL "")
+set(CMake_TEST_FindTIFF "ON" CACHE BOOL "")
+set(CMake_TEST_FindwxWidgets "ON" CACHE BOOL "")
+set(CMake_TEST_FindX11 "ON" CACHE BOOL "")
+set(CMake_TEST_FindXalanC "ON" CACHE BOOL "")
+set(CMake_TEST_FindXercesC "ON" CACHE BOOL "")
+set(CMake_TEST_Fortran_SUBMODULES "ON" CACHE BOOL "")
+set(CMake_TEST_IPO_WORKS_C "ON" CACHE BOOL "")
+set(CMake_TEST_IPO_WORKS_CXX "ON" CACHE BOOL "")
+set(CMake_TEST_IPO_WORKS_Fortran "ON" CACHE BOOL "")
+if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "")
+  set(CMake_TEST_ISPC "ON" CACHE STRING "")
+endif()
+set(CMake_TEST_Qt5 "ON" CACHE BOOL "")
+set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "")
+set(CMake_TEST_UseSWIG "ON" CACHE BOOL "")
+
+include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake")
diff --git a/.gitlab/ci/configure_fedora38_makefiles_clang.cmake b/.gitlab/ci/configure_fedora38_makefiles_clang.cmake
new file mode 100644
index 0000000..ff30ad9
--- /dev/null
+++ b/.gitlab/ci/configure_fedora38_makefiles_clang.cmake
@@ -0,0 +1 @@
+include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora38_common_clang.cmake")
diff --git a/.gitlab/ci/configure_fedora38_ninja.cmake b/.gitlab/ci/configure_fedora38_ninja.cmake
new file mode 100644
index 0000000..ac6b9f6
--- /dev/null
+++ b/.gitlab/ci/configure_fedora38_ninja.cmake
@@ -0,0 +1,14 @@
+set(CMake_TEST_GUI "ON" CACHE BOOL "")
+if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "")
+  set(CMake_TEST_ISPC "ON" CACHE STRING "")
+endif()
+set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "")
+
+# "Release" flags without "-DNDEBUG" so we get assertions.
+set(CMAKE_C_FLAGS_RELEASE "-O3" CACHE STRING "")
+set(CMAKE_CXX_FLAGS_RELEASE "-O3" CACHE STRING "")
+
+# Cover compilation with C++11 only and not higher standards.
+set(CMAKE_CXX_STANDARD "11" CACHE STRING "")
+
+include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora38_common.cmake")
diff --git a/.gitlab/ci/configure_fedora38_ninja_clang.cmake b/.gitlab/ci/configure_fedora38_ninja_clang.cmake
new file mode 100644
index 0000000..214a123
--- /dev/null
+++ b/.gitlab/ci/configure_fedora38_ninja_clang.cmake
@@ -0,0 +1,4 @@
+set(CMake_TEST_MODULE_COMPILATION "named,collation,partitions,internal_partitions,export_bmi,install_bmi,shared" CACHE STRING "")
+set(CMake_TEST_MODULE_COMPILATION_RULES "${CMAKE_CURRENT_LIST_DIR}/cxx_modules_rules_clang.cmake" CACHE STRING "")
+
+include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora38_common_clang.cmake")
diff --git a/.gitlab/ci/configure_fedora37_ninja_multi.cmake b/.gitlab/ci/configure_fedora38_ninja_multi.cmake
similarity index 100%
rename from .gitlab/ci/configure_fedora37_ninja_multi.cmake
rename to .gitlab/ci/configure_fedora38_ninja_multi.cmake
diff --git a/.gitlab/ci/configure_fedora38_ninja_multi_clang.cmake b/.gitlab/ci/configure_fedora38_ninja_multi_clang.cmake
new file mode 100644
index 0000000..214a123
--- /dev/null
+++ b/.gitlab/ci/configure_fedora38_ninja_multi_clang.cmake
@@ -0,0 +1,4 @@
+set(CMake_TEST_MODULE_COMPILATION "named,collation,partitions,internal_partitions,export_bmi,install_bmi,shared" CACHE STRING "")
+set(CMake_TEST_MODULE_COMPILATION_RULES "${CMAKE_CURRENT_LIST_DIR}/cxx_modules_rules_clang.cmake" CACHE STRING "")
+
+include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora38_common_clang.cmake")
diff --git a/.gitlab/ci/configure_fedora37_sphinx.cmake b/.gitlab/ci/configure_fedora38_sphinx.cmake
similarity index 100%
rename from .gitlab/ci/configure_fedora37_sphinx.cmake
rename to .gitlab/ci/configure_fedora38_sphinx.cmake
diff --git a/.gitlab/ci/configure_fedora37_sphinx_package.cmake b/.gitlab/ci/configure_fedora38_sphinx_package.cmake
similarity index 100%
rename from .gitlab/ci/configure_fedora37_sphinx_package.cmake
rename to .gitlab/ci/configure_fedora38_sphinx_package.cmake
diff --git a/.gitlab/ci/configure_fedora38_tidy.cmake b/.gitlab/ci/configure_fedora38_tidy.cmake
new file mode 100644
index 0000000..5b062da
--- /dev/null
+++ b/.gitlab/ci/configure_fedora38_tidy.cmake
@@ -0,0 +1,5 @@
+set(CMake_RUN_CLANG_TIDY ON CACHE BOOL "")
+set(CMake_USE_CLANG_TIDY_MODULE ON CACHE BOOL "")
+set(CMake_CLANG_TIDY_MODULE "$ENV{CI_PROJECT_DIR}/Utilities/ClangTidyModule/build/libcmake-clang-tidy-module.so" CACHE FILEPATH "")
+
+include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora38_common.cmake")
diff --git a/.gitlab/ci/configure_linux_clang_cxx_modules_ninja.cmake b/.gitlab/ci/configure_linux_clang_cxx_modules_ninja.cmake
deleted file mode 100644
index 43bccdb..0000000
--- a/.gitlab/ci/configure_linux_clang_cxx_modules_ninja.cmake
+++ /dev/null
@@ -1,4 +0,0 @@
-set(CMake_TEST_MODULE_COMPILATION "named,partitions,internal_partitions,export_bmi,install_bmi,shared" CACHE STRING "")
-set(CMake_TEST_MODULE_COMPILATION_RULES "${CMAKE_CURRENT_LIST_DIR}/cxx_modules_rules_clang.cmake" CACHE STRING "")
-
-include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake")
diff --git a/.gitlab/ci/configure_linux_clang_cxx_modules_ninja_multi.cmake b/.gitlab/ci/configure_linux_clang_cxx_modules_ninja_multi.cmake
deleted file mode 100644
index 43bccdb..0000000
--- a/.gitlab/ci/configure_linux_clang_cxx_modules_ninja_multi.cmake
+++ /dev/null
@@ -1,4 +0,0 @@
-set(CMake_TEST_MODULE_COMPILATION "named,partitions,internal_partitions,export_bmi,install_bmi,shared" CACHE STRING "")
-set(CMake_TEST_MODULE_COMPILATION_RULES "${CMAKE_CURRENT_LIST_DIR}/cxx_modules_rules_clang.cmake" CACHE STRING "")
-
-include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake")
diff --git a/.gitlab/ci/configure_macos_arm64_ninja.cmake b/.gitlab/ci/configure_macos_arm64_ninja.cmake
index f59b43c..f2068a1 100644
--- a/.gitlab/ci/configure_macos_arm64_ninja.cmake
+++ b/.gitlab/ci/configure_macos_arm64_ninja.cmake
@@ -1,3 +1,4 @@
+set(CMake_TEST_FindOpenAL "ON" CACHE BOOL "")
 set(CMake_TEST_FindOpenMP "ON" CACHE BOOL "")
 set(CMake_TEST_FindOpenMP_C "ON" CACHE BOOL "")
 set(CMake_TEST_FindOpenMP_CXX "ON" CACHE BOOL "")
diff --git a/.gitlab/ci/configure_macos_x86_64_ninja_multi.cmake b/.gitlab/ci/configure_macos_arm64_ninja_multi.cmake
similarity index 100%
rename from .gitlab/ci/configure_macos_x86_64_ninja_multi.cmake
rename to .gitlab/ci/configure_macos_arm64_ninja_multi.cmake
diff --git a/.gitlab/ci/configure_macos_arm64_xcode_ub.cmake b/.gitlab/ci/configure_macos_arm64_xcode_ub.cmake
new file mode 100644
index 0000000..1b976d2
--- /dev/null
+++ b/.gitlab/ci/configure_macos_arm64_xcode_ub.cmake
@@ -0,0 +1,2 @@
+include("${CMAKE_CURRENT_LIST_DIR}/configure_macos_common.cmake")
+include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake")
diff --git a/.gitlab/ci/configure_macos_package_common.cmake b/.gitlab/ci/configure_macos_package_common.cmake
index 3aa8ae2..8aab41b 100644
--- a/.gitlab/ci/configure_macos_package_common.cmake
+++ b/.gitlab/ci/configure_macos_package_common.cmake
@@ -10,12 +10,12 @@
 set(CMAKE_C_STANDARD "11" CACHE STRING "")
 set(CMAKE_CXX_STANDARD "14" CACHE STRING "")
 set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64" CACHE STRING "")
-set(CMAKE_SKIP_BOOTSTRAP_TEST "TRUE" CACHE STRING "")
 set(BUILD_CursesDialog "ON" CACHE BOOL "")
 set(BUILD_QtDialog "TRUE" CACHE BOOL "")
 set(CMake_GUI_DISTRIBUTE_WITH_Qt_LGPL "3" CACHE STRING "")
 set(CMake_INSTALL_DEPENDENCIES "ON" CACHE BOOL "")
 set(CMAKE_SKIP_RPATH "TRUE" CACHE BOOL "")
+set(CMake_TEST_BOOTSTRAP OFF CACHE BOOL "")
 set(CMake_TEST_NO_FindPackageModeMakefileTest "TRUE" CACHE BOOL "")
 
 # XXX(sccache): restore sccache when it works for multiple architectures:
diff --git a/.gitlab/ci/configure_macos_x86_64_makefiles.cmake b/.gitlab/ci/configure_macos_x86_64_makefiles.cmake
index 3c5d8fe..5d1620d 100644
--- a/.gitlab/ci/configure_macos_x86_64_makefiles.cmake
+++ b/.gitlab/ci/configure_macos_x86_64_makefiles.cmake
@@ -1,3 +1,4 @@
+set(CMake_TEST_FindOpenAL "ON" CACHE BOOL "")
 set(CMake_TEST_FindOpenMP "ON" CACHE BOOL "")
 set(CMake_TEST_FindOpenMP_C "ON" CACHE BOOL "")
 set(CMake_TEST_FindOpenMP_CXX "ON" CACHE BOOL "")
diff --git a/.gitlab/ci/configure_macos_x86_64_ninja.cmake b/.gitlab/ci/configure_macos_x86_64_ninja.cmake
index 3c5d8fe..5d1620d 100644
--- a/.gitlab/ci/configure_macos_x86_64_ninja.cmake
+++ b/.gitlab/ci/configure_macos_x86_64_ninja.cmake
@@ -1,3 +1,4 @@
+set(CMake_TEST_FindOpenAL "ON" CACHE BOOL "")
 set(CMake_TEST_FindOpenMP "ON" CACHE BOOL "")
 set(CMake_TEST_FindOpenMP_C "ON" CACHE BOOL "")
 set(CMake_TEST_FindOpenMP_CXX "ON" CACHE BOOL "")
diff --git a/.gitlab/ci/configure_macos_x86_64_ninja_ub.cmake b/.gitlab/ci/configure_macos_x86_64_ninja_ub.cmake
new file mode 100644
index 0000000..1b976d2
--- /dev/null
+++ b/.gitlab/ci/configure_macos_x86_64_ninja_ub.cmake
@@ -0,0 +1,2 @@
+include("${CMAKE_CURRENT_LIST_DIR}/configure_macos_common.cmake")
+include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake")
diff --git a/.gitlab/ci/configure_sphinx.cmake b/.gitlab/ci/configure_sphinx.cmake
index 3750309..9f3f0be 100644
--- a/.gitlab/ci/configure_sphinx.cmake
+++ b/.gitlab/ci/configure_sphinx.cmake
@@ -4,3 +4,6 @@
 set(SPHINX_SINGLEHTML ON CACHE BOOL "")
 set(SPHINX_QTHELP ON CACHE BOOL "")
 set(SPHINX_TEXT ON CACHE BOOL "")
+if(NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "")
+  set(SPHINX_LINKCHECK ON CACHE BOOL "")
+endif()
diff --git a/.gitlab/ci/configure_windows_arm64_vs2022.cmake b/.gitlab/ci/configure_windows_arm64_vs2022.cmake
index c7d41ea..290d380 100644
--- a/.gitlab/ci/configure_windows_arm64_vs2022.cmake
+++ b/.gitlab/ci/configure_windows_arm64_vs2022.cmake
@@ -1 +1,4 @@
+set(CMake_TEST_MODULE_COMPILATION "named,partitions,internal_partitions" CACHE STRING "")
+
+include("${CMAKE_CURRENT_LIST_DIR}/configure_windows_msvc_cxx_modules_common.cmake")
 include("${CMAKE_CURRENT_LIST_DIR}/configure_windows_vs_common.cmake")
diff --git a/.gitlab/ci/configure_windows_clang_ninja.cmake b/.gitlab/ci/configure_windows_clang_ninja.cmake
index ba19834..f864dde 100644
--- a/.gitlab/ci/configure_windows_clang_ninja.cmake
+++ b/.gitlab/ci/configure_windows_clang_ninja.cmake
@@ -1 +1,5 @@
+if("$ENV{CMAKE_CI_BUILD_NAME}" MATCHES "(^|_)gnu(_|$)")
+  set(CMake_TEST_MODULE_COMPILATION "named,collation,partitions,internal_partitions,export_bmi,install_bmi,shared" CACHE STRING "")
+  set(CMake_TEST_MODULE_COMPILATION_RULES "${CMAKE_CURRENT_LIST_DIR}/cxx_modules_rules_clang.cmake" CACHE STRING "")
+endif()
 include("${CMAKE_CURRENT_LIST_DIR}/configure_windows_clang_common.cmake")
diff --git a/.gitlab/ci/configure_windows_intelclassic_ninja.cmake b/.gitlab/ci/configure_windows_intelclassic_ninja.cmake
new file mode 100644
index 0000000..c2d708b
--- /dev/null
+++ b/.gitlab/ci/configure_windows_intelclassic_ninja.cmake
@@ -0,0 +1 @@
+include("${CMAKE_CURRENT_LIST_DIR}/configure_windows_intelcompiler_common.cmake")
diff --git a/.gitlab/ci/configure_windows_intelcompiler_common.cmake b/.gitlab/ci/configure_windows_intelcompiler_common.cmake
new file mode 100644
index 0000000..55dce1d
--- /dev/null
+++ b/.gitlab/ci/configure_windows_intelcompiler_common.cmake
@@ -0,0 +1,5 @@
+set(CMake_TEST_Java OFF CACHE BOOL "")
+
+set(configure_no_sccache 1)
+
+include("${CMAKE_CURRENT_LIST_DIR}/configure_external_test.cmake")
diff --git a/.gitlab/ci/configure_windows_inteloneapi_ninja.cmake b/.gitlab/ci/configure_windows_inteloneapi_ninja.cmake
new file mode 100644
index 0000000..c2d708b
--- /dev/null
+++ b/.gitlab/ci/configure_windows_inteloneapi_ninja.cmake
@@ -0,0 +1 @@
+include("${CMAKE_CURRENT_LIST_DIR}/configure_windows_intelcompiler_common.cmake")
diff --git a/.gitlab/ci/configure_windows_package_common.cmake b/.gitlab/ci/configure_windows_package_common.cmake
index b3929a4..541a541 100644
--- a/.gitlab/ci/configure_windows_package_common.cmake
+++ b/.gitlab/ci/configure_windows_package_common.cmake
@@ -13,7 +13,7 @@
 # Disable ccmake.
 set(BUILD_CursesDialog "OFF" CACHE BOOL "")
 
-set(CMAKE_SKIP_BOOTSTRAP_TEST "TRUE" CACHE STRING "")
+set(CMake_TEST_BOOTSTRAP OFF CACHE BOOL "")
 set(CMake_TEST_Java OFF CACHE BOOL "")
 set(CMake_TEST_Qt5 OFF CACHE BOOL "")
 set(CMake_TEST_Qt6 OFF CACHE BOOL "")
diff --git a/.gitlab/ci/configure_windows_vs2022_x64.cmake b/.gitlab/ci/configure_windows_vs2022_x64.cmake
index 1e0f584..290d380 100644
--- a/.gitlab/ci/configure_windows_vs2022_x64.cmake
+++ b/.gitlab/ci/configure_windows_vs2022_x64.cmake
@@ -1,4 +1,4 @@
-set(CMake_TEST_MODULE_COMPILATION "named,partitions" CACHE STRING "")
+set(CMake_TEST_MODULE_COMPILATION "named,partitions,internal_partitions" CACHE STRING "")
 
 include("${CMAKE_CURRENT_LIST_DIR}/configure_windows_msvc_cxx_modules_common.cmake")
 include("${CMAKE_CURRENT_LIST_DIR}/configure_windows_vs_common.cmake")
diff --git a/.gitlab/ci/configure_windows_vs2022_x64_ninja.cmake b/.gitlab/ci/configure_windows_vs2022_x64_ninja.cmake
index 5bf0be8..54abf72 100644
--- a/.gitlab/ci/configure_windows_vs2022_x64_ninja.cmake
+++ b/.gitlab/ci/configure_windows_vs2022_x64_ninja.cmake
@@ -1,4 +1,5 @@
 if (NOT "$ENV{CMAKE_CI_NIGHTLY}" STREQUAL "")
+  set(CMake_TEST_CPACK_INNOSETUP "ON" CACHE STRING "")
   set(CMake_TEST_ISPC "ON" CACHE STRING "")
 endif()
 set(CMake_TEST_TLS_VERIFY_URL "https://gitlab.kitware.com" CACHE STRING "")
diff --git a/.gitlab/ci/configure_windows_vs_common.cmake b/.gitlab/ci/configure_windows_vs_common.cmake
index 962f03d..daf9aaa 100644
--- a/.gitlab/ci/configure_windows_vs_common.cmake
+++ b/.gitlab/ci/configure_windows_vs_common.cmake
@@ -4,6 +4,7 @@
 set(CMake_TEST_FindOpenMP_C "ON" CACHE BOOL "")
 set(CMake_TEST_FindOpenMP_CXX "ON" CACHE BOOL "")
 set(CMake_TEST_FindOpenMP_Fortran "OFF" CACHE BOOL "")
+set(CMake_TEST_FindPatch "ON" CACHE BOOL "")
 set(CMake_TEST_Java OFF CACHE BOOL "")
 set(CMake_TEST_MFC "ON" CACHE BOOL "")
 
diff --git a/.gitlab/ci/configure_windows_vs_common_ninja.cmake b/.gitlab/ci/configure_windows_vs_common_ninja.cmake
index 1ae1a66..9f7acc3 100644
--- a/.gitlab/ci/configure_windows_vs_common_ninja.cmake
+++ b/.gitlab/ci/configure_windows_vs_common_ninja.cmake
@@ -6,6 +6,7 @@
 set(CMake_TEST_FindOpenMP_C "ON" CACHE BOOL "")
 set(CMake_TEST_FindOpenMP_CXX "ON" CACHE BOOL "")
 set(CMake_TEST_FindOpenMP_Fortran "OFF" CACHE BOOL "")
+set(CMake_TEST_FindPatch "ON" CACHE BOOL "")
 set(CMake_TEST_IPO_WORKS_C "ON" CACHE BOOL "")
 set(CMake_TEST_IPO_WORKS_CXX "ON" CACHE BOOL "")
 set(CMake_TEST_MFC "ON" CACHE BOOL "")
diff --git a/.gitlab/ci/ctest_memcheck_fedora37_asan.lsan.supp b/.gitlab/ci/ctest_memcheck_fedora38_asan.lsan.supp
similarity index 100%
rename from .gitlab/ci/ctest_memcheck_fedora37_asan.lsan.supp
rename to .gitlab/ci/ctest_memcheck_fedora38_asan.lsan.supp
diff --git a/.gitlab/ci/docker/clang_cxx_modules/Dockerfile b/.gitlab/ci/docker/clang_cxx_modules/Dockerfile
deleted file mode 100644
index 4e58125..0000000
--- a/.gitlab/ci/docker/clang_cxx_modules/Dockerfile
+++ /dev/null
@@ -1,13 +0,0 @@
-FROM fedora:37
-MAINTAINER Ben Boeckel <ben.boeckel@kitware.com>
-
-# Install build dependencies for packages.
-COPY install_deps.sh /root/install_deps.sh
-RUN sh /root/install_deps.sh
-
-COPY install_llvm.sh /root/install_llvm.sh
-RUN sh /root/install_llvm.sh
-
-# Install build dependencies for CMake's CI.
-COPY install_cmake_deps.sh /root/install_cmake_deps.sh
-RUN sh /root/install_cmake_deps.sh
diff --git a/.gitlab/ci/docker/clang_cxx_modules/install_cmake_deps.sh b/.gitlab/ci/docker/clang_cxx_modules/install_cmake_deps.sh
deleted file mode 100755
index 465e125..0000000
--- a/.gitlab/ci/docker/clang_cxx_modules/install_cmake_deps.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/sh
-
-set -e
-
-dnf install -y --setopt=install_weak_deps=False \
-    file git-core
-dnf clean all
diff --git a/.gitlab/ci/docker/clang_cxx_modules/install_deps.sh b/.gitlab/ci/docker/clang_cxx_modules/install_deps.sh
deleted file mode 100755
index c1957c3..0000000
--- a/.gitlab/ci/docker/clang_cxx_modules/install_deps.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/sh
-
-set -e
-
-dnf install -y --setopt=install_weak_deps=False \
-    gcc-c++ cmake ninja-build
-dnf clean all
diff --git a/.gitlab/ci/docker/clang_cxx_modules/install_llvm.sh b/.gitlab/ci/docker/clang_cxx_modules/install_llvm.sh
deleted file mode 100755
index 35f925e..0000000
--- a/.gitlab/ci/docker/clang_cxx_modules/install_llvm.sh
+++ /dev/null
@@ -1,39 +0,0 @@
-#!/bin/sh
-
-set -e
-
-readonly revision="6d859df46e93e04bd7a4f90d9a9056763998f638" # llvmorg-16.0.0-rc2-31-g6d859df46e93
-readonly tarball="https://github.com/llvm/llvm-project/archive/$revision.tar.gz"
-
-readonly workdir="$HOME/llvm"
-readonly srcdir="$workdir/llvm"
-readonly builddir="$workdir/build"
-
-mkdir -p "$workdir"
-cd "$workdir"
-curl -L "$tarball" > "llvm-$revision.tar.gz"
-tar xf "llvm-$revision.tar.gz"
-mv "llvm-project-$revision" "$srcdir"
-mkdir -p "$builddir"
-cd "$builddir"
-cmake -GNinja \
-    -DCMAKE_BUILD_TYPE=Release \
-    -DBUILD_SHARED_LIBS=ON \
-    -DLLVM_ENABLE_BINDINGS=OFF \
-    -DLLVM_INCLUDE_BENCHMARKS=OFF \
-    -DLLVM_INCLUDE_DOCS=OFF \
-    -DLLVM_INCLUDE_EXAMPLES=OFF \
-    -DLLVM_INCLUDE_RUNTIMES=OFF \
-    -DLLVM_INCLUDE_TESTS=OFF \
-    -DLLVM_INCLUDE_UTILS=OFF \
-    -DLLVM_TARGETS_TO_BUILD=X86 \
-    -DLLVM_TOOL_CLANG_BUILD=ON \
-    -DLLVM_USE_SYMLINKS=ON \
-    "-DLLVM_EXTERNAL_CLANG_SOURCE_DIR=$srcdir/clang" \
-    -DLLVM_PARALLEL_LINK_JOBS=1 \
-    -DCLANG_BUILD_TOOLS=ON \
-    "-DCMAKE_INSTALL_PREFIX=/opt/llvm-p1689" \
-    "$srcdir/llvm"
-ninja
-ninja install/strip
-rm -rf "$workdir"
diff --git a/.gitlab/ci/docker/debian10-aarch64/deps_packages.lst b/.gitlab/ci/docker/debian10-aarch64/deps_packages.lst
index 5e30e16..ca83323 100644
--- a/.gitlab/ci/docker/debian10-aarch64/deps_packages.lst
+++ b/.gitlab/ci/docker/debian10-aarch64/deps_packages.lst
@@ -59,6 +59,9 @@
 libgsl-dev
 libgtest-dev
 libgtk2.0-dev
+libhdf5-dev
+libhdf5-mpich-dev
+libhdf5-openmpi-dev
 libicu-dev
 libinput-dev
 libjpeg-dev
@@ -76,6 +79,7 @@
 libsqlite3-dev
 libtiff-dev
 libuv1-dev
+libwxgtk3.0-dev
 libx11-dev
 libxalan-c-dev
 libxerces-c-dev
diff --git a/.gitlab/ci/docker/debian10/deps_packages.lst b/.gitlab/ci/docker/debian10/deps_packages.lst
index 3df41f5..0b79675 100644
--- a/.gitlab/ci/docker/debian10/deps_packages.lst
+++ b/.gitlab/ci/docker/debian10/deps_packages.lst
@@ -62,6 +62,9 @@
 libgsl-dev
 libgtest-dev
 libgtk2.0-dev
+libhdf5-dev
+libhdf5-mpich-dev
+libhdf5-openmpi-dev
 libicu-dev
 libinput-dev
 libjpeg-dev
@@ -79,6 +82,7 @@
 libsqlite3-dev
 libtiff-dev
 libuv1-dev
+libwxgtk3.0-dev
 libx11-dev
 libxalan-c-dev
 libxerces-c-dev
diff --git a/.gitlab/ci/docker/fedora37/Dockerfile b/.gitlab/ci/docker/fedora37/Dockerfile
deleted file mode 100644
index b36a17e..0000000
--- a/.gitlab/ci/docker/fedora37/Dockerfile
+++ /dev/null
@@ -1,73 +0,0 @@
-# syntax=docker/dockerfile:1
-
-ARG BASE_IMAGE=fedora:37
-
-FROM ${BASE_IMAGE} AS dnf-cache
-# Populate DNF cache w/ the fresh metadata and prefetch packages.
-RUN --mount=type=bind,source=clang_tidy_headers_packages.lst,target=/root/clang_tidy_headers_packages.lst \
-    --mount=type=bind,source=deps_packages.lst,target=/root/deps_packages.lst \
-    --mount=type=bind,source=iwyu_packages.lst,target=/root/iwyu_packages.lst \
-    --mount=type=bind,source=rvm_packages.lst,target=/root/rvm_packages.lst \
-    --mount=type=tmpfs,target=/var/log \
-    --mount=type=tmpfs,target=/tmp \
-    dnf install \
-        --setopt=install_weak_deps=False \
-        --setopt=fastestmirror=True \
-        --setopt=max_parallel_downloads=10 \
-        --downloadonly \
-        -y \
-        $(grep -h '^[^#]\+$' /root/*.lst)
-
-
-FROM ${BASE_IMAGE} AS rvm-build
-LABEL maintainer="Ben Boeckel <ben.boeckel@kitware.com>"
-
-RUN --mount=type=bind,source=install_rvm.sh,target=/root/install_rvm.sh \
-    --mount=type=bind,source=rvm_packages.lst,target=/root/rvm_packages.lst \
-    --mount=type=cache,from=dnf-cache,source=/var/cache/dnf,target=/var/cache/dnf,sharing=private \
-    --mount=type=tmpfs,target=/var/log \
-    --mount=type=tmpfs,target=/tmp \
-    sh /root/install_rvm.sh
-
-
-FROM ${BASE_IMAGE} AS clang-tidy-headers
-LABEL maintainer="Kyle Edwards <kyle.edwards@kitware.com>"
-
-RUN --mount=type=bind,source=install_clang_tidy_headers.sh,target=/root/install_clang_tidy_headers.sh \
-    --mount=type=bind,source=clang_tidy_headers_packages.lst,target=/root/clang_tidy_headers_packages.lst \
-    --mount=type=cache,from=dnf-cache,source=/var/cache/dnf,target=/var/cache/dnf,sharing=private \
-    --mount=type=tmpfs,target=/var/log \
-    --mount=type=tmpfs,target=/tmp \
-    sh /root/install_clang_tidy_headers.sh
-
-
-FROM ${BASE_IMAGE} AS iwyu-build
-LABEL maintainer="Kyle Edwards <kyle.edwards@kitware.com>"
-
-RUN --mount=type=bind,source=install_iwyu.sh,target=/root/install_iwyu.sh \
-    --mount=type=bind,source=iwyu_packages.lst,target=/root/iwyu_packages.lst \
-    --mount=type=cache,from=dnf-cache,source=/var/cache/dnf,target=/var/cache/dnf,sharing=private \
-    --mount=type=tmpfs,target=/var/log \
-    --mount=type=tmpfs,target=/tmp \
-    sh /root/install_iwyu.sh
-
-
-FROM ${BASE_IMAGE}
-LABEL maintainer="Ben Boeckel <ben.boeckel@kitware.com>"
-
-RUN --mount=type=bind,source=install_deps.sh,target=/root/install_deps.sh \
-    --mount=type=bind,source=deps_packages.lst,target=/root/deps_packages.lst \
-    --mount=type=cache,from=dnf-cache,source=/var/cache/dnf,target=/var/cache/dnf,sharing=private \
-    --mount=type=cache,target=/var/cache/pip \
-    --mount=type=tmpfs,target=/var/log \
-    --mount=type=tmpfs,target=/tmp \
-    sh /root/install_deps.sh
-
-RUN --mount=type=bind,from=rvm-build,source=/root,target=/root \
-    tar -C /usr/local -xf /root/rvm.tar
-
-RUN --mount=type=bind,from=clang-tidy-headers,source=/root,target=/root \
-    tar -C /usr/include -xf /root/clang-tidy-headers.tar
-
-RUN --mount=type=bind,from=iwyu-build,source=/root,target=/root \
-    tar -C / -xf /root/iwyu.tar
diff --git a/.gitlab/ci/docker/fedora37/clang_tidy_headers_packages.lst b/.gitlab/ci/docker/fedora37/clang_tidy_headers_packages.lst
deleted file mode 100644
index fe86105..0000000
--- a/.gitlab/ci/docker/fedora37/clang_tidy_headers_packages.lst
+++ /dev/null
@@ -1,4 +0,0 @@
-dnf-command(download)
-rpm-build
-python3-devel
-clang-tools-extra
diff --git a/.gitlab/ci/docker/fedora37/deps_packages.lst b/.gitlab/ci/docker/fedora37/deps_packages.lst
deleted file mode 100644
index 9ce8007..0000000
--- a/.gitlab/ci/docker/fedora37/deps_packages.lst
+++ /dev/null
@@ -1,110 +0,0 @@
-# Install build requirements.
-ncurses-devel
-openssl-devel
-qt5-qtbase-devel
-qt6-qtbase-devel
-
-# Install development tools.
-clang
-clang-tools-extra
-compiler-rt
-flang
-flang-devel
-gcc-c++
-git-core
-make
-
-# Install optional external build dependencies.
-bzip2-devel
-expat-devel
-jsoncpp-devel
-libarchive-devel
-libcurl-devel
-libuv-devel
-libuv-devel
-libzstd-devel
-rhash-devel
-xz-devel
-zlib-devel
-
-# Install documentation tools.
-python3-sphinx
-texinfo
-qt5-qttools-devel
-qt6-qttools-devel
-
-# Install lint tools.
-clang-analyzer
-codespell
-
-# Tools needed for the test suite.
-findutils
-file
-jq
-which
-
-# Packages needed to test CTest.
-breezy
-subversion
-mercurial
-
-# Packages needed to test CPack.
-rpm-build
-
-# Packages needed to test find modules.
-alsa-lib-devel
-blas-devel
-boost-devel boost-python3-devel
-bzip2-devel
-cups-devel
-DevIL-devel
-doxygen
-expat-devel
-fontconfig-devel
-freeglut-devel
-freetype-devel
-gdal-devel
-gettext
-giflib-devel
-glew-devel
-gmock
-gnutls-devel
-grpc-devel grpc-plugins
-gsl-devel
-gtest-devel
-gtk2-devel
-ImageMagick-c++-devel
-java-11-openjdk-devel
-jsoncpp-devel
-lapack-devel
-libarchive-devel
-libcurl-devel
-libicu-devel
-libinput-devel systemd-devel
-libjpeg-turbo-devel
-libpng-devel
-opensp-devel
-postgresql-server-devel
-libtiff-devel
-libuv-devel
-libxml2-devel
-libxslt-devel
-mpich-devel
-openal-soft-devel
-openmpi-devel
-patch
-perl
-protobuf-devel protobuf-c-devel protobuf-lite-devel
-pypy2 pypy2-devel
-pypy3 pypy3-devel
-python2 python2-devel
-python3 python3-devel python3-numpy
-python3-jsmin python3-jsonschema
-ruby rubygems ruby-devel
-SDL-devel
-sqlite-devel
-swig
-unixODBC-devel
-xalan-c-devel
-xerces-c-devel
-xz-devel
diff --git a/.gitlab/ci/docker/fedora37/install_clang_tidy_headers.sh b/.gitlab/ci/docker/fedora37/install_clang_tidy_headers.sh
deleted file mode 100755
index 200fa1e..0000000
--- a/.gitlab/ci/docker/fedora37/install_clang_tidy_headers.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/sh
-
-set -e
-
-# Packages for building the clang-tidy plugin.
-# TODO: Upstream this as a proper Fedora package.
-dnf install \
-    --setopt=install_weak_deps=False \
-    --setopt=fastestmirror=True \
-    --setopt=max_parallel_downloads=10 \
-    -y \
-    $(grep '^[^#]\+$' /root/clang_tidy_headers_packages.lst)
-
-clang_source_rpm=$(rpm -q --queryformat '%{SOURCERPM}' clang-tools-extra)
-clang_version=$(rpm -q --queryformat '%{VERSION}' clang-tools-extra)
-dnf download --source -y clang
-rpm -i "$clang_source_rpm"
-rpmbuild -bp /root/rpmbuild/SPECS/clang.spec
-cd "/root/rpmbuild/BUILD/clang-tools-extra-$clang_version.src"
-find clang-tidy -name '*.h' | tar -cf /root/clang-tidy-headers.tar -T -
diff --git a/.gitlab/ci/docker/fedora38/Dockerfile b/.gitlab/ci/docker/fedora38/Dockerfile
new file mode 100644
index 0000000..4918693
--- /dev/null
+++ b/.gitlab/ci/docker/fedora38/Dockerfile
@@ -0,0 +1,58 @@
+# syntax=docker/dockerfile:1
+
+ARG BASE_IMAGE=fedora:38
+
+FROM ${BASE_IMAGE} AS dnf-cache
+# Populate DNF cache w/ the fresh metadata and prefetch packages.
+RUN --mount=type=bind,source=deps_packages.lst,target=/root/deps_packages.lst \
+    --mount=type=bind,source=iwyu_packages.lst,target=/root/iwyu_packages.lst \
+    --mount=type=bind,source=rvm_packages.lst,target=/root/rvm_packages.lst \
+    --mount=type=tmpfs,target=/var/log \
+    --mount=type=tmpfs,target=/tmp \
+    dnf install \
+        --setopt=install_weak_deps=False \
+        --setopt=fastestmirror=True \
+        --setopt=max_parallel_downloads=10 \
+        --downloadonly \
+        -y \
+        $(grep -h '^[^#]\+$' /root/*.lst)
+
+
+FROM ${BASE_IMAGE} AS rvm-build
+LABEL maintainer="Ben Boeckel <ben.boeckel@kitware.com>"
+
+RUN --mount=type=bind,source=install_rvm.sh,target=/root/install_rvm.sh \
+    --mount=type=bind,source=rvm_packages.lst,target=/root/rvm_packages.lst \
+    --mount=type=cache,from=dnf-cache,source=/var/cache/dnf,target=/var/cache/dnf,sharing=private \
+    --mount=type=tmpfs,target=/var/log \
+    --mount=type=tmpfs,target=/tmp \
+    sh /root/install_rvm.sh
+
+
+FROM ${BASE_IMAGE} AS iwyu-build
+LABEL maintainer="Kyle Edwards <kyle.edwards@kitware.com>"
+
+RUN --mount=type=bind,source=install_iwyu.sh,target=/root/install_iwyu.sh \
+    --mount=type=bind,source=iwyu_packages.lst,target=/root/iwyu_packages.lst \
+    --mount=type=cache,from=dnf-cache,source=/var/cache/dnf,target=/var/cache/dnf,sharing=private \
+    --mount=type=tmpfs,target=/var/log \
+    --mount=type=tmpfs,target=/tmp \
+    sh /root/install_iwyu.sh
+
+
+FROM ${BASE_IMAGE}
+LABEL maintainer="Ben Boeckel <ben.boeckel@kitware.com>"
+
+RUN --mount=type=bind,source=install_deps.sh,target=/root/install_deps.sh \
+    --mount=type=bind,source=deps_packages.lst,target=/root/deps_packages.lst \
+    --mount=type=cache,from=dnf-cache,source=/var/cache/dnf,target=/var/cache/dnf,sharing=private \
+    --mount=type=cache,target=/var/cache/pip \
+    --mount=type=tmpfs,target=/var/log \
+    --mount=type=tmpfs,target=/tmp \
+    sh /root/install_deps.sh
+
+RUN --mount=type=bind,from=rvm-build,source=/root,target=/root \
+    tar -C /usr/local -xf /root/rvm.tar
+
+RUN --mount=type=bind,from=iwyu-build,source=/root,target=/root \
+    tar -C / -xf /root/iwyu.tar
diff --git a/.gitlab/ci/docker/fedora38/deps_packages.lst b/.gitlab/ci/docker/fedora38/deps_packages.lst
new file mode 100644
index 0000000..c7c1385
--- /dev/null
+++ b/.gitlab/ci/docker/fedora38/deps_packages.lst
@@ -0,0 +1,115 @@
+# Install build requirements.
+ncurses-devel
+openssl-devel
+qt5-qtbase-devel
+qt6-qtbase-devel
+
+# Install development tools.
+clang
+clang-tools-extra
+clang-tools-extra-devel
+compiler-rt
+flang
+flang-devel
+gcc-c++
+git-core
+make
+
+# Install optional external build dependencies.
+bzip2-devel
+expat-devel
+jsoncpp-devel
+libarchive-devel
+libcurl-devel
+libuv-devel
+libuv-devel
+libzstd-devel
+rhash-devel
+xz-devel
+zlib-devel
+
+# Install documentation tools.
+python3-sphinx
+texinfo
+qt5-qttools-devel
+qt6-qttools-devel
+
+# Install lint tools.
+clang-analyzer
+codespell
+
+# Tools needed for the test suite.
+findutils
+file
+jq
+which
+
+# Packages needed to test CTest.
+breezy
+subversion
+mercurial
+
+# Packages needed to test CPack.
+rpm-build
+
+# Packages needed to test find modules.
+alsa-lib-devel
+blas-devel
+boost-devel boost-python3-devel
+bzip2-devel
+cups-devel
+DevIL-devel
+doxygen
+expat-devel
+fontconfig-devel
+freeglut-devel
+freetype-devel
+gdal-devel
+gettext
+giflib-devel
+glew-devel
+gmock
+gnutls-devel
+grpc-devel grpc-plugins
+gsl-devel
+gtest-devel
+gtk2-devel
+hdf5-devel
+hdf5-mpich-devel
+hdf5-openmpi-devel
+ImageMagick-c++-devel
+java-11-openjdk-devel
+jsoncpp-devel
+lapack-devel
+libarchive-devel
+libcurl-devel
+libicu-devel
+libinput-devel systemd-devel
+libjpeg-turbo-devel
+libpng-devel
+opensp-devel
+postgresql-server-devel
+libtiff-devel
+libuv-devel
+libxml2-devel
+libxslt-devel
+mpich-devel
+openal-soft-devel
+openmpi-devel
+patch
+perl
+protobuf-devel protobuf-c-devel protobuf-lite-devel
+pypy2 pypy2-devel
+pypy3 pypy3-devel
+python2 python2-devel
+python3 python3-devel python3-numpy
+python3-jsmin python3-jsonschema
+ruby rubygems ruby-devel
+SDL-devel
+sqlite-devel
+swig
+unixODBC-devel
+wxGTK-devel
+xalan-c-devel
+xerces-c-devel
+xz-devel
diff --git a/.gitlab/ci/docker/fedora37/install_deps.sh b/.gitlab/ci/docker/fedora38/install_deps.sh
similarity index 100%
rename from .gitlab/ci/docker/fedora37/install_deps.sh
rename to .gitlab/ci/docker/fedora38/install_deps.sh
diff --git a/.gitlab/ci/docker/fedora37/install_iwyu.sh b/.gitlab/ci/docker/fedora38/install_iwyu.sh
similarity index 100%
rename from .gitlab/ci/docker/fedora37/install_iwyu.sh
rename to .gitlab/ci/docker/fedora38/install_iwyu.sh
diff --git a/.gitlab/ci/docker/fedora37/install_rvm.sh b/.gitlab/ci/docker/fedora38/install_rvm.sh
similarity index 100%
rename from .gitlab/ci/docker/fedora37/install_rvm.sh
rename to .gitlab/ci/docker/fedora38/install_rvm.sh
diff --git a/.gitlab/ci/docker/fedora37/iwyu_packages.lst b/.gitlab/ci/docker/fedora38/iwyu_packages.lst
similarity index 100%
rename from .gitlab/ci/docker/fedora37/iwyu_packages.lst
rename to .gitlab/ci/docker/fedora38/iwyu_packages.lst
diff --git a/.gitlab/ci/docker/fedora37/rvm_packages.lst b/.gitlab/ci/docker/fedora38/rvm_packages.lst
similarity index 100%
rename from .gitlab/ci/docker/fedora37/rvm_packages.lst
rename to .gitlab/ci/docker/fedora38/rvm_packages.lst
diff --git a/.gitlab/ci/download_python3.cmake b/.gitlab/ci/download_python3.cmake
deleted file mode 100644
index 0f5b18b..0000000
--- a/.gitlab/ci/download_python3.cmake
+++ /dev/null
@@ -1,41 +0,0 @@
-cmake_minimum_required(VERSION 3.17)
-
-set(version "3.8.6")
-set(sha256sum "376e18eef7e3ea467f0e3af041b01fc7e2f12855506c2ab2653ceb5e0951212e")
-set(dirname "python-${version}-embed-win-x86_64")
-set(tarball "${dirname}.tar.xz")
-
-# Download the file.
-file(DOWNLOAD
-  "https://cmake.org/files/dependencies/${tarball}"
-  ".gitlab/${tarball}"
-  STATUS download_status
-  EXPECTED_HASH "SHA256=${sha256sum}")
-
-# Check the download status.
-list(GET download_status 0 res)
-if (res)
-  list(GET download_status 1 err)
-  message(FATAL_ERROR
-    "Failed to download ${tarball}: ${err}")
-endif ()
-
-# Extract the file.
-execute_process(
-  COMMAND
-    "${CMAKE_COMMAND}"
-    -E tar
-    xzf "${tarball}"
-  WORKING_DIRECTORY ".gitlab"
-  RESULT_VARIABLE res
-  ERROR_VARIABLE err
-  ERROR_STRIP_TRAILING_WHITESPACE)
-if (res)
-  message(FATAL_ERROR
-    "Failed to extract ${tarball}: ${err}")
-endif ()
-
-# Move to a predictable directory.
-file(RENAME
-  ".gitlab/${dirname}"
-  ".gitlab/python3")
diff --git a/.gitlab/ci/env_fedora37_common_clang.sh b/.gitlab/ci/env_fedora37_common_clang.sh
deleted file mode 100644
index b03b757..0000000
--- a/.gitlab/ci/env_fedora37_common_clang.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-export CC=/usr/bin/clang-15
-export CXX=/usr/bin/clang++-15
-export FC=/usr/bin/flang-new
-export FFLAGS=-flang-experimental-exec
diff --git a/.gitlab/ci/env_fedora37_makefiles.cmake b/.gitlab/ci/env_fedora37_makefiles.cmake
deleted file mode 100644
index 2bcb6d0..0000000
--- a/.gitlab/ci/env_fedora37_makefiles.cmake
+++ /dev/null
@@ -1,2 +0,0 @@
-set(ENV{MY_RUBY_HOME} "/usr/local/rvm/rubies/ruby-3.0.4")
-set(ENV{PATH} "/usr/lib64/mpich/bin:$ENV{PATH}")
diff --git a/.gitlab/ci/env_fedora37_makefiles.sh b/.gitlab/ci/env_fedora37_makefiles.sh
deleted file mode 100644
index 217ff30..0000000
--- a/.gitlab/ci/env_fedora37_makefiles.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-if test "$CMAKE_CI_NIGHTLY" = "true"; then
-  source .gitlab/ci/ispc-env.sh
-fi
diff --git a/.gitlab/ci/env_fedora37_makefiles_clang.sh b/.gitlab/ci/env_fedora37_makefiles_clang.sh
deleted file mode 100644
index 9ff1d84..0000000
--- a/.gitlab/ci/env_fedora37_makefiles_clang.sh
+++ /dev/null
@@ -1 +0,0 @@
-. .gitlab/ci/env_fedora37_common_clang.sh
diff --git a/.gitlab/ci/env_fedora37_ninja_clang.sh b/.gitlab/ci/env_fedora37_ninja_clang.sh
deleted file mode 100644
index 9ff1d84..0000000
--- a/.gitlab/ci/env_fedora37_ninja_clang.sh
+++ /dev/null
@@ -1 +0,0 @@
-. .gitlab/ci/env_fedora37_common_clang.sh
diff --git a/.gitlab/ci/env_fedora37_asan.sh b/.gitlab/ci/env_fedora38_asan.sh
similarity index 100%
rename from .gitlab/ci/env_fedora37_asan.sh
rename to .gitlab/ci/env_fedora38_asan.sh
diff --git a/.gitlab/ci/env_fedora37_clang_analyzer.sh b/.gitlab/ci/env_fedora38_clang_analyzer.sh
similarity index 100%
rename from .gitlab/ci/env_fedora37_clang_analyzer.sh
rename to .gitlab/ci/env_fedora38_clang_analyzer.sh
diff --git a/.gitlab/ci/env_fedora38_common_clang.sh b/.gitlab/ci/env_fedora38_common_clang.sh
new file mode 100644
index 0000000..fc9c041
--- /dev/null
+++ b/.gitlab/ci/env_fedora38_common_clang.sh
@@ -0,0 +1,4 @@
+export CC=/usr/bin/clang-16
+export CXX=/usr/bin/clang++-16
+export FC=/usr/bin/flang-new
+export FFLAGS=-flang-experimental-exec
diff --git a/.gitlab/ci/env_fedora37_extdeps.sh b/.gitlab/ci/env_fedora38_extdeps.sh
similarity index 100%
rename from .gitlab/ci/env_fedora37_extdeps.sh
rename to .gitlab/ci/env_fedora38_extdeps.sh
diff --git a/.gitlab/ci/env_fedora38_makefiles.cmake b/.gitlab/ci/env_fedora38_makefiles.cmake
new file mode 100644
index 0000000..ef13cda
--- /dev/null
+++ b/.gitlab/ci/env_fedora38_makefiles.cmake
@@ -0,0 +1 @@
+set(ENV{MY_RUBY_HOME} "/usr/local/rvm/rubies/ruby-3.0.4")
diff --git a/.gitlab/ci/env_fedora38_makefiles.sh b/.gitlab/ci/env_fedora38_makefiles.sh
new file mode 100644
index 0000000..c482642
--- /dev/null
+++ b/.gitlab/ci/env_fedora38_makefiles.sh
@@ -0,0 +1,8 @@
+if test "$CMAKE_CI_NIGHTLY" = "true"; then
+  source .gitlab/ci/ispc-env.sh
+fi
+
+# Patch HDF5 Fortran compiler wrappers to work around Fedora bug.
+# https://bugzilla.redhat.com/show_bug.cgi?id=2183289
+sed -i '/^includedir=/ s|/mpich-x86_64||'   /usr/lib64/mpich/bin/h5pfc
+sed -i '/^includedir=/ s|/openmpi-x86_64||' /usr/lib64/openmpi/bin/h5pfc
diff --git a/.gitlab/ci/env_fedora38_makefiles_clang.sh b/.gitlab/ci/env_fedora38_makefiles_clang.sh
new file mode 100644
index 0000000..9f3edde
--- /dev/null
+++ b/.gitlab/ci/env_fedora38_makefiles_clang.sh
@@ -0,0 +1 @@
+. .gitlab/ci/env_fedora38_common_clang.sh
diff --git a/.gitlab/ci/env_fedora37_ninja.sh b/.gitlab/ci/env_fedora38_ninja.sh
similarity index 100%
rename from .gitlab/ci/env_fedora37_ninja.sh
rename to .gitlab/ci/env_fedora38_ninja.sh
diff --git a/.gitlab/ci/env_fedora38_ninja_clang.sh b/.gitlab/ci/env_fedora38_ninja_clang.sh
new file mode 100644
index 0000000..9f3edde
--- /dev/null
+++ b/.gitlab/ci/env_fedora38_ninja_clang.sh
@@ -0,0 +1 @@
+. .gitlab/ci/env_fedora38_common_clang.sh
diff --git a/.gitlab/ci/env_fedora37_ninja_multi.sh b/.gitlab/ci/env_fedora38_ninja_multi.sh
similarity index 100%
rename from .gitlab/ci/env_fedora37_ninja_multi.sh
rename to .gitlab/ci/env_fedora38_ninja_multi.sh
diff --git a/.gitlab/ci/env_fedora38_ninja_multi_clang.sh b/.gitlab/ci/env_fedora38_ninja_multi_clang.sh
new file mode 100644
index 0000000..9f3edde
--- /dev/null
+++ b/.gitlab/ci/env_fedora38_ninja_multi_clang.sh
@@ -0,0 +1 @@
+. .gitlab/ci/env_fedora38_common_clang.sh
diff --git a/.gitlab/ci/env_macos_x86_64_ninja_multi.sh b/.gitlab/ci/env_macos_arm64_ninja_multi.sh
similarity index 100%
rename from .gitlab/ci/env_macos_x86_64_ninja_multi.sh
rename to .gitlab/ci/env_macos_arm64_ninja_multi.sh
diff --git a/.gitlab/ci/env_macos_arm64_xcode_ub.cmake b/.gitlab/ci/env_macos_arm64_xcode_ub.cmake
new file mode 100644
index 0000000..244f088
--- /dev/null
+++ b/.gitlab/ci/env_macos_arm64_xcode_ub.cmake
@@ -0,0 +1 @@
+set(ENV{CMAKE_OSX_ARCHITECTURES} "arm64;x86_64")
diff --git a/.gitlab/ci/env_macos_x86_64_ninja_ub.cmake b/.gitlab/ci/env_macos_x86_64_ninja_ub.cmake
new file mode 100644
index 0000000..4b5c401
--- /dev/null
+++ b/.gitlab/ci/env_macos_x86_64_ninja_ub.cmake
@@ -0,0 +1 @@
+set(ENV{CMAKE_OSX_ARCHITECTURES} "x86_64;arm64")
diff --git a/.gitlab/ci/env_windows_borland5.5.ps1 b/.gitlab/ci/env_windows_borland5.5.ps1
new file mode 100755
index 0000000..0d2e46b
--- /dev/null
+++ b/.gitlab/ci/env_windows_borland5.5.ps1
@@ -0,0 +1 @@
+. .gitlab/ci/borland-env.ps1
diff --git a/.gitlab/ci/env_windows_borland5.8.ps1 b/.gitlab/ci/env_windows_borland5.8.ps1
new file mode 100755
index 0000000..0d2e46b
--- /dev/null
+++ b/.gitlab/ci/env_windows_borland5.8.ps1
@@ -0,0 +1 @@
+. .gitlab/ci/borland-env.ps1
diff --git a/.gitlab/ci/env_windows_intelclassic_ninja.ps1 b/.gitlab/ci/env_windows_intelclassic_ninja.ps1
new file mode 100755
index 0000000..99f83b9
--- /dev/null
+++ b/.gitlab/ci/env_windows_intelclassic_ninja.ps1
@@ -0,0 +1,9 @@
+. .gitlab/ci/ninja-env.ps1
+. .gitlab/ci/intel-env.ps1
+
+$env:CC  = "icl"
+$env:CXX = "icl"
+$env:FC  = "ifort"
+
+cmd /c "icl 2>&1" | Select -First 1
+cmd /c "ifort 2>&1" | Select -First 1
diff --git a/.gitlab/ci/env_windows_inteloneapi_ninja.ps1 b/.gitlab/ci/env_windows_inteloneapi_ninja.ps1
new file mode 100755
index 0000000..3bd1d46
--- /dev/null
+++ b/.gitlab/ci/env_windows_inteloneapi_ninja.ps1
@@ -0,0 +1,9 @@
+. .gitlab/ci/ninja-env.ps1
+. .gitlab/ci/intel-env.ps1
+
+$env:CC  = "icx"
+$env:CXX = "icx"
+$env:FC  = "ifx"
+
+cmd /c "icx 2>&1" | Select -First 1
+cmd /c "ifx 2>&1" | Select -First 1
diff --git a/.gitlab/ci/env_windows_msvc_v71_nmake.ps1 b/.gitlab/ci/env_windows_msvc_v71_nmake.ps1
new file mode 100755
index 0000000..cb3806d
--- /dev/null
+++ b/.gitlab/ci/env_windows_msvc_v71_nmake.ps1
@@ -0,0 +1,2 @@
+Invoke-Expression -Command .gitlab/ci/msvc.ps1
+Invoke-Expression -Command .gitlab/ci/vcvarsall.ps1
diff --git a/.gitlab/ci/env_windows_openwatcom1.9.ps1 b/.gitlab/ci/env_windows_openwatcom1.9.ps1
new file mode 100755
index 0000000..49c28f7
--- /dev/null
+++ b/.gitlab/ci/env_windows_openwatcom1.9.ps1
@@ -0,0 +1 @@
+. .gitlab/ci/openwatcom-env.ps1
diff --git a/.gitlab/ci/env_windows_vs2022_x64_jom.ps1 b/.gitlab/ci/env_windows_vs2022_x64_jom.ps1
new file mode 100755
index 0000000..c933421
--- /dev/null
+++ b/.gitlab/ci/env_windows_vs2022_x64_jom.ps1
@@ -0,0 +1,4 @@
+Invoke-Expression -Command .gitlab/ci/jom.ps1
+$pwdpath = $pwd.Path
+Set-Item -Force -Path "env:PATH" -Value "$pwdpath\.gitlab\jom;$env:PATH"
+Invoke-Expression -Command .gitlab/ci/vcvarsall.ps1
diff --git a/.gitlab/ci/env_windows_vs2022_x64_ninja.ps1 b/.gitlab/ci/env_windows_vs2022_x64_ninja.ps1
index a96658d..50a03ca 100755
--- a/.gitlab/ci/env_windows_vs2022_x64_ninja.ps1
+++ b/.gitlab/ci/env_windows_vs2022_x64_ninja.ps1
@@ -1,3 +1,4 @@
 if ("$env:CMAKE_CI_NIGHTLY" -eq "true") {
+  . ".gitlab/ci/innosetup-env.ps1"
   . ".gitlab/ci/ispc-env.ps1"
 }
diff --git a/.gitlab/ci/env_windows_vs2022_x64_nmake.ps1 b/.gitlab/ci/env_windows_vs2022_x64_nmake.ps1
new file mode 100755
index 0000000..62463cd
--- /dev/null
+++ b/.gitlab/ci/env_windows_vs2022_x64_nmake.ps1
@@ -0,0 +1 @@
+Invoke-Expression -Command .gitlab/ci/vcvarsall.ps1
diff --git a/.gitlab/ci/extdeps-linux.sh b/.gitlab/ci/extdeps-linux.sh
index f0d4c0d..f091525 100755
--- a/.gitlab/ci/extdeps-linux.sh
+++ b/.gitlab/ci/extdeps-linux.sh
@@ -57,6 +57,25 @@
   -DCMAKE_BUILD_TYPE=Release \
   -DJSONCPP_LIB_BUILD_STATIC=ON \
   -DJSONCPP_LIB_BUILD_SHARED=ON \
+  -DJSONCPP_WITH_CMAKE_PACKAGE=ON \
   -DCMAKE_INSTALL_PREFIX=/opt/extdeps
 cmake --build jsoncpp-1.6.0-build --target install
+echo >> /opt/extdeps/lib/cmake/jsoncpp/jsoncppConfig.cmake '
+# Backport imported target from jsoncpp 1.9.5.
+add_library(JsonCpp::JsonCpp INTERFACE IMPORTED)
+set_target_properties(JsonCpp::JsonCpp PROPERTIES INTERFACE_LINK_LIBRARIES "jsoncpp_lib")'
 rm -rf jsoncpp-1.6.0*
+
+#----------------------------------------------------------------------------
+# cppdap
+
+git clone https://github.com/google/cppdap.git
+cd cppdap
+git checkout 03cc18678ed2ed8b2424ec99dee7e4655d876db5 # 2023-05-25
+cd ..
+cmake -S cppdap -B cppdap-build \
+  -DCPPDAP_USE_EXTERNAL_JSONCPP_PACKAGE=ON \
+  -DCMAKE_INSTALL_PREFIX=/opt/extdeps \
+  -DCMAKE_PREFIX_PATH=/opt/extdeps
+cmake --build cppdap-build --target install
+rm -rf cppdap*
diff --git a/.gitlab/ci/innosetup-env.ps1 b/.gitlab/ci/innosetup-env.ps1
new file mode 100755
index 0000000..96e9d8c
--- /dev/null
+++ b/.gitlab/ci/innosetup-env.ps1
@@ -0,0 +1,4 @@
+$pwdpath = $pwd.Path
+& "$pwsh" -File ".gitlab/ci/innosetup.ps1"
+Set-Item -Force -Path "env:PATH" -Value "$pwdpath\.gitlab\innosetup;$env:PATH"
+ISCC 2>$null | Select -First 1
diff --git a/.gitlab/ci/innosetup.ps1 b/.gitlab/ci/innosetup.ps1
new file mode 100755
index 0000000..a7f4eb3
--- /dev/null
+++ b/.gitlab/ci/innosetup.ps1
@@ -0,0 +1,20 @@
+$erroractionpreference = "stop"
+
+$version = "6.2.2-1"
+$sha256sum = "34D5311070678617424628A88C8A7F7BE41157B1A59112F9DFDA1D7EFD4469CC"
+$filename = "innosetup-$version"
+$tarball = "$filename.zip"
+
+$outdir = $pwd.Path
+$outdir = "$outdir\.gitlab"
+$ProgressPreference = 'SilentlyContinue'
+Invoke-WebRequest -Uri "https://cmake.org/files/dependencies/internal/$tarball" -OutFile "$outdir\$tarball"
+$hash = Get-FileHash "$outdir\$tarball" -Algorithm SHA256
+if ($hash.Hash -ne $sha256sum) {
+    exit 1
+}
+
+Add-Type -AssemblyName System.IO.Compression.FileSystem
+[System.IO.Compression.ZipFile]::ExtractToDirectory("$outdir\$tarball", "$outdir")
+Move-Item -Path "$outdir\$filename" -Destination "$outdir\innosetup"
+Remove-Item "$outdir\$tarball"
diff --git a/.gitlab/ci/intel-env.ps1 b/.gitlab/ci/intel-env.ps1
new file mode 100755
index 0000000..75f7286
--- /dev/null
+++ b/.gitlab/ci/intel-env.ps1
@@ -0,0 +1,4 @@
+$pwdpath = $pwd.Path
+& "$pwsh" -File ".gitlab/ci/intel.ps1"
+Invoke-Expression -Command .gitlab/ci/vcvarsall.ps1
+Invoke-Expression -Command .gitlab/ci/intel-vars.ps1
diff --git a/.gitlab/ci/intel-vars.ps1 b/.gitlab/ci/intel-vars.ps1
new file mode 100755
index 0000000..dde0aa2
--- /dev/null
+++ b/.gitlab/ci/intel-vars.ps1
@@ -0,0 +1,9 @@
+$erroractionpreference = "stop"
+
+cmd /c "`".gitlab\intel\setvars.bat`" & set" |
+foreach {
+    if ($_ -match "=") {
+        $v = $_.split("=")
+        [Environment]::SetEnvironmentVariable($v[0], $v[1])
+    }
+}
diff --git a/.gitlab/ci/intel.ps1 b/.gitlab/ci/intel.ps1
new file mode 100755
index 0000000..2262669
--- /dev/null
+++ b/.gitlab/ci/intel.ps1
@@ -0,0 +1,42 @@
+$erroractionpreference = "stop"
+
+if ("$env:CMAKE_CI_BUILD_NAME" -match "(^|_)(oneapi2023\.1\.0|intel2021\.9\.0)(_|$)") {
+    # Intel oneAPI 2023.1.0
+    $version = "2023.1.0"
+    $filename = "intel-oneapi-$version-windows-1"
+    $sha256sum = "5AFCA9E0B03894565209B1295476163ABEBB1F1388E0F3EF5B4D0F9189E65BDC"
+} else {
+    throw ('unknown CMAKE_CI_BUILD_NAME: ' + "$env:CMAKE_CI_BUILD_NAME")
+}
+$tarball = "$filename.zip"
+
+$outdir = $pwd.Path
+$outdir = "$outdir\.gitlab"
+$ProgressPreference = 'SilentlyContinue'
+# This URL is only visible inside of Kitware's network.  See above filename table.
+Invoke-WebRequest -Uri "https://cmake.org/files/dependencies/internal/$tarball" -OutFile "$outdir\$tarball"
+$hash = Get-FileHash "$outdir\$tarball" -Algorithm SHA256
+if ($hash.Hash -ne $sha256sum) {
+    exit 1
+}
+
+Add-Type -AssemblyName System.IO.Compression.FileSystem
+[System.IO.Compression.ZipFile]::ExtractToDirectory("$outdir\$tarball", "$outdir")
+Move-Item -Path "$outdir\$filename" -Destination "$outdir\intel"
+Remove-Item "$outdir\$tarball"
+
+$compiler = "$outdir\intel\compiler"
+$bin = "$compiler\$version\windows\bin"
+$null = New-Item -ItemType Junction -Path "$compiler\latest"   -Target "$compiler\$version"
+$null = New-Item -ItemType HardLink -Path "$bin\icx-cl.exe"    -Target "$bin\icx.exe"
+$null = New-Item -ItemType HardLink -Path "$bin\icx-cc.exe"    -Target "$bin\icx.exe"
+$null = New-Item -ItemType HardLink -Path "$bin\icpx.exe"      -Target "$bin\icx.exe"
+$bin = "$compiler\$version\windows\bin-llvm"
+$null = New-Item -ItemType HardLink -Path "$bin\clang-cl.exe"  -Target "$bin\clang.exe"
+$null = New-Item -ItemType HardLink -Path "$bin\clang-cpp.exe" -Target "$bin\clang.exe"
+$null = New-Item -ItemType HardLink -Path "$bin\clang++.exe"   -Target "$bin\clang.exe"
+$null = New-Item -ItemType HardLink -Path "$bin\lld-link.exe"  -Target "$bin\lld.exe"
+$null = New-Item -ItemType HardLink -Path "$bin\ld.lld.exe"    -Target "$bin\lld.exe"
+$null = New-Item -ItemType HardLink -Path "$bin\llvm-lib.exe"  -Target "$bin\llvm-ar.exe"
+Clear-Variable -Name bin
+Clear-Variable -Name compiler
diff --git a/.gitlab/ci/ispc.ps1 b/.gitlab/ci/ispc.ps1
index 13267c3..524896f 100755
--- a/.gitlab/ci/ispc.ps1
+++ b/.gitlab/ci/ispc.ps1
@@ -1,7 +1,7 @@
 $erroractionpreference = "stop"
 
-$version = "1.18.0"
-$sha256sum = "9210BB2D9D3711367FACCB37ACF49966696132560B565471C1C6121F4924A17E"
+$version = "1.20.0"
+$sha256sum = "E212EBFB4E8AFB57ADC103A2579C52673A3CA49610FBC2A5EAE643D3D378548D"
 $filename = "ispc-v$version-windows"
 $tarball = "$filename.zip"
 
diff --git a/.gitlab/ci/ispc.sh b/.gitlab/ci/ispc.sh
index 2804277..59ee200 100755
--- a/.gitlab/ci/ispc.sh
+++ b/.gitlab/ci/ispc.sh
@@ -2,18 +2,23 @@
 
 set -e
 
-readonly version="1.18.0"
+readonly version="1.20.0"
 
 case "$(uname -s)-$(uname -m)" in
     Linux-x86_64)
         shatool="sha256sum"
-        sha256sum="6c379bb97962e9de7d24fd48b3f7e647dc42be898e9d187948220268c646b692"
+        sha256sum="e6412b88aa312fcd10c46f92df0149ccc4d99e53552c4ce127aa6c634fe9b308"
         platform="linux"
         ;;
+    Darwin-arm64)
+        shatool="shasum -a 256"
+        sha256sum="62cee043a3a4dbff8c2f6d3885a7e573901bbc1325dd93d50f92904b7ea67fec"
+        platform="macOS.arm64"
+        ;;
     Darwin-x86_64)
         shatool="shasum -a 256"
-        sha256sum="d1435b541182406ff6b18446d31ecceef0eae3aed7654391ae676d3142e0000d"
-        platform="macOS"
+        sha256sum="da0f11a048a316081a8ad8170d48b170b2ed7efc3b140fc88b8611238809c8e4"
+        platform="macOS.x86_64"
         ;;
     *)
         echo "Unrecognized platform $(uname -s)-$(uname -m)"
diff --git a/.gitlab/ci/openwatcom-env.ps1 b/.gitlab/ci/openwatcom-env.ps1
new file mode 100755
index 0000000..14ea523
--- /dev/null
+++ b/.gitlab/ci/openwatcom-env.ps1
@@ -0,0 +1,7 @@
+Invoke-Expression -Command .gitlab/ci/openwatcom.ps1
+$pwdpath = $pwd.Path
+Set-Item -Force -Path "env:PATH" -Value "$pwdpath\.gitlab\watcom\binnt;$pwdpath\.gitlab\watcom\binw;$env:PATH"
+Set-Item -Force -Path "env:INCLUDE" -Value "$pwdpath\.gitlab\watcom\h;$pwdpath\.gitlab\watcom\h\nt"
+Set-Item -Force -Path "env:EDPATH" -Value "$pwdpath\.gitlab\watcom\eddat"
+Set-Item -Force -Path "env:WATCOM" -Value "$pwdpath\.gitlab\watcom"
+Set-Item -Force -Path "env:WLINKTMP" -Value "."
diff --git a/.gitlab/ci/pre_build_fedora37_tidy.sh b/.gitlab/ci/pre_build_fedora38_tidy.sh
similarity index 100%
rename from .gitlab/ci/pre_build_fedora37_tidy.sh
rename to .gitlab/ci/pre_build_fedora38_tidy.sh
diff --git a/.gitlab/ci/python-env.ps1 b/.gitlab/ci/python-env.ps1
index 4e897d8..ce16493 100755
--- a/.gitlab/ci/python-env.ps1
+++ b/.gitlab/ci/python-env.ps1
@@ -1,4 +1,4 @@
 $pwdpath = $pwd.Path
-cmake -P .gitlab/ci/download_python3.cmake
+& "$pwsh" -File ".gitlab/ci/python.ps1"
 Set-Item -Force -Path "env:PATH" -Value "$pwdpath\.gitlab\python3;$env:PATH"
 python --version
diff --git a/.gitlab/ci/python.ps1 b/.gitlab/ci/python.ps1
new file mode 100755
index 0000000..27f1807
--- /dev/null
+++ b/.gitlab/ci/python.ps1
@@ -0,0 +1,30 @@
+$erroractionpreference = "stop"
+
+$version = "3.11.3"
+
+if ("$env:PROCESSOR_ARCHITECTURE" -eq "AMD64") {
+    $sha256sum = "7419B2E98516FBD0B66A1237B80187FFB21D32E47B4A4235C2D9D6379597070F"
+    $arch = "amd64"
+} elseif ("$env:PROCESSOR_ARCHITECTURE" -eq "ARM64") {
+    $sha256sum = "03BAD6A7C898FC8F693982437AAB6DB698107B82EA93F76424195AE2C161246C"
+    $arch = "arm64"
+} else {
+    throw ('unknown PROCESSOR_ARCHITECTURE: ' + "$env:PROCESSOR_ARCHITECTURE")
+}
+
+$filename = "python-$version-embed-$arch"
+$tarball = "$filename.zip"
+
+$outdir = $pwd.Path
+$outdir = "$outdir\.gitlab"
+$ProgressPreference = 'SilentlyContinue'
+Invoke-WebRequest -Uri "https://cmake.org/files/dependencies/internal/$tarball" -OutFile "$outdir\$tarball"
+$hash = Get-FileHash "$outdir\$tarball" -Algorithm SHA256
+if ($hash.Hash -ne $sha256sum) {
+    exit 1
+}
+
+Add-Type -AssemblyName System.IO.Compression.FileSystem
+[System.IO.Compression.ZipFile]::ExtractToDirectory("$outdir\$tarball", "$outdir\python3")
+Remove-Item "$outdir\python3\*._pth" # Avoid sys.path specific to embedded python.
+Remove-Item "$outdir\$tarball"
diff --git a/.gitlab/issue_templates/Default.md b/.gitlab/issue_templates/Default.md
new file mode 100644
index 0000000..3b083d2
--- /dev/null
+++ b/.gitlab/issue_templates/Default.md
@@ -0,0 +1,9 @@
+<!--
+This issue tracker is for CMake upstream development:
+
+* If you are having trouble building a specific third-party project
+  that uses CMake, ask for help in that project's forums first.
+
+* If you have a coding or usage question, please ask for help
+  on the CMake discourse forums: https://discourse.cmake.org/
+-->
diff --git a/.gitlab/os-linux.yml b/.gitlab/os-linux.yml
index 9a53401..f4cc401 100644
--- a/.gitlab/os-linux.yml
+++ b/.gitlab/os-linux.yml
@@ -5,7 +5,7 @@
 ### Release
 
 .linux_prep_source:
-    image: "fedora:37"
+    image: "fedora:38"
 
     variables:
         GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake ci"
@@ -45,7 +45,7 @@
 ### Debian
 
 .debian10:
-    image: "kitware/cmake:ci-debian10-x86_64-2023-02-07"
+    image: "kitware/cmake:ci-debian10-x86_64-2023-03-29"
 
     variables:
         GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake ci"
@@ -60,7 +60,7 @@
         CMAKE_CI_NO_INSTALL: 1
 
 .debian10_aarch64:
-    image: "kitware/cmake:ci-debian10-aarch64-2023-02-07"
+    image: "kitware/cmake:ci-debian10-aarch64-2023-03-29"
 
     variables:
         GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake ci"
@@ -68,8 +68,8 @@
 
 ### Fedora
 
-.fedora37:
-    image: "kitware/cmake:ci-fedora37-x86_64-2023-02-07"
+.fedora38:
+    image: "kitware/cmake:ci-fedora38-x86_64-2023-05-22"
 
     variables:
         GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake ci/long file name for testing purposes"
@@ -77,37 +77,37 @@
 
 #### Lint builds
 
-.fedora37_tidy:
-    extends: .fedora37
+.fedora38_tidy:
+    extends: .fedora38
 
     variables:
-        CMAKE_CONFIGURATION: fedora37_tidy
+        CMAKE_CONFIGURATION: fedora38_tidy
         CTEST_NO_WARNINGS_ALLOWED: 1
         CMAKE_CI_NO_INSTALL: 1
 
-.fedora37_clang_analyzer:
-    extends: .fedora37
+.fedora38_clang_analyzer:
+    extends: .fedora38
 
     variables:
-        CMAKE_CONFIGURATION: fedora37_clang_analyzer
+        CMAKE_CONFIGURATION: fedora38_clang_analyzer
         CMAKE_CI_BUILD_TYPE: Debug
         CTEST_NO_WARNINGS_ALLOWED: 1
         CMAKE_CI_NO_INSTALL: 1
 
-.fedora37_sphinx:
-    extends: .fedora37
+.fedora38_sphinx:
+    extends: .fedora38
 
     variables:
-        CMAKE_CONFIGURATION: fedora37_sphinx
+        CMAKE_CONFIGURATION: fedora38_sphinx
         CTEST_NO_WARNINGS_ALLOWED: 1
         CTEST_SOURCE_SUBDIRECTORY: "Utilities/Sphinx"
         CMAKE_CI_NO_INSTALL: 1
 
-.fedora37_sphinx_package:
-    extends: .fedora37
+.fedora38_sphinx_package:
+    extends: .fedora38
 
     variables:
-        CMAKE_CONFIGURATION: fedora37_sphinx_package
+        CMAKE_CONFIGURATION: fedora38_sphinx_package
         CTEST_SOURCE_SUBDIRECTORY: "Utilities/Sphinx"
 
 #### Build and test
@@ -153,35 +153,35 @@
         CMAKE_CI_BUILD_TYPE: Release
         CTEST_NO_WARNINGS_ALLOWED: 1
 
-.fedora37_extdeps:
-    extends: .fedora37
+.fedora38_extdeps:
+    extends: .fedora38
 
     variables:
-        CMAKE_CONFIGURATION: fedora37_extdeps
+        CMAKE_CONFIGURATION: fedora38_extdeps
         CMAKE_CI_BUILD_TYPE: Release
         CTEST_NO_WARNINGS_ALLOWED: 1
 
-.fedora37_ninja:
-    extends: .fedora37
+.fedora38_ninja:
+    extends: .fedora38
 
     variables:
-        CMAKE_CONFIGURATION: fedora37_ninja
+        CMAKE_CONFIGURATION: fedora38_ninja
         CMAKE_CI_BUILD_TYPE: Release
         CTEST_NO_WARNINGS_ALLOWED: 1
 
-.fedora37_ninja_multi:
-    extends: .fedora37
+.fedora38_ninja_multi:
+    extends: .fedora38
 
     variables:
-        CMAKE_CONFIGURATION: fedora37_ninja_multi
+        CMAKE_CONFIGURATION: fedora38_ninja_multi
         CTEST_NO_WARNINGS_ALLOWED: 1
         CMAKE_GENERATOR: "Ninja Multi-Config"
 
-.fedora37_makefiles:
-    extends: .fedora37
+.fedora38_makefiles:
+    extends: .fedora38
 
     variables:
-        CMAKE_CONFIGURATION: fedora37_makefiles
+        CMAKE_CONFIGURATION: fedora38_makefiles
         CTEST_NO_WARNINGS_ALLOWED: 1
         CMAKE_GENERATOR: "Unix Makefiles"
 
@@ -200,18 +200,25 @@
     variables:
         CMAKE_CONFIGURATION: debian10_ninja_clang
 
-.fedora37_makefiles_clang:
-    extends: .fedora37
+.fedora38_makefiles_clang:
+    extends: .fedora38
 
     variables:
-        CMAKE_CONFIGURATION: fedora37_makefiles_clang
+        CMAKE_CONFIGURATION: fedora38_makefiles_clang
         CMAKE_GENERATOR: "Unix Makefiles"
 
-.fedora37_ninja_clang:
-    extends: .fedora37
+.fedora38_ninja_clang:
+    extends: .fedora38
 
     variables:
-        CMAKE_CONFIGURATION: fedora37_ninja_clang
+        CMAKE_CONFIGURATION: fedora38_ninja_clang
+
+.fedora38_ninja_multi_clang:
+    extends: .fedora38
+
+    variables:
+        CMAKE_CONFIGURATION: fedora38_ninja_multi_clang
+        CMAKE_GENERATOR: "Ninja Multi-Config"
 
 ### Sanitizers
 
@@ -226,13 +233,13 @@
         CTEST_MEMORYCHECK_TYPE: AddressSanitizer
         CTEST_MEMORYCHECK_SANITIZER_OPTIONS: ""
 
-.fedora37_asan:
+.fedora38_asan:
     extends:
-        - .fedora37
+        - .fedora38
         - .fedora_asan_addon
 
     variables:
-        CMAKE_CONFIGURATION: fedora37_asan
+        CMAKE_CONFIGURATION: fedora38_asan
 
 ### Intel Compiler
 
@@ -376,28 +383,6 @@
         CMAKE_CONFIGURATION: linux_gcc_cxx_modules_ninja_multi
         CMAKE_GENERATOR: "Ninja Multi-Config"
 
-.clang_cxx_modules_x86_64:
-    image: "kitware/cmake:ci-clang_cxx_modules-x86_64-2023-02-15"
-
-    variables:
-        GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake ci"
-        CMAKE_ARCH: x86_64
-        CC: "/opt/llvm-p1689/bin/clang"
-        CXX: "/opt/llvm-p1689/bin/clang++"
-
-.clang_cxx_modules_ninja:
-    extends: .clang_cxx_modules_x86_64
-
-    variables:
-        CMAKE_CONFIGURATION: linux_clang_cxx_modules_ninja
-
-.clang_cxx_modules_ninja_multi:
-    extends: .clang_cxx_modules_x86_64
-
-    variables:
-        CMAKE_CONFIGURATION: linux_clang_cxx_modules_ninja_multi
-        CMAKE_GENERATOR: "Ninja Multi-Config"
-
 ## Tags
 
 .linux_x86_64_tags:
@@ -478,7 +463,7 @@
 
 .cmake_codespell_linux:
     stage: build
-    extends: .fedora37
+    extends: .fedora38
     script:
         - .gitlab/ci/codespell.sh
     interruptible: true
@@ -623,7 +608,7 @@
 .cmake_org_help:
     stage: build
     extends:
-        - .fedora37
+        - .fedora38
         - .linux_x86_64_tags
         - .cmake_org_help_artifacts
     script:
diff --git a/.gitlab/os-macos.yml b/.gitlab/os-macos.yml
index 652a67a..09d7598 100644
--- a/.gitlab/os-macos.yml
+++ b/.gitlab/os-macos.yml
@@ -7,7 +7,7 @@
         GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake ci ext/$CI_CONCURRENT_ID"
         # TODO: Factor this out so that each job selects the Xcode version to
         # use so that different versions can be tested in a single pipeline.
-        DEVELOPER_DIR: "/Applications/Xcode-14.2.app/Contents/Developer"
+        DEVELOPER_DIR: "/Applications/Xcode-14.3.app/Contents/Developer"
         # Avoid conflicting with other projects running on the same machine.
         SCCACHE_SERVER_PORT: 4227
 
@@ -80,14 +80,29 @@
         CMAKE_GENERATOR: Xcode
         CMAKE_CI_NIGHTLY_IGNORE_DEPS: "true"
 
-.macos_x86_64_ninja_multi:
+.macos_arm64_xcode_ub:
     extends: .macos
 
     variables:
-        CMAKE_CONFIGURATION: macos_x86_64_ninja_multi
+        CMAKE_CONFIGURATION: macos_arm64_xcode_ub
+        CMAKE_GENERATOR: Xcode
+        CMAKE_CI_NIGHTLY_IGNORE_DEPS: "true"
+
+.macos_arm64_ninja_multi:
+    extends: .macos
+
+    variables:
+        CMAKE_CONFIGURATION: macos_arm64_ninja_multi
         CMAKE_GENERATOR: "Ninja Multi-Config"
         CMAKE_CI_NIGHTLY_IGNORE_DEPS: "true"
 
+.macos_x86_64_ninja_ub:
+    extends: .macos
+
+    variables:
+        CMAKE_CONFIGURATION: macos_x86_64_ninja_ub
+        CMAKE_CI_NIGHTLY_IGNORE_DEPS: "true"
+
 ## Tags
 
 .macos_x86_64_tags:
@@ -95,24 +110,15 @@
         - cmake # Since this is a bare runner, pin to a project.
         - macos-x86_64
         - shell
-        - xcode-14.2
+        - xcode-14.3
         - nonconcurrent
 
-.macos_x86_64_tags_package:
-    tags:
-        - cmake # Since this is a bare runner, pin to a project.
-        - macos-x86_64
-        - shell
-        - xcode-14.2
-        - nonconcurrent
-        - finder
-
 .macos_x86_64_tags_ext:
     tags:
         - cmake # Since this is a bare runner, pin to a project.
         - macos-x86_64
         - shell
-        - xcode-14.2
+        - xcode-14.3
         - concurrent
 
 .macos_arm64_tags:
@@ -120,7 +126,7 @@
         - cmake # Since this is a bare runner, pin to a project.
         - macos-arm64
         - shell
-        - xcode-14.2
+        - xcode-14.3
         - nonconcurrent
 
 .macos_arm64_tags_ext:
@@ -128,9 +134,18 @@
         - cmake # Since this is a bare runner, pin to a project.
         - macos-arm64
         - shell
-        - xcode-14.2
+        - xcode-14.3
         - concurrent
 
+.macos_arm64_tags_package:
+    tags:
+        - cmake # Since this is a bare runner, pin to a project.
+        - macos-arm64
+        - shell
+        - xcode-14.3
+        - nonconcurrent
+        - finder
+
 ## macOS-specific scripts
 
 .before_script_macos: &before_script_macos
diff --git a/.gitlab/os-windows.yml b/.gitlab/os-windows.yml
index ded3e65..026f2f4 100644
--- a/.gitlab/os-windows.yml
+++ b/.gitlab/os-windows.yml
@@ -35,25 +35,25 @@
     variables:
         VCVARSALL: "${VS170COMNTOOLS}\\..\\..\\VC\\Auxiliary\\Build\\vcvarsall.bat"
         VCVARSPLATFORM: "x64"
-        VCVARSVERSION: "14.35.32215"
+        VCVARSVERSION: "14.36.32532"
 
 .windows_vcvarsall_vs2022_x86:
     variables:
         VCVARSALL: "${VS170COMNTOOLS}\\..\\..\\VC\\Auxiliary\\Build\\vcvarsall.bat"
         VCVARSPLATFORM: "x86"
-        VCVARSVERSION: "14.35.32215"
+        VCVARSVERSION: "14.36.32532"
 
 .windows_vcvarsall_vs2022_x64_arm64:
     variables:
         VCVARSALL: "${VS170COMNTOOLS}\\..\\..\\VC\\Auxiliary\\Build\\vcvarsall.bat"
         VCVARSPLATFORM: "x64_arm64"
-        VCVARSVERSION: "14.35.32215"
+        VCVARSVERSION: "14.36.32532"
 
 .windows_arm64_vcvarsall_vs2022:
     variables:
         VCVARSALL: "${VS170COMNTOOLS}\\..\\..\\VC\\Auxiliary\\Build\\vcvarsall.bat"
         VCVARSPLATFORM: "arm64"
-        VCVARSVERSION: "14.35.32215"
+        VCVARSVERSION: "14.36.32532"
 
 .windows_vs2022_x64_ninja:
     extends:
@@ -112,7 +112,7 @@
         CMAKE_CONFIGURATION: windows_vs2022_x64
         CMAKE_GENERATOR: "Visual Studio 17 2022"
         CMAKE_GENERATOR_PLATFORM: "x64"
-        CMAKE_GENERATOR_TOOLSET: "v143,version=14.35.32215"
+        CMAKE_GENERATOR_TOOLSET: "v143,version=14.36.32532"
         CMAKE_CI_NIGHTLY_IGNORE_DEPS: "true"
 
 .windows_vs2019_x64:
@@ -222,6 +222,22 @@
     variables:
         CMAKE_CONFIGURATION: windows_msvc_v71_nmake
 
+.windows_intelclassic_ninja:
+    extends:
+        - .windows_ninja
+        - .windows_vcvarsall_vs2022_x64
+
+    variables:
+        CMAKE_CONFIGURATION: windows_intelclassic_ninja
+
+.windows_inteloneapi_ninja:
+    extends:
+        - .windows_ninja
+        - .windows_vcvarsall_vs2022_x64
+
+    variables:
+        CMAKE_CONFIGURATION: windows_inteloneapi_ninja
+
 .windows_openwatcom:
     extends: .windows
 
@@ -245,7 +261,7 @@
         CMAKE_CONFIGURATION: windows_arm64_vs2022
         CMAKE_GENERATOR: "Visual Studio 17 2022"
         CMAKE_GENERATOR_PLATFORM: "ARM64"
-        CMAKE_GENERATOR_TOOLSET: "v143,version=14.35.32215"
+        CMAKE_GENERATOR_TOOLSET: "v143,version=14.36.32532"
         CMAKE_CI_NIGHTLY_IGNORE_DEPS: "true"
 
 .mingw_osdn_io:
@@ -279,7 +295,7 @@
         - windows-x86_64
         - shell
         - vs2022
-        - msvc-19.35
+        - msvc-19.36
         - nonconcurrent
 
 .windows_x86_64_tags_nonconcurrent_vs2022_arm64:
@@ -288,7 +304,7 @@
         - windows-x86_64
         - shell
         - vs2022
-        - msvc-19.35-arm64
+        - msvc-19.36-arm64
         - nonconcurrent
 
 .windows_x86_64_tags_concurrent_vs2022:
@@ -297,7 +313,7 @@
         - windows-x86_64
         - shell
         - vs2022
-        - msvc-19.35
+        - msvc-19.36
         - concurrent
 
 .windows_x86_64_tags_concurrent_vs2019:
@@ -322,7 +338,7 @@
         - windows-arm64
         - shell
         - vs2022
-        - msvc-19.35
+        - msvc-19.36
         - nonconcurrent
 
 .windows_arm64_tags_concurrent_vs2022:
@@ -331,7 +347,7 @@
         - windows-arm64
         - shell
         - vs2022
-        - msvc-19.35
+        - msvc-19.36
         - concurrent
 
 ## Windows-specific scripts
@@ -349,6 +365,10 @@
     - . .gitlab/ci/qt-env.ps1
     - . .gitlab/ci/python-env.ps1
 
+.before_script_windows_external: &before_script_windows_external
+    - . .gitlab/ci/env.ps1
+    - . .gitlab/ci/python-env.ps1
+
 .cmake_build_windows:
     stage: build
 
@@ -381,69 +401,7 @@
     stage: test-ext
 
     script:
-        - . .gitlab/ci/env.ps1
-        - build/install/bin/ctest --output-on-failure -V -S .gitlab/ci/ctest_standalone.cmake
-
-    interruptible: true
-
-.cmake_test_windows_nmake:
-    stage: test-ext
-
-    script:
-        - . .gitlab/ci/env.ps1
-        - Invoke-Expression -Command .gitlab/ci/vcvarsall.ps1
-        - build/install/bin/ctest --output-on-failure -V -S .gitlab/ci/ctest_standalone.cmake
-
-    interruptible: true
-
-.cmake_test_windows_jom:
-    stage: test-ext
-
-    script:
-        - . .gitlab/ci/env.ps1
-        - Invoke-Expression -Command .gitlab/ci/jom.ps1
-        - $pwdpath = $pwd.Path
-        - Set-Item -Force -Path "env:PATH" -Value "$pwdpath\.gitlab\jom;$env:PATH"
-        - Invoke-Expression -Command .gitlab/ci/vcvarsall.ps1
-        - build/install/bin/ctest --output-on-failure -V -S .gitlab/ci/ctest_standalone.cmake
-
-    interruptible: true
-
-.cmake_test_windows_borland:
-    stage: test-ext
-
-    script:
-        - . .gitlab/ci/env.ps1
-        - Invoke-Expression -Command .gitlab/ci/borland.ps1
-        - $pwdpath = $pwd.Path
-        - Set-Item -Force -Path "env:PATH" -Value "$pwdpath\.gitlab\bcc\bin;$env:PATH"
-        - build/install/bin/ctest --output-on-failure -V -S .gitlab/ci/ctest_standalone.cmake
-
-    interruptible: true
-
-.cmake_test_windows_msvc:
-    stage: test-ext
-
-    script:
-        - . .gitlab/ci/env.ps1
-        - Invoke-Expression -Command .gitlab/ci/msvc.ps1
-        - Invoke-Expression -Command .gitlab/ci/vcvarsall.ps1
-        - build/install/bin/ctest --output-on-failure -V -S .gitlab/ci/ctest_standalone.cmake
-
-    interruptible: true
-
-.cmake_test_windows_openwatcom:
-    stage: test-ext
-
-    script:
-        - . .gitlab/ci/env.ps1
-        - Invoke-Expression -Command .gitlab/ci/openwatcom.ps1
-        - $pwdpath = $pwd.Path
-        - Set-Item -Force -Path "env:PATH" -Value "$pwdpath\.gitlab\watcom\binnt;$pwdpath\.gitlab\watcom\binw;$env:PATH"
-        - Set-Item -Force -Path "env:INCLUDE" -Value "$pwdpath\.gitlab\watcom\h;$pwdpath\.gitlab\watcom\h\nt"
-        - Set-Item -Force -Path "env:EDPATH" -Value "$pwdpath\.gitlab\watcom\eddat"
-        - Set-Item -Force -Path "env:WATCOM" -Value "$pwdpath\.gitlab\watcom"
-        - Set-Item -Force -Path "env:WLINKTMP" -Value "."
+        - *before_script_windows_external
         - build/install/bin/ctest --output-on-failure -V -S .gitlab/ci/ctest_standalone.cmake
 
     interruptible: true
diff --git a/.gitlab/upload.yml b/.gitlab/upload.yml
index 114808f..caa2119 100644
--- a/.gitlab/upload.yml
+++ b/.gitlab/upload.yml
@@ -1,7 +1,7 @@
 # Steps for uploading artifacts
 
 .rsync_upload_package:
-    image: "fedora:37"
+    image: "fedora:38"
     stage: upload
     tags:
         - cmake
@@ -21,7 +21,7 @@
 
 .rsync_upload_help:
     stage: upload
-    image: "fedora:37"
+    image: "fedora:38"
     tags:
         - cmake
         - docker
diff --git a/Auxiliary/cmake-mode.el b/Auxiliary/cmake-mode.el
index a11becb..6bd23bf 100644
--- a/Auxiliary/cmake-mode.el
+++ b/Auxiliary/cmake-mode.el
@@ -372,7 +372,7 @@
   (interactive "s")
   (let* ((bufname (if buffer buffer (concat "*CMake" type (if topic "-") topic "*")))
          (buffer  (if (get-buffer bufname) (get-buffer bufname) (generate-new-buffer bufname)))
-         (command (concat cmake-mode-cmake-executable " " type " " topic))
+         (command (concat cmake-mode-cmake-executable " " type " " (if topic (shell-quote-argument topic) topic)))
          ;; Turn of resizing of mini-windows for shell-command.
          (resize-mini-windows nil)
          )
@@ -391,7 +391,7 @@
   (interactive "s")
   (let* ((bufname (if buffer buffer (concat "*CMake" type (if topic "-") topic "*")))
          (buffer  (if (get-buffer bufname) (get-buffer bufname) (generate-new-buffer bufname)))
-         (command (concat cmake-mode-cmake-executable " " type " " topic))
+         (command (concat cmake-mode-cmake-executable " " type " " (if topic (shell-quote-argument topic) topic)))
          ;; Turn of resizing of mini-windows for shell-command.
          (resize-mini-windows nil)
          )
diff --git a/Auxiliary/vim/syntax/cmake.vim b/Auxiliary/vim/syntax/cmake.vim
index 5e936c2..bc8b06a 100644
--- a/Auxiliary/vim/syntax/cmake.vim
+++ b/Auxiliary/vim/syntax/cmake.vim
@@ -73,6 +73,7 @@
             \ AUTOGEN_ORIGIN_DEPENDS
             \ AUTOGEN_PARALLEL
             \ AUTOGEN_SOURCE_GROUP
+            \ AUTOGEN_USE_SYSTEM_INCLUDE
             \ AUTOGEN_TARGETS_FOLDER
             \ AUTOGEN_TARGET_DEPENDS
             \ AUTOMOC
@@ -128,7 +129,10 @@
             \ CPACK_WIX_ACL
             \ CROSSCOMPILING_EMULATOR
             \ CUDA_ARCHITECTURES
+            \ CUDA_CUBIN_COMPILATION
             \ CUDA_EXTENSIONS
+            \ CUDA_FATBIN_COMPILATION
+            \ CUDA_OPTIX_COMPILATION
             \ CUDA_PTX_COMPILATION
             \ CUDA_RESOLVE_DEVICE_SYMBOLS
             \ CUDA_RUNTIME_LIBRARY
@@ -217,6 +221,7 @@
             \ INSTALL_RPATH
             \ INSTALL_RPATH_USE_LINK_PATH
             \ INTERFACE_AUTOUIC_OPTIONS
+            \ INTERFACE_AUTOMOC_MACRO_NAMES
             \ INTERFACE_COMPILE_DEFINITIONS
             \ INTERFACE_COMPILE_FEATURES
             \ INTERFACE_COMPILE_OPTIONS
@@ -320,6 +325,7 @@
             \ SKIP_AUTORCC
             \ SKIP_AUTOUIC
             \ SKIP_BUILD_RPATH
+            \ SKIP_LINTING
             \ SKIP_PRECOMPILE_HEADERS
             \ SKIP_REGULAR_EXPRESSION
             \ SKIP_RETURN_CODE
@@ -459,6 +465,7 @@
             \ BUILD_SHARED_LIBS
             \ CACHE
             \ CMAKE_ABSOLUTE_DESTINATION_FILES
+            \ CMAKE_ADD_CUSTOM_COMMAND_DEPENDS_EXPLICIT_ONLY
             \ CMAKE_AIX_EXPORT_ALL_SYMBOLS
             \ CMAKE_ANDROID_ANT_ADDITIONAL_OPTIONS
             \ CMAKE_ANDROID_API
@@ -678,6 +685,7 @@
             \ CMAKE_ASM_VISIBILITY_PRESET
             \ CMAKE_AUTOGEN_ORIGIN_DEPENDS
             \ CMAKE_AUTOGEN_PARALLEL
+            \ CMAKE_AUTOGEN_USE_SYSTEM_INCLUDE
             \ CMAKE_AUTOGEN_VERBOSE
             \ CMAKE_AUTOMOC
             \ CMAKE_AUTOMOC_COMPILER_PREDEFINES
@@ -686,11 +694,14 @@
             \ CMAKE_AUTOMOC_MOC_OPTIONS
             \ CMAKE_AUTOMOC_PATH_PREFIX
             \ CMAKE_AUTOMOC_RELAXED_MODE
+            \ CMAKE_AUTOMOC_EXECUTABLE
             \ CMAKE_AUTORCC
             \ CMAKE_AUTORCC_OPTIONS
+            \ CMAKE_AUTORCC_EXECUTABLE
             \ CMAKE_AUTOUIC
             \ CMAKE_AUTOUIC_OPTIONS
             \ CMAKE_AUTOUIC_SEARCH_PATHS
+            \ CMAKE_AUTOUIC_EXECUTABLE
             \ CMAKE_BACKWARDS_COMPATIBILITY
             \ CMAKE_BINARY_DIR
             \ CMAKE_BUILD_RPATH
@@ -2095,6 +2106,7 @@
             \ COMMENT
             \ CROSSCOMPILING_EMULATOR
             \ DEPENDS
+            \ DEPENDS_EXPLICIT_ONLY
             \ DEPFILE
             \ GENERATED
             \ IMPLICIT_DEPENDS
@@ -2735,6 +2747,7 @@
             \ READ_SYMLINK
             \ REAL_PATH
             \ REGEX
+            \ RELATIVE
             \ RELATIVE_PATH
             \ RELEASE
             \ REMOVE
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0ce42fb..d559c08 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,7 +1,7 @@
 # Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
 # file Copyright.txt or https://cmake.org/licensing for details.
 
-cmake_minimum_required(VERSION 3.13...3.24 FATAL_ERROR)
+cmake_minimum_required(VERSION 3.13...3.25 FATAL_ERROR)
 set(CMAKE_USER_MAKE_RULES_OVERRIDE_C ${CMAKE_CURRENT_SOURCE_DIR}/Source/Modules/OverrideC.cmake)
 set(CMAKE_USER_MAKE_RULES_OVERRIDE_CXX ${CMAKE_CURRENT_SOURCE_DIR}/Source/Modules/OverrideCXX.cmake)
 
@@ -131,6 +131,23 @@
   endif()
 endif()
 
+# Check whether to build support for the debugger mode.
+if(NOT CMake_TEST_EXTERNAL_CMAKE)
+  if(NOT DEFINED CMake_ENABLE_DEBUGGER)
+    # The debugger uses cppdap, which does not compile everywhere.
+    if(CMAKE_SYSTEM_NAME MATCHES "Windows|Darwin|Linux|BSD|DragonFly|CYGWIN|MSYS"
+        AND NOT (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19.16)
+        AND NOT (CMAKE_CXX_COMPILER_ID STREQUAL "XLClang" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 16.1)
+        )
+      set(CMake_ENABLE_DEBUGGER 1)
+    else()
+      set(CMake_ENABLE_DEBUGGER 0)
+    endif()
+  endif()
+else()
+  set(CMake_ENABLE_DEBUGGER 0)
+endif()
+
 #-----------------------------------------------------------------------
 # a macro to deal with system libraries, implemented as a macro
 # simply to improve readability of the main script
@@ -141,7 +158,7 @@
 
   # Allow the user to enable/disable all system utility library options by
   # defining CMAKE_USE_SYSTEM_LIBRARIES or CMAKE_USE_SYSTEM_LIBRARY_${util}.
-  set(UTILITIES BZIP2 CURL EXPAT FORM JSONCPP LIBARCHIVE LIBLZMA LIBRHASH LIBUV NGHTTP2 ZLIB ZSTD)
+  set(UTILITIES BZIP2 CPPDAP CURL EXPAT FORM JSONCPP LIBARCHIVE LIBLZMA LIBRHASH LIBUV NGHTTP2 ZLIB ZSTD)
   foreach(util IN LISTS UTILITIES)
     if(NOT DEFINED CMAKE_USE_SYSTEM_LIBRARY_${util}
         AND DEFINED CMAKE_USE_SYSTEM_LIBRARIES)
@@ -169,6 +186,9 @@
 
   # Optionally use system utility libraries.
   option(CMAKE_USE_SYSTEM_LIBARCHIVE "Use system-installed libarchive" "${CMAKE_USE_SYSTEM_LIBRARY_LIBARCHIVE}")
+  if(CMake_ENABLE_DEBUGGER)
+    option(CMAKE_USE_SYSTEM_CPPDAP "Use system-installed cppdap" "${CMAKE_USE_SYSTEM_LIBRARY_CPPDAP}")
+  endif()
   option(CMAKE_USE_SYSTEM_CURL "Use system-installed curl" "${CMAKE_USE_SYSTEM_LIBRARY_CURL}")
   option(CMAKE_USE_SYSTEM_EXPAT "Use system-installed expat" "${CMAKE_USE_SYSTEM_LIBRARY_EXPAT}")
   CMAKE_DEPENDENT_OPTION(CMAKE_USE_SYSTEM_ZLIB "Use system-installed zlib"
@@ -182,7 +202,8 @@
   CMAKE_DEPENDENT_OPTION(CMAKE_USE_SYSTEM_NGHTTP2 "Use system-installed nghttp2"
     "${CMAKE_USE_SYSTEM_LIBRARY_NGHTTP2}" "NOT CMAKE_USE_SYSTEM_CURL" ON)
   option(CMAKE_USE_SYSTEM_FORM "Use system-installed libform" "${CMAKE_USE_SYSTEM_LIBRARY_FORM}")
-  option(CMAKE_USE_SYSTEM_JSONCPP "Use system-installed jsoncpp" "${CMAKE_USE_SYSTEM_LIBRARY_JSONCPP}")
+  CMAKE_DEPENDENT_OPTION(CMAKE_USE_SYSTEM_JSONCPP "Use system-installed jsoncpp"
+    "${CMAKE_USE_SYSTEM_LIBRARY_JSONCPP}" "NOT CMAKE_USE_SYSTEM_CPPDAP" ON)
   option(CMAKE_USE_SYSTEM_LIBRHASH "Use system-installed librhash" "${CMAKE_USE_SYSTEM_LIBRARY_LIBRHASH}")
   option(CMAKE_USE_SYSTEM_LIBUV "Use system-installed libuv" "${CMAKE_USE_SYSTEM_LIBRARY_LIBUV}")
 
diff --git a/CompileFlags.cmake b/CompileFlags.cmake
index 6331af1..f94e079 100644
--- a/CompileFlags.cmake
+++ b/CompileFlags.cmake
@@ -8,7 +8,7 @@
   set(_INTEL_WINDOWS 1)
 endif()
 
-if(WIN32 AND CMAKE_C_COMPILER_ID STREQUAL "Clang"
+if(WIN32 AND CMAKE_C_COMPILER_ID MATCHES "^(Clang|IntelLLVM)$"
    AND "x${CMAKE_CXX_SIMULATE_ID}" STREQUAL "xMSVC")
   set(_CLANG_MSVC_WINDOWS 1)
 endif()
@@ -22,18 +22,19 @@
 else()
 endif()
 
-if(MSVC)
-  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${CMAKE_CXX_LINKER_WRAPPER_FLAG}-stack:10000000")
-endif()
-
 # MSVC 14.28 enables C5105, but the Windows SDK 10.0.18362.0 triggers it.
 if(CMAKE_C_COMPILER_ID STREQUAL "MSVC" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 19.28)
   set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -wd5105")
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -wd5105")
 endif()
 
-if(_CLANG_MSVC_WINDOWS AND "x${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "xGNU")
-  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Xlinker -stack:20000000")
+# Use a stack size large enough for CMake_DEFAULT_RECURSION_LIMIT.
+if(MSVC)
+  string(APPEND CMAKE_EXE_LINKER_FLAGS " ${CMAKE_CXX_LINKER_WRAPPER_FLAG}-stack:10000000")
+elseif(MINGW OR MSYS OR CYGWIN)
+  string(APPEND CMAKE_EXE_LINKER_FLAGS " -Wl,--stack,10000000")
+elseif(_CLANG_MSVC_WINDOWS AND "x${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "xGNU")
+  string(APPEND CMAKE_EXE_LINKER_FLAGS " -Xlinker -stack:20000000")
 endif()
 
 #silence duplicate symbol warnings on AIX
diff --git a/Help/command/FIND_XXX.txt b/Help/command/FIND_XXX.txt
index bd55e24..fe26d2b 100644
--- a/Help/command/FIND_XXX.txt
+++ b/Help/command/FIND_XXX.txt
@@ -132,6 +132,9 @@
 .. |CMAKE_PREFIX_PATH_XXX_SUBDIR| replace::
    |prefix_XXX_SUBDIR| for each ``<prefix>`` in :variable:`CMAKE_PREFIX_PATH`
 
+.. |ENV_CMAKE_PREFIX_PATH_XXX_SUBDIR| replace::
+   |prefix_XXX_SUBDIR| for each ``<prefix>`` in :envvar:`CMAKE_PREFIX_PATH`
+
 .. |SYSTEM_ENVIRONMENT_PREFIX_PATH_XXX_SUBDIR| replace::
    |prefix_XXX_SUBDIR| for each ``<prefix>/[s]bin`` in ``PATH``, and
    |entry_XXX_SUBDIR| for other entries in ``PATH``
@@ -140,21 +143,40 @@
    |prefix_XXX_SUBDIR| for each ``<prefix>`` in
    :variable:`CMAKE_SYSTEM_PREFIX_PATH`
 
-1. .. versionadded:: 3.12
-    If called from within a find module or any other script loaded by a call to
-    :command:`find_package(<PackageName>)`, search prefixes unique to the
-    current package being found.  Specifically, look in the
-    :variable:`<PackageName>_ROOT` CMake variable and the
-    :envvar:`<PackageName>_ROOT` environment variable.
-    The package root variables are maintained as a stack, so if called from
-    nested find modules or config packages, root paths from the parent's find
-    module or config package will be searched after paths from the current
-    module or package.  In other words, the search order would be
-    ``<CurrentPackage>_ROOT``, ``ENV{<CurrentPackage>_ROOT}``,
-    ``<ParentPackage>_ROOT``, ``ENV{<ParentPackage>_ROOT}``, etc.
-    This can be skipped if ``NO_PACKAGE_ROOT_PATH`` is passed or by setting
-    the :variable:`CMAKE_FIND_USE_PACKAGE_ROOT_PATH` to ``FALSE``.
-    See policy :policy:`CMP0074`.
+1. If called from within a find module or any other script loaded by a call to
+   :command:`find_package(<PackageName>)`, search prefixes unique to the
+   current package being found.  See policy :policy:`CMP0074`.
+
+   .. versionadded:: 3.12
+
+   Specifically, search paths specified by the following variables, in order:
+
+   a. :variable:`<PackageName>_ROOT` CMake variable,
+      where ``<PackageName>`` is the case-preserved package name.
+
+   b. :variable:`<PACKAGENAME>_ROOT` CMake variable,
+      where ``<PACKAGENAME>`` is the upper-cased package name.
+      See policy :policy:`CMP0144`.
+
+      .. versionadded:: 3.27
+
+   c. :envvar:`<PackageName>_ROOT` environment variable,
+      where ``<PackageName>`` is the case-preserved package name.
+
+   d. :envvar:`<PACKAGENAME>_ROOT` environment variable,
+      where ``<PACKAGENAME>`` is the upper-cased package name.
+      See policy :policy:`CMP0144`.
+
+      .. versionadded:: 3.27
+
+   The package root variables are maintained as a stack, so if called from
+   nested find modules or config packages, root paths from the parent's find
+   module or config package will be searched after paths from the current
+   module or package.  In other words, the search order would be
+   ``<CurrentPackage>_ROOT``, ``ENV{<CurrentPackage>_ROOT}``,
+   ``<ParentPackage>_ROOT``, ``ENV{<ParentPackage>_ROOT}``, etc.
+   This can be skipped if ``NO_PACKAGE_ROOT_PATH`` is passed or by setting
+   the :variable:`CMAKE_FIND_USE_PACKAGE_ROOT_PATH` to ``FALSE``.
 
    * |FIND_PACKAGE_ROOT_PREFIX_PATH_XXX|
 
@@ -175,9 +197,9 @@
    This can be skipped if ``NO_CMAKE_ENVIRONMENT_PATH`` is passed or
    by setting the :variable:`CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH` to ``FALSE``.
 
-   * |CMAKE_PREFIX_PATH_XXX|
-   * |CMAKE_XXX_PATH|
-   * |CMAKE_XXX_MAC_PATH|
+   * |ENV_CMAKE_PREFIX_PATH_XXX|
+   * |ENV_CMAKE_XXX_PATH|
+   * |ENV_CMAKE_XXX_MAC_PATH|
 
 4. Search the paths specified by the ``HINTS`` option.
    These should be paths computed by system introspection, such as a
diff --git a/Help/command/UNSET_NOTE.txt b/Help/command/UNSET_NOTE.txt
new file mode 100644
index 0000000..8dc9125
--- /dev/null
+++ b/Help/command/UNSET_NOTE.txt
@@ -0,0 +1,9 @@
+.. note::
+
+  When evaluating :ref:`Variable References` of the form ``${VAR}``, CMake
+  first searches for a normal variable with that name.  If no such normal
+  variable exists, CMake will then search for a cache entry with that name.
+  Because of this, **unsetting a normal variable can expose a cache variable
+  that was previously hidden**.  To force a variable reference of the form
+  ``${VAR}`` to return an empty string, use ``set(<variable> "")``, which
+  clears the normal variable but leaves it defined.
diff --git a/Help/command/add_compile_options.rst b/Help/command/add_compile_options.rst
index 0ccebc6..869d0c2 100644
--- a/Help/command/add_compile_options.rst
+++ b/Help/command/add_compile_options.rst
@@ -11,6 +11,11 @@
 These options are used when compiling targets from the current
 directory and below.
 
+.. note::
+
+  These options are not used when linking.
+  See the :command:`add_link_options` command for that.
+
 Arguments
 ^^^^^^^^^
 
@@ -48,5 +53,15 @@
 
 * The command :command:`target_compile_options` adds target-specific options.
 
+* This command adds compile options for all languages.
+  Use the :genex:`COMPILE_LANGUAGE` generator expression to specify
+  per-language compile options.
+
 * The source file property :prop_sf:`COMPILE_OPTIONS` adds options to one
   source file.
+
+* :command:`add_link_options` adds options for linking.
+
+* :variable:`CMAKE_<LANG>_FLAGS` and :variable:`CMAKE_<LANG>_FLAGS_<CONFIG>`
+  add language-wide flags passed to all invocations of the compiler.
+  This includes invocations that drive compiling and those that drive linking.
diff --git a/Help/command/add_custom_command.rst b/Help/command/add_custom_command.rst
index 293d3f0..7b3aa1a 100644
--- a/Help/command/add_custom_command.rst
+++ b/Help/command/add_custom_command.rst
@@ -25,7 +25,8 @@
                      [DEPFILE depfile]
                      [JOB_POOL job_pool]
                      [VERBATIM] [APPEND] [USES_TERMINAL]
-                     [COMMAND_EXPAND_LISTS])
+                     [COMMAND_EXPAND_LISTS]
+                     [DEPENDS_EXPLICIT_ONLY])
 
 This defines a command to generate specified ``OUTPUT`` file(s).
 A target created in the same directory (``CMakeLists.txt`` file)
@@ -357,6 +358,24 @@
   :ref:`Makefile Generators`, :ref:`Visual Studio Generators`,
   and the :generator:`Xcode` generator.
 
+``DEPENDS_EXPLICIT_ONLY``
+
+  .. versionadded:: 3.27
+
+  Indicate that the command's ``DEPENDS`` argument represents all files
+  required by the command and implicit dependencies are not required.
+
+  Without this option, if any target uses the output of the custom command,
+  CMake will consider that target's dependencies as implicit dependencies for
+  the custom command in case this custom command requires files implicitly
+  created by those targets.
+
+  This option can be enabled on all custom commands by setting
+  :variable:`CMAKE_ADD_CUSTOM_COMMAND_DEPENDS_EXPLICIT_ONLY` to ``ON``.
+
+  Only the :ref:`Ninja Generators` actually use this information to remove
+  unnecessary implicit dependencies.
+
 Examples: Generating Files
 ^^^^^^^^^^^^^^^^^^^^^^^^^^
 
@@ -470,9 +489,12 @@
 of the following is specified:
 
 ``PRE_BUILD``
-  On :ref:`Visual Studio Generators`, run before any other rules are
-  executed within the target.
-  On other generators, run just before ``PRE_LINK`` commands.
+  This option has unique behavior for the :ref:`Visual Studio Generators`.
+  When using one of the Visual Studio generators, the command will run before
+  any other rules are executed within the target.  With all other generators,
+  this option behaves the same as ``PRE_LINK`` instead.  Because of this,
+  it is recommended to avoid using ``PRE_BUILD`` except when it is known that
+  a Visual Studio generator is being used.
 ``PRE_LINK``
   Run after sources have been compiled but before linking the binary
   or running the librarian or archiver tool of a static library.
diff --git a/Help/command/add_link_options.rst b/Help/command/add_link_options.rst
index c09e106..df72715 100644
--- a/Help/command/add_link_options.rst
+++ b/Help/command/add_link_options.rst
@@ -38,3 +38,7 @@
 * :command:`link_libraries`
 * :command:`target_link_libraries`
 * :command:`target_link_options`
+
+* :variable:`CMAKE_<LANG>_FLAGS` and :variable:`CMAKE_<LANG>_FLAGS_<CONFIG>`
+  add language-wide flags passed to all invocations of the compiler.
+  This includes invocations that drive compiling and those that drive linking.
diff --git a/Help/command/add_test.rst b/Help/command/add_test.rst
index 53555a4..02dd3986 100644
--- a/Help/command/add_test.rst
+++ b/Help/command/add_test.rst
@@ -12,13 +12,24 @@
 
 Adds a test called ``<name>``.  The test name may contain arbitrary
 characters, expressed as a :ref:`Quoted Argument` or :ref:`Bracket Argument`
-if necessary.  See policy :policy:`CMP0110`.  The options are:
+if necessary.  See policy :policy:`CMP0110`.
+
+CMake only generates tests if the :command:`enable_testing` command has been
+invoked.  The :module:`CTest` module invokes ``enable_testing`` automatically
+unless ``BUILD_TESTING`` is set to ``OFF``.
+
+Tests added with the ``add_test(NAME)`` signature support using
+:manual:`generator expressions <cmake-generator-expressions(7)>`
+in test properties set by :command:`set_property(TEST)` or
+:command:`set_tests_properties`. Test properties may only be set in the
+directory the test is created in.
+
+``add_test`` options are:
 
 ``COMMAND``
-  Specify the test command-line.  If ``<command>`` specifies an
-  executable target (created by :command:`add_executable`) it will
-  automatically be replaced by the location of the executable created
-  at build time.
+  Specify the test command-line.  If ``<command>`` specifies an executable
+  target created by :command:`add_executable`, it will automatically be
+  replaced by the location of the executable created at build time.
 
   The command may be specified using
   :manual:`generator expressions <cmake-generator-expressions(7)>`.
@@ -27,38 +38,29 @@
   Restrict execution of the test only to the named configurations.
 
 ``WORKING_DIRECTORY``
-  Set the :prop_test:`WORKING_DIRECTORY` test property to
-  specify the working directory in which to execute the test.
-  If not specified the test will be run with the current working
-  directory set to the build directory corresponding to the
-  current source directory.
-
-  The working directory may be specified using
-  :manual:`generator expressions <cmake-generator-expressions(7)>`.
+  Set the test property :prop_test:`WORKING_DIRECTORY` in which to execute the
+  test. If not specified, the test will be run in
+  :variable:`CMAKE_CURRENT_BINARY_DIR`. The working directory may be specified
+  using :manual:`generator expressions <cmake-generator-expressions(7)>`.
 
 ``COMMAND_EXPAND_LISTS``
   .. versionadded:: 3.16
 
-  Lists in ``COMMAND`` arguments will be expanded, including those
-  created with
+  Lists in ``COMMAND`` arguments will be expanded, including those created with
   :manual:`generator expressions <cmake-generator-expressions(7)>`.
 
-The given test command is expected to exit with code ``0`` to pass and
-non-zero to fail, or vice-versa if the :prop_test:`WILL_FAIL` test
-property is set.  Any output written to stdout or stderr will be
-captured by :manual:`ctest(1)` but does not affect the pass/fail status
-unless the :prop_test:`PASS_REGULAR_EXPRESSION`,
-:prop_test:`FAIL_REGULAR_EXPRESSION` or
-:prop_test:`SKIP_REGULAR_EXPRESSION` test property is used.
+If the test command exits with code ``0`` the test passes. Non-zero exit code
+is a "failed" test. The test property :prop_test:`WILL_FAIL` inverts this
+logic. Note that system-level test failures such as segmentation faults or
+heap errors will still fail the test even if ``WILL_FALL`` is true. Output
+written to stdout or stderr is captured by :manual:`ctest(1)` and only
+affects the pass/fail status via the :prop_test:`PASS_REGULAR_EXPRESSION`,
+:prop_test:`FAIL_REGULAR_EXPRESSION`, or :prop_test:`SKIP_REGULAR_EXPRESSION`
+test properties.
 
 .. versionadded:: 3.16
   Added :prop_test:`SKIP_REGULAR_EXPRESSION` property.
 
-Tests added with the ``add_test(NAME)`` signature support using
-:manual:`generator expressions <cmake-generator-expressions(7)>`
-in test properties set by :command:`set_property(TEST)` or
-:command:`set_tests_properties`.
-
 Example usage:
 
 .. code-block:: cmake
@@ -71,16 +73,9 @@
 passing the configuration name and the full path to the executable
 file produced by target ``myexe``.
 
-.. note::
-
-  CMake will generate tests only if the :command:`enable_testing`
-  command has been invoked.  The :module:`CTest` module invokes the
-  command automatically unless the ``BUILD_TESTING`` option is turned
-  ``OFF``.
-
 ---------------------------------------------------------------------
 
-This command also supports a simpler, but less flexible, signature:
+The command syntax above is recommended over the older, less flexible form:
 
 .. code-block:: cmake
 
diff --git a/Help/command/cmake_language.rst b/Help/command/cmake_language.rst
index 8801a9f..707568c 100644
--- a/Help/command/cmake_language.rst
+++ b/Help/command/cmake_language.rst
@@ -27,163 +27,155 @@
 Calling Commands
 ^^^^^^^^^^^^^^^^
 
-.. _CALL:
-
-.. code-block:: cmake
-
+.. signature::
   cmake_language(CALL <command> [<arg>...])
 
-Calls the named ``<command>`` with the given arguments (if any).
-For example, the code:
+  Calls the named ``<command>`` with the given arguments (if any).
+  For example, the code:
 
-.. code-block:: cmake
+  .. code-block:: cmake
 
-  set(message_command "message")
-  cmake_language(CALL ${message_command} STATUS "Hello World!")
+    set(message_command "message")
+    cmake_language(CALL ${message_command} STATUS "Hello World!")
 
-is equivalent to
+  is equivalent to
 
-.. code-block:: cmake
+  .. code-block:: cmake
 
-  message(STATUS "Hello World!")
+    message(STATUS "Hello World!")
 
-.. note::
-  To ensure consistency of the code, the following commands are not allowed:
+  .. note::
+    To ensure consistency of the code, the following commands are not allowed:
 
-  * ``if`` / ``elseif`` / ``else`` / ``endif``
-  * ``block`` / ``endblock``
-  * ``while`` / ``endwhile``
-  * ``foreach`` / ``endforeach``
-  * ``function`` / ``endfunction``
-  * ``macro`` / ``endmacro``
+    * ``if`` / ``elseif`` / ``else`` / ``endif``
+    * ``block`` / ``endblock``
+    * ``while`` / ``endwhile``
+    * ``foreach`` / ``endforeach``
+    * ``function`` / ``endfunction``
+    * ``macro`` / ``endmacro``
 
 Evaluating Code
 ^^^^^^^^^^^^^^^
 
-.. _EVAL:
-
-.. code-block:: cmake
-
+.. signature::
   cmake_language(EVAL CODE <code>...)
+  :target: EVAL
 
-Evaluates the ``<code>...`` as CMake code.
+  Evaluates the ``<code>...`` as CMake code.
 
-For example, the code:
+  For example, the code:
 
-.. code-block:: cmake
+  .. code-block:: cmake
 
-  set(A TRUE)
-  set(B TRUE)
-  set(C TRUE)
-  set(condition "(A AND B) OR C")
+    set(A TRUE)
+    set(B TRUE)
+    set(C TRUE)
+    set(condition "(A AND B) OR C")
 
-  cmake_language(EVAL CODE "
-    if (${condition})
-      message(STATUS TRUE)
-    else()
-      message(STATUS FALSE)
-    endif()"
-  )
+    cmake_language(EVAL CODE "
+      if (${condition})
+        message(STATUS TRUE)
+      else()
+        message(STATUS FALSE)
+      endif()"
+    )
 
-is equivalent to
+  is equivalent to
 
-.. code-block:: cmake
+  .. code-block:: cmake
 
-  set(A TRUE)
-  set(B TRUE)
-  set(C TRUE)
-  set(condition "(A AND B) OR C")
+    set(A TRUE)
+    set(B TRUE)
+    set(C TRUE)
+    set(condition "(A AND B) OR C")
 
-  file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/eval.cmake "
-    if (${condition})
-      message(STATUS TRUE)
-    else()
-      message(STATUS FALSE)
-    endif()"
-  )
+    file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/eval.cmake "
+      if (${condition})
+        message(STATUS TRUE)
+      else()
+        message(STATUS FALSE)
+      endif()"
+    )
 
-  include(${CMAKE_CURRENT_BINARY_DIR}/eval.cmake)
+    include(${CMAKE_CURRENT_BINARY_DIR}/eval.cmake)
 
 Deferring Calls
 ^^^^^^^^^^^^^^^
 
 .. versionadded:: 3.19
 
-.. _DEFER:
-
-.. code-block:: cmake
-
+.. signature::
   cmake_language(DEFER <options>... CALL <command> [<arg>...])
 
-Schedules a call to the named ``<command>`` with the given arguments (if any)
-to occur at a later time.  By default, deferred calls are executed as if
-written at the end of the current directory's ``CMakeLists.txt`` file,
-except that they run even after a :command:`return` call.  Variable
-references in arguments are evaluated at the time the deferred call is
-executed.
+  Schedules a call to the named ``<command>`` with the given arguments (if any)
+  to occur at a later time.  By default, deferred calls are executed as if
+  written at the end of the current directory's ``CMakeLists.txt`` file,
+  except that they run even after a :command:`return` call.  Variable
+  references in arguments are evaluated at the time the deferred call is
+  executed.
 
-The options are:
+  The options are:
 
-``DIRECTORY <dir>``
-  Schedule the call for the end of the given directory instead of the
-  current directory.  The ``<dir>`` may reference either a source
-  directory or its corresponding binary directory.  Relative paths are
-  treated as relative to the current source directory.
+  ``DIRECTORY <dir>``
+    Schedule the call for the end of the given directory instead of the
+    current directory.  The ``<dir>`` may reference either a source
+    directory or its corresponding binary directory.  Relative paths are
+    treated as relative to the current source directory.
 
-  The given directory must be known to CMake, being either the top-level
-  directory or one added by :command:`add_subdirectory`.  Furthermore,
-  the given directory must not yet be finished processing.  This means
-  it can be the current directory or one of its ancestors.
+    The given directory must be known to CMake, being either the top-level
+    directory or one added by :command:`add_subdirectory`.  Furthermore,
+    the given directory must not yet be finished processing.  This means
+    it can be the current directory or one of its ancestors.
 
-``ID <id>``
-  Specify an identification for the deferred call.
-  The ``<id>`` may not be empty and may not begin with a capital letter ``A-Z``.
-  The ``<id>`` may begin with an underscore (``_``) only if it was generated
-  automatically by an earlier call that used ``ID_VAR`` to get the id.
+  ``ID <id>``
+    Specify an identification for the deferred call.
+    The ``<id>`` may not be empty and may not begin with a capital letter ``A-Z``.
+    The ``<id>`` may begin with an underscore (``_``) only if it was generated
+    automatically by an earlier call that used ``ID_VAR`` to get the id.
 
-``ID_VAR <var>``
-  Specify a variable in which to store the identification for the
-  deferred call.  If ``ID <id>`` is not given, a new identification
-  will be generated and the generated id will start with an underscore (``_``).
+  ``ID_VAR <var>``
+    Specify a variable in which to store the identification for the
+    deferred call.  If ``ID <id>`` is not given, a new identification
+    will be generated and the generated id will start with an underscore (``_``).
 
-The currently scheduled list of deferred calls may be retrieved:
+  The currently scheduled list of deferred calls may be retrieved:
 
-.. code-block:: cmake
+  .. code-block:: cmake
 
-  cmake_language(DEFER [DIRECTORY <dir>] GET_CALL_IDS <var>)
+    cmake_language(DEFER [DIRECTORY <dir>] GET_CALL_IDS <var>)
 
-This will store in ``<var>`` a :ref:`semicolon-separated list <CMake Language
-Lists>` of deferred call ids.  The ids are for the directory scope in which
-the calls have been deferred to (i.e. where they will be executed), which can
-be different to the scope in which they were created.  The ``DIRECTORY``
-option can be used to specify the scope for which to retrieve the call ids.
-If that option is not given, the call ids for the current directory scope will
-be returned.
+  This will store in ``<var>`` a :ref:`semicolon-separated list <CMake Language
+  Lists>` of deferred call ids.  The ids are for the directory scope in which
+  the calls have been deferred to (i.e. where they will be executed), which can
+  be different to the scope in which they were created.  The ``DIRECTORY``
+  option can be used to specify the scope for which to retrieve the call ids.
+  If that option is not given, the call ids for the current directory scope
+  will be returned.
 
-Details of a specific call may be retrieved from its id:
+  Details of a specific call may be retrieved from its id:
 
-.. code-block:: cmake
+  .. code-block:: cmake
 
-  cmake_language(DEFER [DIRECTORY <dir>] GET_CALL <id> <var>)
+    cmake_language(DEFER [DIRECTORY <dir>] GET_CALL <id> <var>)
 
-This will store in ``<var>`` a :ref:`semicolon-separated list <CMake Language
-Lists>` in which the first element is the name of the command to be
-called, and the remaining elements are its unevaluated arguments (any
-contained ``;`` characters are included literally and cannot be distinguished
-from multiple arguments).  If multiple calls are scheduled with the same id,
-this retrieves the first one.  If no call is scheduled with the given id in
-the specified ``DIRECTORY`` scope (or the current directory scope if no
-``DIRECTORY`` option is given), this stores an empty string in the variable.
+  This will store in ``<var>`` a :ref:`semicolon-separated list <CMake Language
+  Lists>` in which the first element is the name of the command to be
+  called, and the remaining elements are its unevaluated arguments (any
+  contained ``;`` characters are included literally and cannot be distinguished
+  from multiple arguments).  If multiple calls are scheduled with the same id,
+  this retrieves the first one.  If no call is scheduled with the given id in
+  the specified ``DIRECTORY`` scope (or the current directory scope if no
+  ``DIRECTORY`` option is given), this stores an empty string in the variable.
 
-Deferred calls may be canceled by their id:
+  Deferred calls may be canceled by their id:
 
-.. code-block:: cmake
+  .. code-block:: cmake
 
-  cmake_language(DEFER [DIRECTORY <dir>] CANCEL_CALL <id>...)
+    cmake_language(DEFER [DIRECTORY <dir>] CANCEL_CALL <id>...)
 
-This cancels all deferred calls matching any of the given ids in the specified
-``DIRECTORY`` scope (or the current directory scope if no ``DIRECTORY`` option
-is given).  Unknown ids are silently ignored.
+  This cancels all deferred calls matching any of the given ids in the specified
+  ``DIRECTORY`` scope (or the current directory scope if no ``DIRECTORY`` option
+  is given).  Unknown ids are silently ignored.
 
 Deferred Call Examples
 """"""""""""""""""""""
@@ -229,8 +221,6 @@
   Deferred Message 1
   Deferred Message 2
 
-
-.. _SET_DEPENDENCY_PROVIDER:
 .. _dependency_providers:
 
 Dependency Providers
@@ -241,51 +231,50 @@
 .. note:: A high-level introduction to this feature can be found in the
           :ref:`Using Dependencies Guide <dependency_providers_overview>`.
 
-.. code-block:: cmake
-
+.. signature::
   cmake_language(SET_DEPENDENCY_PROVIDER <command>
                  SUPPORTED_METHODS <methods>...)
 
-When a call is made to :command:`find_package` or
-:command:`FetchContent_MakeAvailable`, the call may be forwarded to a
-dependency provider which then has the opportunity to fulfill the request.
-If the request is for one of the ``<methods>`` specified when the provider
-was set, CMake calls the provider's ``<command>`` with a set of
-method-specific arguments.  If the provider does not fulfill the request,
-or if the provider doesn't support the request's method, or no provider
-is set, the built-in :command:`find_package` or
-:command:`FetchContent_MakeAvailable` implementation is used to fulfill
-the request in the usual way.
+  When a call is made to :command:`find_package` or
+  :command:`FetchContent_MakeAvailable`, the call may be forwarded to a
+  dependency provider which then has the opportunity to fulfill the request.
+  If the request is for one of the ``<methods>`` specified when the provider
+  was set, CMake calls the provider's ``<command>`` with a set of
+  method-specific arguments.  If the provider does not fulfill the request,
+  or if the provider doesn't support the request's method, or no provider
+  is set, the built-in :command:`find_package` or
+  :command:`FetchContent_MakeAvailable` implementation is used to fulfill
+  the request in the usual way.
 
-One or more of the following values can be specified for the ``<methods>``
-when setting the provider:
+  One or more of the following values can be specified for the ``<methods>``
+  when setting the provider:
 
-``FIND_PACKAGE``
-  The provider command accepts :command:`find_package` requests.
+  ``FIND_PACKAGE``
+    The provider command accepts :command:`find_package` requests.
 
-``FETCHCONTENT_MAKEAVAILABLE_SERIAL``
-  The provider command accepts :command:`FetchContent_MakeAvailable`
-  requests.  It expects each dependency to be fed to the provider command
-  one at a time, not the whole list in one go.
+  ``FETCHCONTENT_MAKEAVAILABLE_SERIAL``
+    The provider command accepts :command:`FetchContent_MakeAvailable`
+    requests.  It expects each dependency to be fed to the provider command
+    one at a time, not the whole list in one go.
 
-Only one provider can be set at any point in time.  If a provider is already
-set when ``cmake_language(SET_DEPENDENCY_PROVIDER)`` is called, the new
-provider replaces the previously set one.  The specified ``<command>`` must
-already exist when ``cmake_language(SET_DEPENDENCY_PROVIDER)`` is called.
-As a special case, providing an empty string for the ``<command>`` and no
-``<methods>`` will discard any previously set provider.
+  Only one provider can be set at any point in time.  If a provider is already
+  set when ``cmake_language(SET_DEPENDENCY_PROVIDER)`` is called, the new
+  provider replaces the previously set one.  The specified ``<command>`` must
+  already exist when ``cmake_language(SET_DEPENDENCY_PROVIDER)`` is called.
+  As a special case, providing an empty string for the ``<command>`` and no
+  ``<methods>`` will discard any previously set provider.
 
-The dependency provider can only be set while processing one of the files
-specified by the :variable:`CMAKE_PROJECT_TOP_LEVEL_INCLUDES` variable.
-Thus, dependency providers can only be set as part of the first call to
-:command:`project`.  Calling ``cmake_language(SET_DEPENDENCY_PROVIDER)``
-outside of that context will result in an error.
+  The dependency provider can only be set while processing one of the files
+  specified by the :variable:`CMAKE_PROJECT_TOP_LEVEL_INCLUDES` variable.
+  Thus, dependency providers can only be set as part of the first call to
+  :command:`project`.  Calling ``cmake_language(SET_DEPENDENCY_PROVIDER)``
+  outside of that context will result in an error.
 
-.. note::
-  The choice of dependency provider should always be under the user's control.
-  As a convenience, a project may choose to provide a file that users can
-  list in their :variable:`CMAKE_PROJECT_TOP_LEVEL_INCLUDES` variable, but
-  the use of such a file should always be the user's choice.
+  .. note::
+    The choice of dependency provider should always be under the user's control.
+    As a convenience, a project may choose to provide a file that users can
+    list in their :variable:`CMAKE_PROJECT_TOP_LEVEL_INCLUDES` variable, but
+    the use of such a file should always be the user's choice.
 
 Provider commands
 """""""""""""""""
@@ -499,23 +488,21 @@
 
 .. versionadded:: 3.25
 
-.. _GET_MESSAGE_LOG_LEVEL:
 .. _query_message_log_level:
 
-.. code-block:: cmake
-
+.. signature::
   cmake_language(GET_MESSAGE_LOG_LEVEL <output_variable>)
 
-Writes the current :command:`message` logging level
-into the given ``<output_variable>``.
+  Writes the current :command:`message` logging level
+  into the given ``<output_variable>``.
 
-See :command:`message` for the possible logging levels.
+  See :command:`message` for the possible logging levels.
 
-The current message logging level can be set either using the
-:option:`--log-level <cmake --log-level>`
-command line option of the :manual:`cmake(1)` program or using
-the :variable:`CMAKE_MESSAGE_LOG_LEVEL` variable.
+  The current message logging level can be set either using the
+  :option:`--log-level <cmake --log-level>`
+  command line option of the :manual:`cmake(1)` program or using
+  the :variable:`CMAKE_MESSAGE_LOG_LEVEL` variable.
 
-If both the command line option and the variable are set, the command line
-option takes precedence. If neither are set, the default logging level
-is returned.
+  If both the command line option and the variable are set, the command line
+  option takes precedence. If neither are set, the default logging level
+  is returned.
diff --git a/Help/command/configure_file.rst b/Help/command/configure_file.rst
index 6f4cedf..07dc2e1 100644
--- a/Help/command/configure_file.rst
+++ b/Help/command/configure_file.rst
@@ -12,10 +12,10 @@
                  [NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ])
 
 Copies an ``<input>`` file to an ``<output>`` file and substitutes
-variable values referenced as ``@VAR@`` or ``${VAR}`` in the input
-file content.  Each variable reference will be replaced with the
-current value of the variable, or the empty string if the variable
-is not defined.  Furthermore, input lines of the form
+variable values referenced as ``@VAR@``, ``${VAR}``, ``$CACHE{VAR}`` or
+``$ENV{VAR}`` in the input file content.  Each variable reference will be
+replaced with the current value of the variable, or the empty string if
+the variable is not defined.  Furthermore, input lines of the form
 
 .. code-block:: c
 
diff --git a/Help/command/file.rst b/Help/command/file.rst
index df895d0..30a7f4d 100644
--- a/Help/command/file.rst
+++ b/Help/command/file.rst
@@ -26,7 +26,7 @@
   `Reading`_
     file(`READ`_ <filename> <out-var> [...])
     file(`STRINGS`_ <filename> <out-var> [...])
-    file(`\<HASH\> <HASH_>`_ <filename> <out-var>)
+    file(`\<HASH\>`_ <filename> <out-var>)
     file(`TIMESTAMP`_ <filename> <out-var> [...])
     file(`GET_RUNTIME_DEPENDENCIES`_ [...])
 
@@ -68,568 +68,574 @@
 Reading
 ^^^^^^^
 
-.. _READ:
-
-.. code-block:: cmake
-
+.. signature::
   file(READ <filename> <variable>
        [OFFSET <offset>] [LIMIT <max-in>] [HEX])
 
-Read content from a file called ``<filename>`` and store it in a
-``<variable>``.  Optionally start from the given ``<offset>`` and
-read at most ``<max-in>`` bytes.  The ``HEX`` option causes data to
-be converted to a hexadecimal representation (useful for binary data). If the
-``HEX`` option is specified, letters in the output (``a`` through ``f``) are in
-lowercase.
+  Read content from a file called ``<filename>`` and store it in a
+  ``<variable>``.  Optionally start from the given ``<offset>`` and
+  read at most ``<max-in>`` bytes.  The ``HEX`` option causes data to
+  be converted to a hexadecimal representation (useful for binary data).
+  If the ``HEX`` option is specified, letters in the output
+  (``a`` through ``f``) are in lowercase.
 
-.. _STRINGS:
-
-.. code-block:: cmake
-
+.. signature::
   file(STRINGS <filename> <variable> [<options>...])
 
-Parse a list of ASCII strings from ``<filename>`` and store it in
-``<variable>``.  Binary data in the file are ignored.  Carriage return
-(``\r``, CR) characters are ignored.  The options are:
+  Parse a list of ASCII strings from ``<filename>`` and store it in
+  ``<variable>``.  Binary data in the file are ignored.  Carriage return
+  (``\r``, CR) characters are ignored.  The options are:
 
-``LENGTH_MAXIMUM <max-len>``
- Consider only strings of at most a given length.
+    ``LENGTH_MAXIMUM <max-len>``
+      Consider only strings of at most a given length.
 
-``LENGTH_MINIMUM <min-len>``
- Consider only strings of at least a given length.
+    ``LENGTH_MINIMUM <min-len>``
+      Consider only strings of at least a given length.
 
-``LIMIT_COUNT <max-num>``
- Limit the number of distinct strings to be extracted.
+    ``LIMIT_COUNT <max-num>``
+      Limit the number of distinct strings to be extracted.
 
-``LIMIT_INPUT <max-in>``
- Limit the number of input bytes to read from the file.
+    ``LIMIT_INPUT <max-in>``
+      Limit the number of input bytes to read from the file.
 
-``LIMIT_OUTPUT <max-out>``
- Limit the number of total bytes to store in the ``<variable>``.
+    ``LIMIT_OUTPUT <max-out>``
+      Limit the number of total bytes to store in the ``<variable>``.
 
-``NEWLINE_CONSUME``
- Treat newline characters (``\n``, LF) as part of string content
- instead of terminating at them.
+    ``NEWLINE_CONSUME``
+      Treat newline characters (``\n``, LF) as part of string content
+      instead of terminating at them.
 
-``NO_HEX_CONVERSION``
- Intel Hex and Motorola S-record files are automatically converted to
- binary while reading unless this option is given.
+    ``NO_HEX_CONVERSION``
+      Intel Hex and Motorola S-record files are automatically converted to
+      binary while reading unless this option is given.
 
-``REGEX <regex>``
- Consider only strings that match the given regular expression,
- as described under :ref:`string(REGEX) <Regex Specification>`.
+    ``REGEX <regex>``
+      Consider only strings that match the given regular expression,
+      as described under :ref:`string(REGEX) <Regex Specification>`.
 
-``ENCODING <encoding-type>``
- .. versionadded:: 3.1
+    ``ENCODING <encoding-type>``
+      .. versionadded:: 3.1
 
- Consider strings of a given encoding.  Currently supported encodings are:
- ``UTF-8``, ``UTF-16LE``, ``UTF-16BE``, ``UTF-32LE``, ``UTF-32BE``.
- If the ``ENCODING`` option is not provided and the file has a Byte Order Mark,
- the ``ENCODING`` option will be defaulted to respect the Byte Order Mark.
+      Consider strings of a given encoding.  Currently supported encodings are:
+      ``UTF-8``, ``UTF-16LE``, ``UTF-16BE``, ``UTF-32LE``, ``UTF-32BE``.
+      If the ``ENCODING`` option is not provided and the file
+      has a Byte Order Mark, the ``ENCODING`` option will be defaulted
+      to respect the Byte Order Mark.
 
- .. versionadded:: 3.2
-   Added the ``UTF-16LE``, ``UTF-16BE``, ``UTF-32LE``, ``UTF-32BE`` encodings.
+  .. versionadded:: 3.2
+    Added the ``UTF-16LE``, ``UTF-16BE``, ``UTF-32LE``, ``UTF-32BE`` encodings.
 
-For example, the code
+  For example, the code
 
-.. code-block:: cmake
+  .. code-block:: cmake
 
-  file(STRINGS myfile.txt myfile)
+    file(STRINGS myfile.txt myfile)
 
-stores a list in the variable ``myfile`` in which each item is a line
-from the input file.
+  stores a list in the variable ``myfile`` in which each item is a line
+  from the input file.
 
-.. _HASH:
-
-.. code-block:: cmake
-
+.. signature::
   file(<HASH> <filename> <variable>)
+  :target: <HASH>
 
-Compute a cryptographic hash of the content of ``<filename>`` and
-store it in a ``<variable>``.  The supported ``<HASH>`` algorithm names
-are those listed by the :ref:`string(\<HASH\>) <Supported Hash Algorithms>`
-command.
+  Compute a cryptographic hash of the content of ``<filename>`` and
+  store it in a ``<variable>``.  The supported ``<HASH>`` algorithm names
+  are those listed by the :command:`string(<HASH>)` command.
 
-.. _TIMESTAMP:
-
-.. code-block:: cmake
-
+.. signature::
   file(TIMESTAMP <filename> <variable> [<format>] [UTC])
 
-Compute a string representation of the modification time of ``<filename>``
-and store it in ``<variable>``.  Should the command be unable to obtain a
-timestamp variable will be set to the empty string ("").
+  Compute a string representation of the modification time of ``<filename>``
+  and store it in ``<variable>``.  Should the command be unable to obtain a
+  timestamp variable will be set to the empty string ("").
 
-See the :command:`string(TIMESTAMP)` command for documentation of
-the ``<format>`` and ``UTC`` options.
+  See the :command:`string(TIMESTAMP)` command for documentation of
+  the ``<format>`` and ``UTC`` options.
 
-.. _GET_RUNTIME_DEPENDENCIES:
+.. signature::
+  file(GET_RUNTIME_DEPENDENCIES [...])
 
-.. code-block:: cmake
+  .. versionadded:: 3.16
 
-  file(GET_RUNTIME_DEPENDENCIES
-    [RESOLVED_DEPENDENCIES_VAR <deps_var>]
-    [UNRESOLVED_DEPENDENCIES_VAR <unresolved_deps_var>]
-    [CONFLICTING_DEPENDENCIES_PREFIX <conflicting_deps_prefix>]
-    [EXECUTABLES [<executable_files>...]]
-    [LIBRARIES [<library_files>...]]
-    [MODULES [<module_files>...]]
-    [DIRECTORIES [<directories>...]]
-    [BUNDLE_EXECUTABLE <bundle_executable_file>]
-    [PRE_INCLUDE_REGEXES [<regexes>...]]
-    [PRE_EXCLUDE_REGEXES [<regexes>...]]
-    [POST_INCLUDE_REGEXES [<regexes>...]]
-    [POST_EXCLUDE_REGEXES [<regexes>...]]
-    [POST_INCLUDE_FILES [<files>...]]
-    [POST_EXCLUDE_FILES [<files>...]]
-    )
+  Recursively get the list of libraries depended on by the given files:
 
-.. versionadded:: 3.16
+  .. code-block:: cmake
 
-Recursively get the list of libraries depended on by the given files.
-
-Please note that this sub-command is not intended to be used in project mode.
-It is intended for use at install time, either from code generated by the
-:command:`install(RUNTIME_DEPENDENCY_SET)` command, or from code provided by
-the project via :command:`install(CODE)` or :command:`install(SCRIPT)`.
-For example:
-
-.. code-block:: cmake
-
-  install(CODE [[
     file(GET_RUNTIME_DEPENDENCIES
-      # ...
+      [RESOLVED_DEPENDENCIES_VAR <deps_var>]
+      [UNRESOLVED_DEPENDENCIES_VAR <unresolved_deps_var>]
+      [CONFLICTING_DEPENDENCIES_PREFIX <conflicting_deps_prefix>]
+      [EXECUTABLES [<executable_files>...]]
+      [LIBRARIES [<library_files>...]]
+      [MODULES [<module_files>...]]
+      [DIRECTORIES [<directories>...]]
+      [BUNDLE_EXECUTABLE <bundle_executable_file>]
+      [PRE_INCLUDE_REGEXES [<regexes>...]]
+      [PRE_EXCLUDE_REGEXES [<regexes>...]]
+      [POST_INCLUDE_REGEXES [<regexes>...]]
+      [POST_EXCLUDE_REGEXES [<regexes>...]]
+      [POST_INCLUDE_FILES [<files>...]]
+      [POST_EXCLUDE_FILES [<files>...]]
       )
-    ]])
 
-The arguments are as follows:
+  Please note that this sub-command is not intended to be used in project mode.
+  It is intended for use at install time, either from code generated by the
+  :command:`install(RUNTIME_DEPENDENCY_SET)` command, or from code provided by
+  the project via :command:`install(CODE)` or :command:`install(SCRIPT)`.
+  For example:
 
-``RESOLVED_DEPENDENCIES_VAR <deps_var>``
-  Name of the variable in which to store the list of resolved dependencies.
+  .. code-block:: cmake
 
-``UNRESOLVED_DEPENDENCIES_VAR <unresolved_deps_var>``
-  Name of the variable in which to store the list of unresolved dependencies.
-  If this variable is not specified, and there are any unresolved dependencies,
-  an error is issued.
+    install(CODE [[
+      file(GET_RUNTIME_DEPENDENCIES
+        # ...
+        )
+      ]])
 
-``CONFLICTING_DEPENDENCIES_PREFIX <conflicting_deps_prefix>``
-  Variable prefix in which to store conflicting dependency information.
-  Dependencies are conflicting if two files with the same name are found in
-  two different directories. The list of filenames that conflict are stored in
-  ``<conflicting_deps_prefix>_FILENAMES``. For each filename, the list of paths
-  that were found for that filename are stored in
-  ``<conflicting_deps_prefix>_<filename>``.
+  The arguments are as follows:
 
-``EXECUTABLES <executable_files>``
-  List of executable files to read for dependencies. These are executables that
-  are typically created with :command:`add_executable`, but they do not have to
-  be created by CMake. On Apple platforms, the paths to these files determine
-  the value of ``@executable_path`` when recursively resolving the libraries.
-  Specifying any kind of library (``STATIC``, ``MODULE``, or ``SHARED``) here
-  will result in undefined behavior.
+    ``RESOLVED_DEPENDENCIES_VAR <deps_var>``
+      Name of the variable in which to store the list of resolved dependencies.
 
-``LIBRARIES <library_files>``
-  List of library files to read for dependencies. These are libraries that are
-  typically created with :command:`add_library(SHARED)`, but they do not have
-  to be created by CMake. Specifying ``STATIC`` libraries, ``MODULE``
-  libraries, or executables here will result in undefined behavior.
+    ``UNRESOLVED_DEPENDENCIES_VAR <unresolved_deps_var>``
+      Name of the variable in which to store the list of unresolved
+      dependencies. If this variable is not specified, and there are any
+      unresolved dependencies, an error is issued.
 
-``MODULES <module_files>``
-  List of loadable module files to read for dependencies. These are modules
-  that are typically created with :command:`add_library(MODULE)`, but they do
-  not have to be created by CMake. They are typically used by calling
-  ``dlopen()`` at runtime rather than linked at link time with ``ld -l``.
-  Specifying ``STATIC`` libraries, ``SHARED`` libraries, or executables here
-  will result in undefined behavior.
+    ``CONFLICTING_DEPENDENCIES_PREFIX <conflicting_deps_prefix>``
+      Variable prefix in which to store conflicting dependency information.
+      Dependencies are conflicting if two files with the same name are found in
+      two different directories. The list of filenames that conflict are stored
+      in ``<conflicting_deps_prefix>_FILENAMES``. For each filename, the list
+      of paths that were found for that filename are stored in
+      ``<conflicting_deps_prefix>_<filename>``.
 
-``DIRECTORIES <directories>``
-  List of additional directories to search for dependencies. On Linux
-  platforms, these directories are searched if the dependency is not found in
-  any of the other usual paths. If it is found in such a directory, a warning
-  is issued, because it means that the file is incomplete (it does not list all
-  of the directories that contain its dependencies). On Windows platforms,
-  these directories are searched if the dependency is not found in any of the
-  other search paths, but no warning is issued, because searching other paths
-  is a normal part of Windows dependency resolution. On Apple platforms, this
-  argument has no effect.
+    ``EXECUTABLES <executable_files>``
+      List of executable files to read for dependencies. These are executables
+      that are typically created with :command:`add_executable`, but they do
+      not have to be created by CMake. On Apple platforms, the paths to these
+      files determine the value of ``@executable_path`` when recursively
+      resolving the libraries. Specifying any kind of library (``STATIC``,
+      ``MODULE``, or ``SHARED``) here will result in undefined behavior.
 
-``BUNDLE_EXECUTABLE <bundle_executable_file>``
-  Executable to treat as the "bundle executable" when resolving libraries. On
-  Apple platforms, this argument determines the value of ``@executable_path``
-  when recursively resolving libraries for ``LIBRARIES`` and ``MODULES`` files.
-  It has no effect on ``EXECUTABLES`` files. On other platforms, it has no
-  effect. This is typically (but not always) one of the executables in the
-  ``EXECUTABLES`` argument which designates the "main" executable of the
-  package.
+    ``LIBRARIES <library_files>``
+      List of library files to read for dependencies. These are libraries that
+      are typically created with :command:`add_library(SHARED)`, but they do
+      not have to be created by CMake. Specifying ``STATIC`` libraries,
+      ``MODULE`` libraries, or executables here will result in undefined
+      behavior.
 
-The following arguments specify filters for including or excluding libraries to
-be resolved. See below for a full description of how they work.
+    ``MODULES <module_files>``
+      List of loadable module files to read for dependencies. These are modules
+      that are typically created with :command:`add_library(MODULE)`, but they
+      do not have to be created by CMake. They are typically used by calling
+      ``dlopen()`` at runtime rather than linked at link time with ``ld -l``.
+      Specifying ``STATIC`` libraries, ``SHARED`` libraries, or executables
+      here will result in undefined behavior.
 
-``PRE_INCLUDE_REGEXES <regexes>``
-  List of pre-include regexes through which to filter the names of
-  not-yet-resolved dependencies.
+    ``DIRECTORIES <directories>``
+      List of additional directories to search for dependencies. On Linux
+      platforms, these directories are searched if the dependency is not found
+      in any of the other usual paths. If it is found in such a directory, a
+      warning is issued, because it means that the file is incomplete (it does
+      not list all of the directories that contain its dependencies).
+      On Windows platforms, these directories are searched if the dependency
+      is not found in any of the other search paths, but no warning is issued,
+      because searching other paths is a normal part of Windows dependency
+      resolution. On Apple platforms, this argument has no effect.
 
-``PRE_EXCLUDE_REGEXES <regexes>``
-  List of pre-exclude regexes through which to filter the names of
-  not-yet-resolved dependencies.
+    ``BUNDLE_EXECUTABLE <bundle_executable_file>``
+      Executable to treat as the "bundle executable" when resolving libraries.
+      On Apple platforms, this argument determines the value of
+      ``@executable_path`` when recursively resolving libraries for
+      ``LIBRARIES`` and ``MODULES`` files. It has no effect on ``EXECUTABLES``
+      files. On other platforms, it has no effect. This is typically (but not
+      always) one of the executables in the ``EXECUTABLES`` argument which
+      designates the "main" executable of the package.
 
-``POST_INCLUDE_REGEXES <regexes>``
-  List of post-include regexes through which to filter the names of resolved
-  dependencies.
+  The following arguments specify filters for including or excluding libraries
+  to be resolved. See below for a full description of how they work.
 
-``POST_EXCLUDE_REGEXES <regexes>``
-  List of post-exclude regexes through which to filter the names of resolved
-  dependencies.
+    ``PRE_INCLUDE_REGEXES <regexes>``
+      List of pre-include regexes through which to filter the names of
+      not-yet-resolved dependencies.
 
-``POST_INCLUDE_FILES <files>``
-  .. versionadded:: 3.21
+    ``PRE_EXCLUDE_REGEXES <regexes>``
+      List of pre-exclude regexes through which to filter the names of
+      not-yet-resolved dependencies.
 
-  List of post-include filenames through which to filter the names of resolved
-  dependencies. Symlinks are resolved when attempting to match these filenames.
+    ``POST_INCLUDE_REGEXES <regexes>``
+      List of post-include regexes through which to filter the names of
+      resolved dependencies.
 
-``POST_EXCLUDE_FILES <files>``
-  .. versionadded:: 3.21
+    ``POST_EXCLUDE_REGEXES <regexes>``
+      List of post-exclude regexes through which to filter the names of
+      resolved dependencies.
 
-  List of post-exclude filenames through which to filter the names of resolved
-  dependencies. Symlinks are resolved when attempting to match these filenames.
+    ``POST_INCLUDE_FILES <files>``
+      .. versionadded:: 3.21
 
-These arguments can be used to exclude unwanted system libraries when
-resolving the dependencies, or to include libraries from a specific
-directory. The filtering works as follows:
+      List of post-include filenames through which to filter the names of
+      resolved dependencies. Symlinks are resolved when attempting to match
+      these filenames.
 
-1. If the not-yet-resolved dependency matches any of the
-   ``PRE_INCLUDE_REGEXES``, steps 2 and 3 are skipped, and the dependency
-   resolution proceeds to step 4.
-2. If the not-yet-resolved dependency matches any of the
-   ``PRE_EXCLUDE_REGEXES``, dependency resolution stops for that dependency.
-3. Otherwise, dependency resolution proceeds.
-4. ``file(GET_RUNTIME_DEPENDENCIES)`` searches for the dependency according to
-   the linking rules of the platform (see below).
-5. If the dependency is found, and its full path matches one of the
-   ``POST_INCLUDE_REGEXES`` or ``POST_INCLUDE_FILES``, the full path is added
-   to the resolved dependencies, and ``file(GET_RUNTIME_DEPENDENCIES)``
-   recursively resolves that library's own dependencies. Otherwise, resolution
-   proceeds to step 6.
-6. If the dependency is found, but its full path matches one of the
-   ``POST_EXCLUDE_REGEXES`` or ``POST_EXCLUDE_FILES``, it is not added to the
-   resolved dependencies, and dependency resolution stops for that dependency.
-7. If the dependency is found, and its full path does not match either
-   ``POST_INCLUDE_REGEXES``, ``POST_INCLUDE_FILES``, ``POST_EXCLUDE_REGEXES``,
-   or ``POST_EXCLUDE_FILES``, the full path is added to the resolved
-   dependencies, and ``file(GET_RUNTIME_DEPENDENCIES)``  recursively resolves
-   that library's own dependencies.
+    ``POST_EXCLUDE_FILES <files>``
+      .. versionadded:: 3.21
 
-Different platforms have different rules for how dependencies are resolved.
-These specifics are described here.
+      List of post-exclude filenames through which to filter the names of
+      resolved dependencies. Symlinks are resolved when attempting to match
+      these filenames.
 
-On Linux platforms, library resolution works as follows:
+  These arguments can be used to exclude unwanted system libraries when
+  resolving the dependencies, or to include libraries from a specific
+  directory. The filtering works as follows:
 
-1. If the depending file does not have any ``RUNPATH`` entries, and the library
-   exists in one of the depending file's ``RPATH`` entries, or its parents', in
-   that order, the dependency is resolved to that file.
-2. Otherwise, if the depending file has any ``RUNPATH`` entries, and the
-   library exists in one of those entries, the dependency is resolved to that
-   file.
-3. Otherwise, if the library exists in one of the directories listed by
-   ``ldconfig``, the dependency is resolved to that file.
-4. Otherwise, if the library exists in one of the ``DIRECTORIES`` entries, the
-   dependency is resolved to that file. In this case, a warning is issued,
-   because finding a file in one of the ``DIRECTORIES`` means that the
-   depending file is not complete (it does not list all the directories from
-   which it pulls dependencies).
-5. Otherwise, the dependency is unresolved.
+  1. If the not-yet-resolved dependency matches any of the
+     ``PRE_INCLUDE_REGEXES``, steps 2 and 3 are skipped, and the dependency
+     resolution proceeds to step 4.
 
-On Windows platforms, library resolution works as follows:
+  2. If the not-yet-resolved dependency matches any of the
+     ``PRE_EXCLUDE_REGEXES``, dependency resolution stops for that dependency.
 
-1. The dependent DLL name is converted to lowercase. Windows DLL names are
-   case-insensitive, and some linkers mangle the case of the DLL dependency
-   names. However, this makes it more difficult for ``PRE_INCLUDE_REGEXES``,
-   ``PRE_EXCLUDE_REGEXES``, ``POST_INCLUDE_REGEXES``, and
-   ``POST_EXCLUDE_REGEXES`` to properly filter DLL names - every regex would
-   have to check for both uppercase and lowercase letters. For example:
+  3. Otherwise, dependency resolution proceeds.
 
-   .. code-block:: cmake
+  4. ``file(GET_RUNTIME_DEPENDENCIES)`` searches for the dependency according
+     to the linking rules of the platform (see below).
 
-     file(GET_RUNTIME_DEPENDENCIES
-       # ...
-       PRE_INCLUDE_REGEXES "^[Mm][Yy][Ll][Ii][Bb][Rr][Aa][Rr][Yy]\\.[Dd][Ll][Ll]$"
-       )
+  5. If the dependency is found, and its full path matches one of the
+     ``POST_INCLUDE_REGEXES`` or ``POST_INCLUDE_FILES``, the full path is added
+     to the resolved dependencies, and ``file(GET_RUNTIME_DEPENDENCIES)``
+     recursively resolves that library's own dependencies. Otherwise, resolution
+     proceeds to step 6.
 
-   Converting the DLL name to lowercase allows the regexes to only match
-   lowercase names, thus simplifying the regex. For example:
+  6. If the dependency is found, but its full path matches one of the
+     ``POST_EXCLUDE_REGEXES`` or ``POST_EXCLUDE_FILES``, it is not added to the
+     resolved dependencies, and dependency resolution stops for that dependency.
 
-   .. code-block:: cmake
+  7. If the dependency is found, and its full path does not match either
+     ``POST_INCLUDE_REGEXES``, ``POST_INCLUDE_FILES``, ``POST_EXCLUDE_REGEXES``,
+     or ``POST_EXCLUDE_FILES``, the full path is added to the resolved
+     dependencies, and ``file(GET_RUNTIME_DEPENDENCIES)``  recursively resolves
+     that library's own dependencies.
 
-     file(GET_RUNTIME_DEPENDENCIES
-       # ...
-       PRE_INCLUDE_REGEXES "^mylibrary\\.dll$"
-       )
+  Different platforms have different rules for how dependencies are resolved.
+  These specifics are described here.
 
-   This regex will match ``mylibrary.dll`` regardless of how it is cased,
-   either on disk or in the depending file. (For example, it will match
-   ``mylibrary.dll``, ``MyLibrary.dll``, and ``MYLIBRARY.DLL``.)
+  On Linux platforms, library resolution works as follows:
 
-   Please note that the directory portion of any resolved DLLs retains its
-   casing and is not converted to lowercase. Only the filename portion is
-   converted.
+  1. If the depending file does not have any ``RUNPATH`` entries, and the
+     library exists in one of the depending file's ``RPATH`` entries, or its
+     parents', in that order, the dependency is resolved to that file.
+  2. Otherwise, if the depending file has any ``RUNPATH`` entries, and the
+     library exists in one of those entries, the dependency is resolved to that
+     file.
+  3. Otherwise, if the library exists in one of the directories listed by
+     ``ldconfig``, the dependency is resolved to that file.
+  4. Otherwise, if the library exists in one of the ``DIRECTORIES`` entries,
+     the dependency is resolved to that file. In this case, a warning is
+     issued, because finding a file in one of the ``DIRECTORIES`` means that
+     the depending file is not complete (it does not list all the directories
+     from which it pulls dependencies).
 
-2. (**Not yet implemented**) If the depending file is a Windows Store app, and
-   the dependency is listed as a dependency in the application's package
-   manifest, the dependency is resolved to that file.
-3. Otherwise, if the library exists in the same directory as the depending
-   file, the dependency is resolved to that file.
-4. Otherwise, if the library exists in either the operating system's
-   ``system32`` directory or the ``Windows`` directory, in that order, the
-   dependency is resolved to that file.
-5. Otherwise, if the library exists in one of the directories specified by
-   ``DIRECTORIES``, in the order they are listed, the dependency is resolved to
-   that file. In this case, a warning is not issued, because searching other
-   directories is a normal part of Windows library resolution.
-6. Otherwise, the dependency is unresolved.
+  5. Otherwise, the dependency is unresolved.
 
-On Apple platforms, library resolution works as follows:
+  On Windows platforms, library resolution works as follows:
 
-1. If the dependency starts with ``@executable_path/``, and an ``EXECUTABLES``
-   argument is in the process of being resolved, and replacing
-   ``@executable_path/`` with the directory of the executable yields an
-   existing file, the dependency is resolved to that file.
-2. Otherwise, if the dependency starts with ``@executable_path/``, and there is
-   a ``BUNDLE_EXECUTABLE`` argument, and replacing ``@executable_path/`` with
-   the directory of the bundle executable yields an existing file, the
-   dependency is resolved to that file.
-3. Otherwise, if the dependency starts with ``@loader_path/``, and replacing
-   ``@loader_path/`` with the directory of the depending file yields an
-   existing file, the dependency is resolved to that file.
-4. Otherwise, if the dependency starts with ``@rpath/``, and replacing
-   ``@rpath/`` with one of the ``RPATH`` entries of the depending file yields
-   an existing file, the dependency is resolved to that file. Note that
-   ``RPATH`` entries that start with ``@executable_path/`` or ``@loader_path/``
-   also have these items replaced with the appropriate path.
-5. Otherwise, if the dependency is an absolute file that exists, the dependency
-   is resolved to that file.
-6. Otherwise, the dependency is unresolved.
+  1. DLL dependency names are converted to lowercase for matching filters.
+     Windows DLL names are case-insensitive, and some linkers mangle the
+     case of the DLL dependency names.  However, this makes it more difficult
+     for ``PRE_INCLUDE_REGEXES``, ``PRE_EXCLUDE_REGEXES``,
+     ``POST_INCLUDE_REGEXES``, and ``POST_EXCLUDE_REGEXES`` to properly
+     filter DLL names - every regex would have to check for both uppercase
+     and lowercase letters.  For example:
 
-This function accepts several variables that determine which tool is used for
-dependency resolution:
+     .. code-block:: cmake
 
-.. variable:: CMAKE_GET_RUNTIME_DEPENDENCIES_PLATFORM
+       file(GET_RUNTIME_DEPENDENCIES
+         # ...
+         PRE_INCLUDE_REGEXES "^[Mm][Yy][Ll][Ii][Bb][Rr][Aa][Rr][Yy]\\.[Dd][Ll][Ll]$"
+         )
 
-  Determines which operating system and executable format the files are built
-  for. This could be one of several values:
+     Converting the DLL name to lowercase allows the regexes to only match
+     lowercase names, thus simplifying the regex. For example:
 
-  * ``linux+elf``
-  * ``windows+pe``
-  * ``macos+macho``
+     .. code-block:: cmake
 
-  If this variable is not specified, it is determined automatically by system
-  introspection.
+       file(GET_RUNTIME_DEPENDENCIES
+         # ...
+         PRE_INCLUDE_REGEXES "^mylibrary\\.dll$"
+         )
 
-.. variable:: CMAKE_GET_RUNTIME_DEPENDENCIES_TOOL
+     This regex will match ``mylibrary.dll`` regardless of how it is cased,
+     either on disk or in the depending file. (For example, it will match
+     ``mylibrary.dll``, ``MyLibrary.dll``, and ``MYLIBRARY.DLL``.)
 
-  Determines the tool to use for dependency resolution. It could be one of
-  several values, depending on the value of
-  :variable:`CMAKE_GET_RUNTIME_DEPENDENCIES_PLATFORM`:
+     .. versionchanged:: 3.27
 
-  ================================================= =============================================
-     ``CMAKE_GET_RUNTIME_DEPENDENCIES_PLATFORM``       ``CMAKE_GET_RUNTIME_DEPENDENCIES_TOOL``
-  ================================================= =============================================
-  ``linux+elf``                                     ``objdump``
-  ``windows+pe``                                    ``dumpbin``
-  ``windows+pe``                                    ``objdump``
-  ``macos+macho``                                   ``otool``
-  ================================================= =============================================
+       The conversion to lowercase only applies while matching filters.
+       Results reported after filtering case-preserve each DLL name as it is
+       found on disk, if resolved, and otherwise as it is referenced by the
+       dependent binary.
 
-  If this variable is not specified, it is determined automatically by system
-  introspection.
+       Prior to CMake 3.27, the results were reported with lowercase DLL
+       file names, but the directory portion retained its casing.
 
-.. variable:: CMAKE_GET_RUNTIME_DEPENDENCIES_COMMAND
+  2. (**Not yet implemented**) If the depending file is a Windows Store app,
+     and the dependency is listed as a dependency in the application's package
+     manifest, the dependency is resolved to that file.
 
-  Determines the path to the tool to use for dependency resolution. This is the
-  actual path to ``objdump``, ``dumpbin``, or ``otool``.
+  3. Otherwise, if the library exists in the same directory as the depending
+     file, the dependency is resolved to that file.
 
-  If this variable is not specified, it is determined by the value of
-  ``CMAKE_OBJDUMP`` if set, else by system introspection.
+  4. Otherwise, if the library exists in either the operating system's
+     ``system32`` directory or the ``Windows`` directory, in that order, the
+     dependency is resolved to that file.
 
-  .. versionadded:: 3.18
-    Use ``CMAKE_OBJDUMP`` if set.
+  5. Otherwise, if the library exists in one of the directories specified by
+     ``DIRECTORIES``, in the order they are listed, the dependency is resolved
+     to that file. In this case, a warning is not issued, because searching
+     other directories is a normal part of Windows library resolution.
+
+  6. Otherwise, the dependency is unresolved.
+
+  On Apple platforms, library resolution works as follows:
+
+  1. If the dependency starts with ``@executable_path/``, and an
+     ``EXECUTABLES`` argument is in the process of being resolved, and
+     replacing ``@executable_path/`` with the directory of the executable
+     yields an existing file, the dependency is resolved to that file.
+
+  2. Otherwise, if the dependency starts with ``@executable_path/``, and there
+     is a ``BUNDLE_EXECUTABLE`` argument, and replacing ``@executable_path/``
+     with the directory of the bundle executable yields an existing file, the
+     dependency is resolved to that file.
+
+  3. Otherwise, if the dependency starts with ``@loader_path/``, and replacing
+     ``@loader_path/`` with the directory of the depending file yields an
+     existing file, the dependency is resolved to that file.
+
+  4. Otherwise, if the dependency starts with ``@rpath/``, and replacing
+     ``@rpath/`` with one of the ``RPATH`` entries of the depending file
+     yields an existing file, the dependency is resolved to that file.
+     Note that ``RPATH`` entries that start with ``@executable_path/`` or
+     ``@loader_path/`` also have these items replaced with the appropriate
+     path.
+
+  5. Otherwise, if the dependency is an absolute file that exists,
+     the dependency is resolved to that file.
+
+  6. Otherwise, the dependency is unresolved.
+
+  This function accepts several variables that determine which tool is used for
+  dependency resolution:
+
+  .. variable:: CMAKE_GET_RUNTIME_DEPENDENCIES_PLATFORM
+
+    Determines which operating system and executable format the files are built
+    for. This could be one of several values:
+
+    * ``linux+elf``
+    * ``windows+pe``
+    * ``macos+macho``
+
+    If this variable is not specified, it is determined automatically by system
+    introspection.
+
+  .. variable:: CMAKE_GET_RUNTIME_DEPENDENCIES_TOOL
+
+    Determines the tool to use for dependency resolution. It could be one of
+    several values, depending on the value of
+    :variable:`CMAKE_GET_RUNTIME_DEPENDENCIES_PLATFORM`:
+
+    ================================================= =============================================
+       ``CMAKE_GET_RUNTIME_DEPENDENCIES_PLATFORM``       ``CMAKE_GET_RUNTIME_DEPENDENCIES_TOOL``
+    ================================================= =============================================
+    ``linux+elf``                                     ``objdump``
+    ``windows+pe``                                    ``objdump`` or ``dumpbin``
+    ``macos+macho``                                   ``otool``
+    ================================================= =============================================
+
+    If this variable is not specified, it is determined automatically by system
+    introspection.
+
+  .. variable:: CMAKE_GET_RUNTIME_DEPENDENCIES_COMMAND
+
+    Determines the path to the tool to use for dependency resolution. This is
+    the actual path to ``objdump``, ``dumpbin``, or ``otool``.
+
+    If this variable is not specified, it is determined by the value of
+    ``CMAKE_OBJDUMP`` if set, else by system introspection.
+
+    .. versionadded:: 3.18
+      Use ``CMAKE_OBJDUMP`` if set.
 
 Writing
 ^^^^^^^
 
-.. _WRITE:
-.. _APPEND:
-
-.. code-block:: cmake
-
+.. signature::
   file(WRITE <filename> <content>...)
   file(APPEND <filename> <content>...)
 
-Write ``<content>`` into a file called ``<filename>``.  If the file does
-not exist, it will be created.  If the file already exists, ``WRITE``
-mode will overwrite it and ``APPEND`` mode will append to the end.
-Any directories in the path specified by ``<filename>`` that do not
-exist will be created.
+  Write ``<content>`` into a file called ``<filename>``.  If the file does
+  not exist, it will be created.  If the file already exists, ``WRITE``
+  mode will overwrite it and ``APPEND`` mode will append to the end.
+  Any directories in the path specified by ``<filename>`` that do not
+  exist will be created.
 
-If the file is a build input, use the :command:`configure_file` command
-to update the file only when its content changes.
+  If the file is a build input, use the :command:`configure_file` command
+  to update the file only when its content changes.
 
-.. _TOUCH:
-.. _TOUCH_NOCREATE:
-
-.. code-block:: cmake
-
+.. signature::
   file(TOUCH [<files>...])
   file(TOUCH_NOCREATE [<files>...])
 
-.. versionadded:: 3.12
+  .. versionadded:: 3.12
 
-Create a file with no content if it does not yet exist. If the file already
-exists, its access and/or modification will be updated to the time when the
-function call is executed.
+  Create a file with no content if it does not yet exist. If the file already
+  exists, its access and/or modification will be updated to the time when the
+  function call is executed.
 
-Use TOUCH_NOCREATE to touch a file if it exists but not create it. If a file
-does not exist it will be silently ignored.
+  Use ``TOUCH_NOCREATE`` to touch a file if it exists but not create it.
+  If a file does not exist it will be silently ignored.
 
-With TOUCH and TOUCH_NOCREATE the contents of an existing file will not be
-modified.
+  With ``TOUCH`` and ``TOUCH_NOCREATE``, the contents of an existing file
+  will not be modified.
 
-.. _GENERATE:
+.. signature::
+  file(GENERATE [...])
 
-.. code-block:: cmake
+  Generate an output file for each build configuration supported by the current
+  :manual:`CMake Generator <cmake-generators(7)>`.  Evaluate
+  :manual:`generator expressions <cmake-generator-expressions(7)>`
+  from the input content to produce the output content.
 
-  file(GENERATE OUTPUT output-file
-       <INPUT input-file|CONTENT content>
-       [CONDITION expression] [TARGET target]
-       [NO_SOURCE_PERMISSIONS | USE_SOURCE_PERMISSIONS |
-        FILE_PERMISSIONS <permissions>...]
-       [NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ])
+  .. code-block:: cmake
 
-Generate an output file for each build configuration supported by the current
-:manual:`CMake Generator <cmake-generators(7)>`.  Evaluate
-:manual:`generator expressions <cmake-generator-expressions(7)>`
-from the input content to produce the output content.  The options are:
+    file(GENERATE OUTPUT <output-file>
+         <INPUT <input-file>|CONTENT <content>>
+         [CONDITION <expression>] [TARGET <target>]
+         [NO_SOURCE_PERMISSIONS | USE_SOURCE_PERMISSIONS |
+          FILE_PERMISSIONS <permissions>...]
+         [NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ])
 
-``CONDITION <condition>``
-  Generate the output file for a particular configuration only if
-  the condition is true.  The condition must be either ``0`` or ``1``
-  after evaluating generator expressions.
+  The options are:
 
-``CONTENT <content>``
-  Use the content given explicitly as input.
+    ``CONDITION <condition>``
+      Generate the output file for a particular configuration only if
+      the condition is true.  The condition must be either ``0`` or ``1``
+      after evaluating generator expressions.
 
-``INPUT <input-file>``
-  Use the content from a given file as input.
+    ``CONTENT <content>``
+      Use the content given explicitly as input.
 
-  .. versionchanged:: 3.10
-    A relative path is treated with respect to the value of
-    :variable:`CMAKE_CURRENT_SOURCE_DIR`.  See policy :policy:`CMP0070`.
+    ``INPUT <input-file>``
+      Use the content from a given file as input.
 
-``OUTPUT <output-file>``
-  Specify the output file name to generate.  Use generator expressions
-  such as :genex:`$<CONFIG>` to specify a configuration-specific
-  output file name.  Multiple configurations may generate the same output
-  file only if the generated content is identical.  Otherwise, the
-  ``<output-file>`` must evaluate to an unique name for each configuration.
+      .. versionchanged:: 3.10
+        A relative path is treated with respect to the value of
+        :variable:`CMAKE_CURRENT_SOURCE_DIR`.  See policy :policy:`CMP0070`.
 
-  .. versionchanged:: 3.10
-    A relative path (after evaluating generator expressions) is treated
-    with respect to the value of :variable:`CMAKE_CURRENT_BINARY_DIR`.
-    See policy :policy:`CMP0070`.
+    ``OUTPUT <output-file>``
+      Specify the output file name to generate.  Use generator expressions
+      such as :genex:`$<CONFIG>` to specify a configuration-specific
+      output file name.  Multiple configurations may generate the same output
+      file only if the generated content is identical.  Otherwise, the
+      ``<output-file>`` must evaluate to an unique name for each configuration.
 
-``TARGET <target>``
-  .. versionadded:: 3.19
+      .. versionchanged:: 3.10
+        A relative path (after evaluating generator expressions) is treated
+        with respect to the value of :variable:`CMAKE_CURRENT_BINARY_DIR`.
+        See policy :policy:`CMP0070`.
 
-  Specify which target to use when evaluating generator expressions that
-  require a target for evaluation (e.g.
-  :genex:`$<COMPILE_FEATURES:...>`,
-  :genex:`$<TARGET_PROPERTY:prop>`).
+    ``TARGET <target>``
+      .. versionadded:: 3.19
 
-``NO_SOURCE_PERMISSIONS``
-  .. versionadded:: 3.20
+      Specify which target to use when evaluating generator expressions that
+      require a target for evaluation (e.g.
+      :genex:`$<COMPILE_FEATURES:...>`,
+      :genex:`$<TARGET_PROPERTY:prop>`).
 
-  The generated file permissions default to the standard 644 value
-  (-rw-r--r--).
+    ``NO_SOURCE_PERMISSIONS``
+      .. versionadded:: 3.20
 
-``USE_SOURCE_PERMISSIONS``
-  .. versionadded:: 3.20
+      The generated file permissions default to the standard 644 value
+      (-rw-r--r--).
 
-  Transfer the file permissions of the ``INPUT`` file to the generated file.
-  This is already the default behavior if none of the three permissions-related
-  keywords are given (``NO_SOURCE_PERMISSIONS``, ``USE_SOURCE_PERMISSIONS``
-  or ``FILE_PERMISSIONS``).  The ``USE_SOURCE_PERMISSIONS`` keyword mostly
-  serves as a way of making the intended behavior clearer at the call site.
-  It is an error to specify this option without ``INPUT``.
+    ``USE_SOURCE_PERMISSIONS``
+      .. versionadded:: 3.20
 
-``FILE_PERMISSIONS <permissions>...``
-  .. versionadded:: 3.20
+      Transfer the file permissions of the ``INPUT`` file to the generated
+      file. This is already the default behavior if none of the three
+      permissions-related keywords are given (``NO_SOURCE_PERMISSIONS``,
+      ``USE_SOURCE_PERMISSIONS`` or ``FILE_PERMISSIONS``).  The
+      ``USE_SOURCE_PERMISSIONS`` keyword mostly serves as a way of making
+      the intended behavior clearer at the call site. It is an error to
+      specify this option without ``INPUT``.
 
-  Use the specified permissions for the generated file.
+    ``FILE_PERMISSIONS <permissions>...``
+      .. versionadded:: 3.20
 
-``NEWLINE_STYLE <style>``
-  .. versionadded:: 3.20
+      Use the specified permissions for the generated file.
 
-  Specify the newline style for the generated file.  Specify
-  ``UNIX`` or ``LF`` for ``\n`` newlines, or specify
-  ``DOS``, ``WIN32``, or ``CRLF`` for ``\r\n`` newlines.
+    ``NEWLINE_STYLE <style>``
+      .. versionadded:: 3.20
 
-Exactly one ``CONTENT`` or ``INPUT`` option must be given.  A specific
-``OUTPUT`` file may be named by at most one invocation of ``file(GENERATE)``.
-Generated files are modified and their timestamp updated on subsequent cmake
-runs only if their content is changed.
+      Specify the newline style for the generated file.  Specify
+      ``UNIX`` or ``LF`` for ``\n`` newlines, or specify
+      ``DOS``, ``WIN32``, or ``CRLF`` for ``\r\n`` newlines.
 
-Note also that ``file(GENERATE)`` does not create the output file until the
-generation phase. The output file will not yet have been written when the
-``file(GENERATE)`` command returns, it is written only after processing all
-of a project's ``CMakeLists.txt`` files.
+  Exactly one ``CONTENT`` or ``INPUT`` option must be given.  A specific
+  ``OUTPUT`` file may be named by at most one invocation of ``file(GENERATE)``.
+  Generated files are modified and their timestamp updated on subsequent cmake
+  runs only if their content is changed.
 
-.. _CONFIGURE:
+  Note also that ``file(GENERATE)`` does not create the output file until the
+  generation phase. The output file will not yet have been written when the
+  ``file(GENERATE)`` command returns, it is written only after processing all
+  of a project's ``CMakeLists.txt`` files.
 
-.. code-block:: cmake
-
-  file(CONFIGURE OUTPUT output-file
-       CONTENT content
+.. signature::
+  file(CONFIGURE OUTPUT <output-file>
+       CONTENT <content>
        [ESCAPE_QUOTES] [@ONLY]
        [NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ])
+  :target: CONFIGURE
 
-.. versionadded:: 3.18
+  .. versionadded:: 3.18
 
-Generate an output file using the input given by ``CONTENT`` and substitute
-variable values referenced as ``@VAR@`` or ``${VAR}`` contained therein. The
-substitution rules behave the same as the :command:`configure_file` command.
-In order to match :command:`configure_file`'s behavior, generator expressions
-are not supported for both ``OUTPUT`` and ``CONTENT``.
+  Generate an output file using the input given by ``CONTENT`` and substitute
+  variable values referenced as ``@VAR@`` or ``${VAR}`` contained therein. The
+  substitution rules behave the same as the :command:`configure_file` command.
+  In order to match :command:`configure_file`'s behavior, generator expressions
+  are not supported for both ``OUTPUT`` and ``CONTENT``.
 
-The arguments are:
+  The arguments are:
 
-``OUTPUT <output-file>``
-  Specify the output file name to generate. A relative path is treated with
-  respect to the value of :variable:`CMAKE_CURRENT_BINARY_DIR`.
-  ``<output-file>`` does not support generator expressions.
+    ``OUTPUT <output-file>``
+      Specify the output file name to generate. A relative path is treated with
+      respect to the value of :variable:`CMAKE_CURRENT_BINARY_DIR`.
+      ``<output-file>`` does not support generator expressions.
 
-``CONTENT <content>``
-  Use the content given explicitly as input.
-  ``<content>`` does not support generator expressions.
+    ``CONTENT <content>``
+      Use the content given explicitly as input.
+      ``<content>`` does not support generator expressions.
 
-``ESCAPE_QUOTES``
-  Escape any substituted quotes with backslashes (C-style).
+    ``ESCAPE_QUOTES``
+      Escape any substituted quotes with backslashes (C-style).
 
-``@ONLY``
-  Restrict variable replacement to references of the form ``@VAR@``.
-  This is useful for configuring scripts that use ``${VAR}`` syntax.
+    ``@ONLY``
+      Restrict variable replacement to references of the form ``@VAR@``.
+      This is useful for configuring scripts that use ``${VAR}`` syntax.
 
-``NEWLINE_STYLE <style>``
-  Specify the newline style for the output file.  Specify
-  ``UNIX`` or ``LF`` for ``\n`` newlines, or specify
-  ``DOS``, ``WIN32``, or ``CRLF`` for ``\r\n`` newlines.
+    ``NEWLINE_STYLE <style>``
+      Specify the newline style for the output file.  Specify
+      ``UNIX`` or ``LF`` for ``\n`` newlines, or specify
+      ``DOS``, ``WIN32``, or ``CRLF`` for ``\r\n`` newlines.
 
 Filesystem
 ^^^^^^^^^^
 
-.. _GLOB:
-.. _GLOB_RECURSE:
-
-.. code-block:: cmake
-
+.. signature::
   file(GLOB <variable>
        [LIST_DIRECTORIES true|false] [RELATIVE <path>] [CONFIGURE_DEPENDS]
        [<globbing-expressions>...])
@@ -637,633 +643,609 @@
        [LIST_DIRECTORIES true|false] [RELATIVE <path>] [CONFIGURE_DEPENDS]
        [<globbing-expressions>...])
 
-Generate a list of files that match the ``<globbing-expressions>`` and
-store it into the ``<variable>``.  Globbing expressions are similar to
-regular expressions, but much simpler.  If ``RELATIVE`` flag is
-specified, the results will be returned as relative paths to the given
-path.
+  Generate a list of files that match the ``<globbing-expressions>`` and
+  store it into the ``<variable>``.  Globbing expressions are similar to
+  regular expressions, but much simpler.  If ``RELATIVE`` flag is
+  specified, the results will be returned as relative paths to the given
+  path.
 
-.. versionchanged:: 3.6
-  The results will be ordered lexicographically.
+  .. versionchanged:: 3.6
+    The results will be ordered lexicographically.
 
-On Windows and macOS, globbing is case-insensitive even if the underlying
-filesystem is case-sensitive (both filenames and globbing expressions are
-converted to lowercase before matching).  On other platforms, globbing is
-case-sensitive.
+  On Windows and macOS, globbing is case-insensitive even if the underlying
+  filesystem is case-sensitive (both filenames and globbing expressions are
+  converted to lowercase before matching).  On other platforms, globbing is
+  case-sensitive.
 
-.. versionadded:: 3.3
-  By default ``GLOB`` lists directories - directories are omitted in result if
-  ``LIST_DIRECTORIES`` is set to false.
+  .. versionadded:: 3.3
+    By default ``GLOB`` lists directories. Directories are omitted in the
+    result if ``LIST_DIRECTORIES`` is set to false.
 
-.. versionadded:: 3.12
-  If the ``CONFIGURE_DEPENDS`` flag is specified, CMake will add logic
-  to the main build system check target to rerun the flagged ``GLOB`` commands
-  at build time. If any of the outputs change, CMake will regenerate the build
-  system.
+  .. versionadded:: 3.12
+    If the ``CONFIGURE_DEPENDS`` flag is specified, CMake will add logic
+    to the main build system check target to rerun the flagged ``GLOB``
+    commands at build time. If any of the outputs change, CMake will regenerate
+    the build system.
 
-.. note::
-  We do not recommend using GLOB to collect a list of source files from
-  your source tree.  If no CMakeLists.txt file changes when a source is
-  added or removed then the generated build system cannot know when to
-  ask CMake to regenerate.
-  The ``CONFIGURE_DEPENDS`` flag may not work reliably on all generators, or if
-  a new generator is added in the future that cannot support it, projects using
-  it will be stuck. Even if ``CONFIGURE_DEPENDS`` works reliably, there is
-  still a cost to perform the check on every rebuild.
+  .. note::
+    We do not recommend using GLOB to collect a list of source files from
+    your source tree.  If no CMakeLists.txt file changes when a source is
+    added or removed then the generated build system cannot know when to
+    ask CMake to regenerate.
+    The ``CONFIGURE_DEPENDS`` flag may not work reliably on all generators, or
+    if a new generator is added in the future that cannot support it, projects
+    using it will be stuck. Even if ``CONFIGURE_DEPENDS`` works reliably, there
+    is still a cost to perform the check on every rebuild.
 
-Examples of globbing expressions include::
+  Examples of globbing expressions include:
 
-  *.cxx      - match all files with extension cxx
-  *.vt?      - match all files with extension vta,...,vtz
-  f[3-5].txt - match files f3.txt, f4.txt, f5.txt
+  ============== ======================================================
+  ``*.cxx``      match all files with extension ``cxx``
+  ``*.vt?``      match all files with extension ``vta``, ..., ``vtz``
+  ``f[3-5].txt`` match files ``f3.txt``, ``f4.txt``, ``f5.txt``
+  ============== ======================================================
 
-The ``GLOB_RECURSE`` mode will traverse all the subdirectories of the
-matched directory and match the files.  Subdirectories that are symlinks
-are only traversed if ``FOLLOW_SYMLINKS`` is given or policy
-:policy:`CMP0009` is not set to ``NEW``.
+  The ``GLOB_RECURSE`` mode will traverse all the subdirectories of the
+  matched directory and match the files.  Subdirectories that are symlinks
+  are only traversed if ``FOLLOW_SYMLINKS`` is given or policy
+  :policy:`CMP0009` is not set to ``NEW``.
 
-.. versionadded:: 3.3
-  By default ``GLOB_RECURSE`` omits directories from result list - setting
-  ``LIST_DIRECTORIES`` to true adds directories to result list.
-  If ``FOLLOW_SYMLINKS`` is given or policy :policy:`CMP0009` is not set to
-  ``NEW`` then ``LIST_DIRECTORIES`` treats symlinks as directories.
+  .. versionadded:: 3.3
+    By default ``GLOB_RECURSE`` omits directories from result list. Setting
+    ``LIST_DIRECTORIES`` to true adds directories to result list.
+    If ``FOLLOW_SYMLINKS`` is given or policy :policy:`CMP0009` is not set to
+    ``NEW`` then ``LIST_DIRECTORIES`` treats symlinks as directories.
 
-Examples of recursive globbing include::
+  Examples of recursive globbing include:
 
-  /dir/*.py  - match all python files in /dir and subdirectories
+  ============== ======================================================
+  ``/dir/*.py``  match all python files in ``/dir`` and subdirectories
+  ============== ======================================================
 
-.. _MAKE_DIRECTORY:
-
-.. code-block:: cmake
-
+.. signature::
   file(MAKE_DIRECTORY [<directories>...])
 
-Create the given directories and their parents as needed.
+  Create the given directories and their parents as needed.
 
-.. _REMOVE:
-.. _REMOVE_RECURSE:
-
-.. code-block:: cmake
-
+.. signature::
   file(REMOVE [<files>...])
   file(REMOVE_RECURSE [<files>...])
 
-Remove the given files.  The ``REMOVE_RECURSE`` mode will remove the given
-files and directories, also non-empty directories. No error is emitted if a
-given file does not exist.  Relative input paths are evaluated with respect
-to the current source directory.
+  Remove the given files.  The ``REMOVE_RECURSE`` mode will remove the given
+  files and directories, including non-empty directories. No error is emitted
+  if a given file does not exist.  Relative input paths are evaluated with
+  respect to the current source directory.
 
-.. versionchanged:: 3.15
-  Empty input paths are ignored with a warning.  Previous versions of CMake
-  interpreted empty strings as a relative path with respect to the current
-  directory and removed its contents.
+  .. versionchanged:: 3.15
+    Empty input paths are ignored with a warning.  Previous versions of CMake
+    interpreted empty strings as a relative path with respect to the current
+    directory and removed its contents.
 
-.. _RENAME:
+.. signature::
+  file(RENAME <oldname> <newname> [RESULT <result>] [NO_REPLACE])
 
-.. code-block:: cmake
+  Move a file or directory within a filesystem from ``<oldname>`` to
+  ``<newname>``, replacing the destination atomically.
 
-  file(RENAME <oldname> <newname>
-       [RESULT <result>]
-       [NO_REPLACE])
+  The options are:
 
-Move a file or directory within a filesystem from ``<oldname>`` to
-``<newname>``, replacing the destination atomically.
+    ``RESULT <result>``
+      .. versionadded:: 3.21
 
-The options are:
+      Set ``<result>`` variable to ``0`` on success or an error message
+      otherwise. If ``RESULT`` is not specified and the operation fails,
+      an error is emitted.
 
-``RESULT <result>``
-  .. versionadded:: 3.21
+    ``NO_REPLACE``
+      .. versionadded:: 3.21
 
-  Set ``<result>`` variable to ``0`` on success or an error message otherwise.
-  If ``RESULT`` is not specified and the operation fails, an error is emitted.
+      If the ``<newname>`` path already exists, do not replace it.
+      If ``RESULT <result>`` is used, the result variable will be
+      set to ``NO_REPLACE``.  Otherwise, an error is emitted.
 
-``NO_REPLACE``
-  .. versionadded:: 3.21
-
-  If the ``<newname>`` path already exists, do not replace it.
-  If ``RESULT <result>`` is used, the result variable will be
-  set to ``NO_REPLACE``.  Otherwise, an error is emitted.
-
-.. _COPY_FILE:
-
-.. code-block:: cmake
-
+.. signature::
   file(COPY_FILE <oldname> <newname>
        [RESULT <result>]
        [ONLY_IF_DIFFERENT]
        [INPUT_MAY_BE_RECENT])
 
-.. versionadded:: 3.21
+  .. versionadded:: 3.21
 
-Copy a file from ``<oldname>`` to ``<newname>``. Directories are not
-supported. Symlinks are ignored and ``<oldfile>``'s content is read and
-written to ``<newname>`` as a new file.
+  Copy a file from ``<oldname>`` to ``<newname>``. Directories are not
+  supported. Symlinks are ignored and ``<oldfile>``'s content is read and
+  written to ``<newname>`` as a new file.
 
-The options are:
+  The options are:
 
-``RESULT <result>``
-  Set ``<result>`` variable to ``0`` on success or an error message otherwise.
-  If ``RESULT`` is not specified and the operation fails, an error is emitted.
+    ``RESULT <result>``
+      Set ``<result>`` variable to ``0`` on success or an error message
+      otherwise.  If ``RESULT`` is not specified and the operation fails,
+      an error is emitted.
 
-``ONLY_IF_DIFFERENT``
-  If the ``<newname>`` path already exists, do not replace it if the file's
-  contents are already the same as ``<oldname>`` (this avoids updating
-  ``<newname>``'s timestamp).
+    ``ONLY_IF_DIFFERENT``
+      If the ``<newname>`` path already exists, do not replace it if the file's
+      contents are already the same as ``<oldname>`` (this avoids updating
+      ``<newname>``'s timestamp).
 
-``INPUT_MAY_BE_RECENT``
-  .. versionadded:: 3.26
+    ``INPUT_MAY_BE_RECENT``
+      .. versionadded:: 3.26
 
-  Tell CMake that the input file may have been recently created.  This is
-  meaningful only on Windows, where files may be inaccessible for a short
-  time after they are created.  With this option, if permission is denied,
-  CMake will retry reading the input a few times.
+      Tell CMake that the input file may have been recently created.  This is
+      meaningful only on Windows, where files may be inaccessible for a short
+      time after they are created.  With this option, if permission is denied,
+      CMake will retry reading the input a few times.
 
-This sub-command has some similarities to :command:`configure_file` with the
-``COPYONLY`` option.  An important difference is that :command:`configure_file`
-creates a dependency on the source file, so CMake will be re-run if it changes.
-The ``file(COPY_FILE)`` sub-command does not create such a dependency.
+  This sub-command has some similarities to :command:`configure_file`
+  with the ``COPYONLY`` option.  An important difference is that
+  :command:`configure_file` creates a dependency on the source file,
+  so CMake will be re-run if it changes. The ``file(COPY_FILE)``
+  sub-command does not create such a dependency.
 
-See also the ``file(COPY)`` sub-command just below which provides
-further file-copying capabilities.
+  See also the :command:`file(COPY)` sub-command just below which provides
+  further file-copying capabilities.
 
-.. _COPY:
-.. _INSTALL:
+.. signature::
+  file(COPY [...])
+  file(INSTALL [...])
 
-.. code-block:: cmake
+  The ``COPY`` signature copies files, directories, and symlinks to a
+  destination folder.  Relative input paths are evaluated with respect
+  to the current source directory, and a relative destination is
+  evaluated with respect to the current build directory.  Copying
+  preserves input file timestamps, and optimizes out a file if it exists
+  at the destination with the same timestamp.  Copying preserves input
+  permissions unless explicit permissions or ``NO_SOURCE_PERMISSIONS``
+  are given (default is ``USE_SOURCE_PERMISSIONS``).
 
-  file(<COPY|INSTALL> <files>... DESTINATION <dir>
-       [NO_SOURCE_PERMISSIONS | USE_SOURCE_PERMISSIONS]
-       [FILE_PERMISSIONS <permissions>...]
-       [DIRECTORY_PERMISSIONS <permissions>...]
-       [FOLLOW_SYMLINK_CHAIN]
-       [FILES_MATCHING]
-       [[PATTERN <pattern> | REGEX <regex>]
-        [EXCLUDE] [PERMISSIONS <permissions>...]] [...])
+  .. code-block:: cmake
 
-.. note::
+    file(<COPY|INSTALL> <files>... DESTINATION <dir>
+         [NO_SOURCE_PERMISSIONS | USE_SOURCE_PERMISSIONS]
+         [FILE_PERMISSIONS <permissions>...]
+         [DIRECTORY_PERMISSIONS <permissions>...]
+         [FOLLOW_SYMLINK_CHAIN]
+         [FILES_MATCHING]
+         [[PATTERN <pattern> | REGEX <regex>]
+          [EXCLUDE] [PERMISSIONS <permissions>...]] [...])
 
-  For a simple file copying operation, the ``file(COPY_FILE)`` sub-command
-  just above may be easier to use.
+  .. note::
 
-The ``COPY`` signature copies files, directories, and symlinks to a
-destination folder.  Relative input paths are evaluated with respect
-to the current source directory, and a relative destination is
-evaluated with respect to the current build directory.  Copying
-preserves input file timestamps, and optimizes out a file if it exists
-at the destination with the same timestamp.  Copying preserves input
-permissions unless explicit permissions or ``NO_SOURCE_PERMISSIONS``
-are given (default is ``USE_SOURCE_PERMISSIONS``).
+    For a simple file copying operation, the :command:`file(COPY_FILE)`
+    sub-command just above may be easier to use.
 
-.. versionadded:: 3.15
-  If ``FOLLOW_SYMLINK_CHAIN`` is specified, ``COPY`` will recursively resolve
-  the symlinks at the paths given until a real file is found, and install
-  a corresponding symlink in the destination for each symlink encountered. For
-  each symlink that is installed, the resolution is stripped of the directory,
-  leaving only the filename, meaning that the new symlink points to a file in
-  the same directory as the symlink. This feature is useful on some Unix systems,
-  where libraries are installed as a chain of symlinks with version numbers, with
-  less specific versions pointing to more specific versions.
-  ``FOLLOW_SYMLINK_CHAIN`` will install all of these symlinks and the library
-  itself into the destination directory. For example, if you have the following
-  directory structure:
+  .. versionadded:: 3.15
+    If ``FOLLOW_SYMLINK_CHAIN`` is specified, ``COPY`` will recursively resolve
+    the symlinks at the paths given until a real file is found, and install
+    a corresponding symlink in the destination for each symlink encountered.
+    For each symlink that is installed, the resolution is stripped of the
+    directory, leaving only the filename, meaning that the new symlink points
+    to a file in the same directory as the symlink. This feature is useful on
+    some Unix systems, where libraries are installed as a chain of symlinks
+    with version numbers, with less specific versions pointing to more specific
+    versions. ``FOLLOW_SYMLINK_CHAIN`` will install all of these symlinks and
+    the library itself into the destination directory. For example, if you have
+    the following directory structure:
 
-* ``/opt/foo/lib/libfoo.so.1.2.3``
-* ``/opt/foo/lib/libfoo.so.1.2 -> libfoo.so.1.2.3``
-* ``/opt/foo/lib/libfoo.so.1 -> libfoo.so.1.2``
-* ``/opt/foo/lib/libfoo.so -> libfoo.so.1``
+      * ``/opt/foo/lib/libfoo.so.1.2.3``
+      * ``/opt/foo/lib/libfoo.so.1.2 -> libfoo.so.1.2.3``
+      * ``/opt/foo/lib/libfoo.so.1 -> libfoo.so.1.2``
+      * ``/opt/foo/lib/libfoo.so -> libfoo.so.1``
 
-and you do:
+    and you do:
 
-.. code-block:: cmake
+    .. code-block:: cmake
 
-  file(COPY /opt/foo/lib/libfoo.so DESTINATION lib FOLLOW_SYMLINK_CHAIN)
+      file(COPY /opt/foo/lib/libfoo.so DESTINATION lib FOLLOW_SYMLINK_CHAIN)
 
-This will install all of the symlinks and ``libfoo.so.1.2.3`` itself into
-``lib``.
+    This will install all of the symlinks and ``libfoo.so.1.2.3`` itself into
+    ``lib``.
 
-See the :command:`install(DIRECTORY)` command for documentation of
-permissions, ``FILES_MATCHING``, ``PATTERN``, ``REGEX``, and
-``EXCLUDE`` options.  Copying directories preserves the structure
-of their content even if options are used to select a subset of
-files.
+  See the :command:`install(DIRECTORY)` command for documentation of
+  permissions, ``FILES_MATCHING``, ``PATTERN``, ``REGEX``, and
+  ``EXCLUDE`` options.  Copying directories preserves the structure
+  of their content even if options are used to select a subset of
+  files.
 
-The ``INSTALL`` signature differs slightly from ``COPY``: it prints
-status messages, and ``NO_SOURCE_PERMISSIONS`` is default.
+  The ``INSTALL`` signature differs slightly from ``COPY``: it prints
+  status messages, and ``NO_SOURCE_PERMISSIONS`` is default. Installation
+  scripts generated by the :command:`install` command use this signature
+  (with some undocumented options for internal use).
 
-Installation scripts generated by the :command:`install` command
-use this signature (with some undocumented options for internal use).
+  .. versionchanged:: 3.22
 
-.. versionchanged:: 3.22
+    The environment variable :envvar:`CMAKE_INSTALL_MODE` can override the
+    default copying behavior of :command:`file(INSTALL)`.
 
-  The environment variable :envvar:`CMAKE_INSTALL_MODE` can override the
-  default copying behavior of :command:`file(INSTALL)`.
-
-.. _SIZE:
-
-.. code-block:: cmake
-
+.. signature::
   file(SIZE <filename> <variable>)
 
-.. versionadded:: 3.14
+  .. versionadded:: 3.14
 
-Determine the file size of the ``<filename>`` and put the result in
-``<variable>`` variable. Requires that ``<filename>`` is a valid path
-pointing to a file and is readable.
+  Determine the file size of the ``<filename>`` and put the result in
+  ``<variable>`` variable. Requires that ``<filename>`` is a valid path
+  pointing to a file and is readable.
 
-.. _READ_SYMLINK:
-
-.. code-block:: cmake
-
+.. signature::
   file(READ_SYMLINK <linkname> <variable>)
 
-.. versionadded:: 3.14
+  .. versionadded:: 3.14
 
-This subcommand queries the symlink ``<linkname>`` and stores the path it
-points to in the result ``<variable>``.  If ``<linkname>`` does not exist or
-is not a symlink, CMake issues a fatal error.
+  Query the symlink ``<linkname>`` and stores the path it points to
+  in the result ``<variable>``.  If ``<linkname>`` does not exist
+  or is not a symlink, CMake issues a fatal error.
 
-Note that this command returns the raw symlink path and does not resolve
-a relative path.  The following is an example of how to ensure that an
-absolute path is obtained:
+  Note that this command returns the raw symlink path and does not resolve
+  a relative path.  The following is an example of how to ensure that an
+  absolute path is obtained:
 
-.. code-block:: cmake
+  .. code-block:: cmake
 
-  set(linkname "/path/to/foo.sym")
-  file(READ_SYMLINK "${linkname}" result)
-  if(NOT IS_ABSOLUTE "${result}")
-    get_filename_component(dir "${linkname}" DIRECTORY)
-    set(result "${dir}/${result}")
-  endif()
+    set(linkname "/path/to/foo.sym")
+    file(READ_SYMLINK "${linkname}" result)
+    if(NOT IS_ABSOLUTE "${result}")
+      get_filename_component(dir "${linkname}" DIRECTORY)
+      set(result "${dir}/${result}")
+    endif()
 
-.. _CREATE_LINK:
-
-.. code-block:: cmake
-
+.. signature::
   file(CREATE_LINK <original> <linkname>
        [RESULT <result>] [COPY_ON_ERROR] [SYMBOLIC])
 
-.. versionadded:: 3.14
+  .. versionadded:: 3.14
 
-Create a link ``<linkname>`` that points to ``<original>``.
-It will be a hard link by default, but providing the ``SYMBOLIC`` option
-results in a symbolic link instead.  Hard links require that ``original``
-exists and is a file, not a directory.  If ``<linkname>`` already exists,
-it will be overwritten.
+  Create a link ``<linkname>`` that points to ``<original>``.
+  It will be a hard link by default, but providing the ``SYMBOLIC`` option
+  results in a symbolic link instead.  Hard links require that ``original``
+  exists and is a file, not a directory.  If ``<linkname>`` already exists,
+  it will be overwritten.
 
-The ``<result>`` variable, if specified, receives the status of the operation.
-It is set to ``0`` upon success or an error message otherwise.  If ``RESULT``
-is not specified and the operation fails, a fatal error is emitted.
+  The ``<result>`` variable, if specified, receives the status of the
+  operation.  It is set to ``0`` upon success or an error message otherwise.
+  If ``RESULT`` is not specified and the operation fails, a fatal error is
+  emitted.
 
-Specifying ``COPY_ON_ERROR`` enables copying the file as a fallback if
-creating the link fails.  It can be useful for handling situations such as
-``<original>`` and ``<linkname>`` being on different drives or mount points,
-which would make them unable to support a hard link.
+  Specifying ``COPY_ON_ERROR`` enables copying the file as a fallback if
+  creating the link fails.  It can be useful for handling situations such as
+  ``<original>`` and ``<linkname>`` being on different drives or mount points,
+  which would make them unable to support a hard link.
 
-.. _CHMOD:
-
-.. code-block:: cmake
-
+.. signature::
   file(CHMOD <files>... <directories>...
-      [PERMISSIONS <permissions>...]
-      [FILE_PERMISSIONS <permissions>...]
-      [DIRECTORY_PERMISSIONS <permissions>...])
+       [PERMISSIONS <permissions>...]
+       [FILE_PERMISSIONS <permissions>...]
+       [DIRECTORY_PERMISSIONS <permissions>...])
 
-.. versionadded:: 3.19
+  .. versionadded:: 3.19
 
-Set the permissions for the ``<files>...`` and ``<directories>...`` specified.
-Valid permissions are  ``OWNER_READ``, ``OWNER_WRITE``, ``OWNER_EXECUTE``,
-``GROUP_READ``, ``GROUP_WRITE``, ``GROUP_EXECUTE``, ``WORLD_READ``,
-``WORLD_WRITE``, ``WORLD_EXECUTE``, ``SETUID``, ``SETGID``.
+  Set the permissions for the ``<files>...`` and ``<directories>...``
+  specified. Valid permissions are  ``OWNER_READ``, ``OWNER_WRITE``,
+  ``OWNER_EXECUTE``, ``GROUP_READ``, ``GROUP_WRITE``, ``GROUP_EXECUTE``,
+  ``WORLD_READ``, ``WORLD_WRITE``, ``WORLD_EXECUTE``, ``SETUID``, ``SETGID``.
 
-Valid combination of keywords are:
+  Valid combination of keywords are:
 
-``PERMISSIONS``
-  All items are changed.
+    ``PERMISSIONS``
+      All items are changed.
 
-``FILE_PERMISSIONS``
-  Only files are changed.
+    ``FILE_PERMISSIONS``
+      Only files are changed.
 
-``DIRECTORY_PERMISSIONS``
-  Only directories are changed.
+    ``DIRECTORY_PERMISSIONS``
+      Only directories are changed.
 
-``PERMISSIONS`` and ``FILE_PERMISSIONS``
-  ``FILE_PERMISSIONS`` overrides ``PERMISSIONS`` for files.
+    ``PERMISSIONS`` and ``FILE_PERMISSIONS``
+      ``FILE_PERMISSIONS`` overrides ``PERMISSIONS`` for files.
 
-``PERMISSIONS`` and ``DIRECTORY_PERMISSIONS``
-  ``DIRECTORY_PERMISSIONS`` overrides ``PERMISSIONS`` for directories.
+    ``PERMISSIONS`` and ``DIRECTORY_PERMISSIONS``
+      ``DIRECTORY_PERMISSIONS`` overrides ``PERMISSIONS`` for directories.
 
-``FILE_PERMISSIONS`` and ``DIRECTORY_PERMISSIONS``
-  Use ``FILE_PERMISSIONS`` for files and ``DIRECTORY_PERMISSIONS`` for
-  directories.
+    ``FILE_PERMISSIONS`` and ``DIRECTORY_PERMISSIONS``
+      Use ``FILE_PERMISSIONS`` for files and ``DIRECTORY_PERMISSIONS`` for
+      directories.
 
-
-.. _CHMOD_RECURSE:
-
-.. code-block:: cmake
-
+.. signature::
   file(CHMOD_RECURSE <files>... <directories>...
        [PERMISSIONS <permissions>...]
        [FILE_PERMISSIONS <permissions>...]
        [DIRECTORY_PERMISSIONS <permissions>...])
 
-.. versionadded:: 3.19
+  .. versionadded:: 3.19
 
-Same as `CHMOD`_, but change the permissions of files and directories present in
-the ``<directories>...`` recursively.
+  Same as :cref:`CHMOD`, but change the permissions of files and directories
+  present in the ``<directories>...`` recursively.
+
 
 Path Conversion
 ^^^^^^^^^^^^^^^
 
-.. _REAL_PATH:
-
-.. code-block:: cmake
-
+.. signature::
   file(REAL_PATH <path> <out-var> [BASE_DIRECTORY <dir>] [EXPAND_TILDE])
 
-.. versionadded:: 3.19
+  .. versionadded:: 3.19
 
-Compute the absolute path to an existing file or directory with symlinks
-resolved.
+  Compute the absolute path to an existing file or directory with symlinks
+  resolved.  The options are:
 
-``BASE_DIRECTORY <dir>``
-  If the provided ``<path>`` is a relative path, it is evaluated relative to the
-  given base directory ``<dir>``. If no base directory is provided, the default
-  base directory will be :variable:`CMAKE_CURRENT_SOURCE_DIR`.
+    ``BASE_DIRECTORY <dir>``
+      If the provided ``<path>`` is a relative path, it is evaluated relative
+      to the given base directory ``<dir>``. If no base directory is provided,
+      the default base directory will be :variable:`CMAKE_CURRENT_SOURCE_DIR`.
 
-``EXPAND_TILDE``
-  .. versionadded:: 3.21
+    ``EXPAND_TILDE``
+      .. versionadded:: 3.21
 
-  If the ``<path>`` is ``~`` or starts with ``~/``, the ``~`` is replaced by
-  the user's home directory.  The path to the home directory is obtained from
-  environment variables.  On Windows, the ``USERPROFILE`` environment variable
-  is used, falling back to the ``HOME`` environment variable if ``USERPROFILE``
-  is not defined.  On all other platforms, only ``HOME`` is used.
+      If the ``<path>`` is ``~`` or starts with ``~/``, the ``~`` is replaced
+      by the user's home directory.  The path to the home directory is obtained
+      from environment variables.  On Windows, the ``USERPROFILE`` environment
+      variable is used, falling back to the ``HOME`` environment variable
+      if ``USERPROFILE`` is not defined.  On all other platforms, only ``HOME``
+      is used.
 
-.. _RELATIVE_PATH:
-
-.. code-block:: cmake
-
+.. signature::
   file(RELATIVE_PATH <variable> <directory> <file>)
 
-Compute the relative path from a ``<directory>`` to a ``<file>`` and
-store it in the ``<variable>``.
+  Compute the relative path from a ``<directory>`` to a ``<file>`` and
+  store it in the ``<variable>``.
 
-.. _TO_CMAKE_PATH:
-.. _TO_NATIVE_PATH:
-
-.. code-block:: cmake
-
+.. signature::
   file(TO_CMAKE_PATH "<path>" <variable>)
   file(TO_NATIVE_PATH "<path>" <variable>)
 
-The ``TO_CMAKE_PATH`` mode converts a native ``<path>`` into a cmake-style
-path with forward-slashes (``/``).  The input can be a single path or a
-system search path like ``$ENV{PATH}``.  A search path will be converted
-to a cmake-style list separated by ``;`` characters.
+  The ``TO_CMAKE_PATH`` mode converts a native ``<path>`` into a cmake-style
+  path with forward-slashes (``/``).  The input can be a single path or a
+  system search path like ``$ENV{PATH}``.  A search path will be converted
+  to a cmake-style list separated by ``;`` characters.
 
-The ``TO_NATIVE_PATH`` mode converts a cmake-style ``<path>`` into a native
-path with platform-specific slashes (``\`` on Windows hosts and ``/``
-elsewhere).
+  The ``TO_NATIVE_PATH`` mode converts a cmake-style ``<path>`` into a native
+  path with platform-specific slashes (``\`` on Windows hosts and ``/``
+  elsewhere).
 
-Always use double quotes around the ``<path>`` to be sure it is treated
-as a single argument to this command.
+  Always use double quotes around the ``<path>`` to be sure it is treated
+  as a single argument to this command.
 
 Transfer
 ^^^^^^^^
 
-.. _DOWNLOAD:
-.. _UPLOAD:
-
-.. code-block:: cmake
-
+.. signature::
   file(DOWNLOAD <url> [<file>] [<options>...])
-  file(UPLOAD   <file> <url> [<options>...])
+  file(UPLOAD <file> <url> [<options>...])
 
-The ``DOWNLOAD`` subcommand downloads the given ``<url>`` to a local ``<file>``.
-The ``UPLOAD`` mode uploads a local ``<file>`` to a given ``<url>``.
+  The ``DOWNLOAD`` subcommand downloads the given ``<url>`` to a local
+  ``<file>``.  The ``UPLOAD`` mode uploads a local ``<file>`` to a given
+  ``<url>``.
 
-.. versionadded:: 3.19
-  If ``<file>`` is not specified for ``file(DOWNLOAD)``, the file is not saved.
-  This can be useful if you want to know if a file can be downloaded (for example,
-  to check that it exists) without actually saving it anywhere.
+  .. versionadded:: 3.19
+    If ``<file>`` is not specified for ``file(DOWNLOAD)``, the file is not
+    saved. This can be useful if you want to know if a file can be downloaded
+    (for example, to check that it exists) without actually saving it anywhere.
 
-Options to both ``DOWNLOAD`` and ``UPLOAD`` are:
+  Options to both ``DOWNLOAD`` and ``UPLOAD`` are:
 
-``INACTIVITY_TIMEOUT <seconds>``
-  Terminate the operation after a period of inactivity.
+    ``INACTIVITY_TIMEOUT <seconds>``
+      Terminate the operation after a period of inactivity.
 
-``LOG <variable>``
-  Store a human-readable log of the operation in a variable.
+    ``LOG <variable>``
+      Store a human-readable log of the operation in a variable.
 
-``SHOW_PROGRESS``
-  Print progress information as status messages until the operation is
-  complete.
+    ``SHOW_PROGRESS``
+      Print progress information as status messages until the operation is
+      complete.
 
-``STATUS <variable>``
-  Store the resulting status of the operation in a variable.
-  The status is a ``;`` separated list of length 2.
-  The first element is the numeric return value for the operation,
-  and the second element is a string value for the error.
-  A ``0`` numeric error means no error in the operation.
+    ``STATUS <variable>``
+      Store the resulting status of the operation in a variable.
+      The status is a ``;`` separated list of length 2.
+      The first element is the numeric return value for the operation,
+      and the second element is a string value for the error.
+      A ``0`` numeric error means no error in the operation.
 
-``TIMEOUT <seconds>``
-  Terminate the operation after a given total time has elapsed.
+    ``TIMEOUT <seconds>``
+      Terminate the operation after a given total time has elapsed.
 
-``USERPWD <username>:<password>``
-  .. versionadded:: 3.7
+    ``USERPWD <username>:<password>``
+      .. versionadded:: 3.7
 
-  Set username and password for operation.
+      Set username and password for operation.
 
-``HTTPHEADER <HTTP-header>``
-  .. versionadded:: 3.7
+    ``HTTPHEADER <HTTP-header>``
+      .. versionadded:: 3.7
 
-  HTTP header for operation. Suboption can be repeated several times.
+      HTTP header for ``DOWNLOAD`` and ``UPLOAD`` operations. ``HTTPHEADER``
+      can be repeated for multiple options:
 
-``NETRC <level>``
-  .. versionadded:: 3.11
+      .. code-block:: cmake
 
-  Specify whether the .netrc file is to be used for operation.  If this
-  option is not specified, the value of the :variable:`CMAKE_NETRC` variable
-  will be used instead.
-  Valid levels are:
+        file(DOWNLOAD <url>
+             HTTPHEADER "Authorization: Bearer <auth-token>"
+             HTTPHEADER "UserAgent: Mozilla/5.0")
 
-  ``IGNORED``
-    The .netrc file is ignored.
-    This is the default.
-  ``OPTIONAL``
-    The .netrc file is optional, and information in the URL is preferred.
-    The file will be scanned to find which ever information is not specified
-    in the URL.
-  ``REQUIRED``
-    The .netrc file is required, and information in the URL is ignored.
+    ``NETRC <level>``
+      .. versionadded:: 3.11
 
-``NETRC_FILE <file>``
-  .. versionadded:: 3.11
+      Specify whether the .netrc file is to be used for operation.  If this
+      option is not specified, the value of the :variable:`CMAKE_NETRC`
+      variable will be used instead.
 
-  Specify an alternative .netrc file to the one in your home directory,
-  if the ``NETRC`` level is ``OPTIONAL`` or ``REQUIRED``. If this option
-  is not specified, the value of the :variable:`CMAKE_NETRC_FILE` variable will
-  be used instead.
+      Valid levels are:
 
-``TLS_VERIFY <ON|OFF>``
-  Specify whether to verify the server certificate for ``https://`` URLs.
-  The default is to *not* verify. If this option is not specified, the value
-  of the :variable:`CMAKE_TLS_VERIFY` variable will be used instead.
+        ``IGNORED``
+          The .netrc file is ignored.
+          This is the default.
 
-  .. versionadded:: 3.18
-    Added support to ``file(UPLOAD)``.
+        ``OPTIONAL``
+          The .netrc file is optional, and information in the URL is preferred.
+          The file will be scanned to find which ever information is not
+          specified in the URL.
 
-``TLS_CAINFO <file>``
-  Specify a custom Certificate Authority file for ``https://`` URLs. If this
-  option is not specified, the value of the :variable:`CMAKE_TLS_CAINFO`
-  variable will be used instead.
+        ``REQUIRED``
+          The .netrc file is required, and information in the URL is ignored.
 
-  .. versionadded:: 3.18
-    Added support to ``file(UPLOAD)``.
+    ``NETRC_FILE <file>``
+      .. versionadded:: 3.11
 
-For ``https://`` URLs CMake must be built with OpenSSL support.  ``TLS/SSL``
-certificates are not checked by default.  Set ``TLS_VERIFY`` to ``ON`` to
-check certificates.
+      Specify an alternative .netrc file to the one in your home directory,
+      if the ``NETRC`` level is ``OPTIONAL`` or ``REQUIRED``. If this option
+      is not specified, the value of the :variable:`CMAKE_NETRC_FILE` variable
+      will be used instead.
 
-Additional options to ``DOWNLOAD`` are:
+    ``TLS_VERIFY <ON|OFF>``
+      Specify whether to verify the server certificate for ``https://`` URLs.
+      The default is to *not* verify. If this option is not specified, the
+      value of the :variable:`CMAKE_TLS_VERIFY` variable will be used instead.
 
-``EXPECTED_HASH ALGO=<value>``
+      .. versionadded:: 3.18
+        Added support to ``file(UPLOAD)``.
 
-  Verify that the downloaded content hash matches the expected value, where
-  ``ALGO`` is one of the algorithms supported by ``file(<HASH>)``.
-  If the file already exists and matches the hash, the download is skipped.
-  If the file already exists and does not match the hash, the file is
-  downloaded again. If after download the file does not match the hash, the
-  operation fails with an error. It is an error to specify this option if
-  ``DOWNLOAD`` is not given a ``<file>``.
+    ``TLS_CAINFO <file>``
+      Specify a custom Certificate Authority file for ``https://`` URLs.
+      If this option is not specified, the value of the
+      :variable:`CMAKE_TLS_CAINFO` variable will be used instead.
 
-``EXPECTED_MD5 <value>``
-  Historical short-hand for ``EXPECTED_HASH MD5=<value>``. It is an error to
-  specify this if ``DOWNLOAD`` is not given a ``<file>``.
+      .. versionadded:: 3.18
+        Added support to ``file(UPLOAD)``.
 
-``RANGE_START <value>``
-  .. versionadded:: 3.24
+  For ``https://`` URLs CMake must be built with OpenSSL support.  ``TLS/SSL``
+  certificates are not checked by default.  Set ``TLS_VERIFY`` to ``ON`` to
+  check certificates.
 
-  Offset of the start of the range in file in bytes. Could be omitted to
-  download up to the specified ``RANGE_END``.
+  Additional options to ``DOWNLOAD`` are:
 
-``RANGE_END <value>``
-  .. versionadded:: 3.24
+    ``EXPECTED_HASH <algorithm>=<value>``
+      Verify that the downloaded content hash matches the expected value, where
+      ``<algorithm>`` is one of the algorithms supported by :cref:`<HASH>`.
+      If the file already exists and matches the hash, the download is skipped.
+      If the file already exists and does not match the hash, the file is
+      downloaded again. If after download the file does not match the hash, the
+      operation fails with an error. It is an error to specify this option if
+      ``DOWNLOAD`` is not given a ``<file>``.
 
-  Offset of the end of the range in file in bytes. Could be omitted to
-  download everything from the specified ``RANGE_START`` to the end of file.
+    ``EXPECTED_MD5 <value>``
+      Historical short-hand for ``EXPECTED_HASH MD5=<value>``. It is an error
+      to specify this if ``DOWNLOAD`` is not given a ``<file>``.
+
+    ``RANGE_START <value>``
+      .. versionadded:: 3.24
+
+      Offset of the start of the range in file in bytes. Could be omitted to
+      download up to the specified ``RANGE_END``.
+
+    ``RANGE_END <value>``
+      .. versionadded:: 3.24
+
+      Offset of the end of the range in file in bytes. Could be omitted to
+      download everything from the specified ``RANGE_START`` to the end of
+      file.
 
 Locking
 ^^^^^^^
 
-.. _LOCK:
-
-.. code-block:: cmake
-
+.. signature::
   file(LOCK <path> [DIRECTORY] [RELEASE]
        [GUARD <FUNCTION|FILE|PROCESS>]
        [RESULT_VARIABLE <variable>]
        [TIMEOUT <seconds>])
 
-.. versionadded:: 3.2
+  .. versionadded:: 3.2
 
-Lock a file specified by ``<path>`` if no ``DIRECTORY`` option present and file
-``<path>/cmake.lock`` otherwise. File will be locked for scope defined by
-``GUARD`` option (default value is ``PROCESS``). ``RELEASE`` option can be used
-to unlock file explicitly. If option ``TIMEOUT`` is not specified CMake will
-wait until lock succeed or until fatal error occurs. If ``TIMEOUT`` is set to
-``0`` lock will be tried once and result will be reported immediately. If
-``TIMEOUT`` is not ``0`` CMake will try to lock file for the period specified
-by ``<seconds>`` value. Any errors will be interpreted as fatal if there is no
-``RESULT_VARIABLE`` option. Otherwise result will be stored in ``<variable>``
-and will be ``0`` on success or error message on failure.
+  Lock a file specified by ``<path>`` if no ``DIRECTORY`` option present and
+  file ``<path>/cmake.lock`` otherwise.  The file will be locked for the scope
+  defined by the ``GUARD`` option (default value is ``PROCESS``).  The
+  ``RELEASE`` option can be used to unlock the file explicitly.  If the
+  ``TIMEOUT`` option is not specified, CMake will wait until the lock succeeds
+  or until a fatal error occurs.  If ``TIMEOUT`` is set to ``0``, locking will
+  be tried once and the result will be reported immediately.  If ``TIMEOUT``
+  is not ``0``, CMake will try to lock the file for the period specified by
+  the ``TIMEOUT <seconds>`` value.  Any errors will be interpreted as fatal if
+  there is no ``RESULT_VARIABLE`` option.  Otherwise, the result will be stored
+  in ``<variable>`` and will be ``0`` on success or an error message on
+  failure.
 
-Note that lock is advisory - there is no guarantee that other processes will
-respect this lock, i.e. lock synchronize two or more CMake instances sharing
-some modifiable resources. Similar logic applied to ``DIRECTORY`` option -
-locking parent directory doesn't prevent other ``LOCK`` commands to lock any
-child directory or file.
+  Note that lock is advisory; there is no guarantee that other processes will
+  respect this lock, i.e. lock synchronize two or more CMake instances sharing
+  some modifiable resources. Similar logic applies to the ``DIRECTORY`` option;
+  locking a parent directory doesn't prevent other ``LOCK`` commands from
+  locking any child directory or file.
 
-Trying to lock file twice is not allowed.  Any intermediate directories and
-file itself will be created if they not exist.  ``GUARD`` and ``TIMEOUT``
-options ignored on ``RELEASE`` operation.
+  Trying to lock the same file twice is not allowed.  Any intermediate
+  directories and the file itself will be created if they not exist.  The
+  ``GUARD`` and ``TIMEOUT`` options are ignored on the ``RELEASE`` operation.
 
 Archiving
 ^^^^^^^^^
 
-.. _ARCHIVE_CREATE:
-
-.. code-block:: cmake
-
+.. signature::
   file(ARCHIVE_CREATE OUTPUT <archive>
     PATHS <paths>...
     [FORMAT <format>]
-    [COMPRESSION <compression> [COMPRESSION_LEVEL <compression-level>]]
+    [COMPRESSION <compression>
+     [COMPRESSION_LEVEL <compression-level>]]
     [MTIME <mtime>]
     [VERBOSE])
+  :target: ARCHIVE_CREATE
+  :break: verbatim
 
-.. versionadded:: 3.18
+  .. versionadded:: 3.18
 
-Creates the specified ``<archive>`` file with the files and directories
-listed in ``<paths>``.  Note that ``<paths>`` must list actual files or
-directories, wildcards are not supported.
+  Creates the specified ``<archive>`` file with the files and directories
+  listed in ``<paths>``.  Note that ``<paths>`` must list actual files or
+  directories; wildcards are not supported.
 
-Use the ``FORMAT`` option to specify the archive format.  Supported values
-for ``<format>`` are ``7zip``, ``gnutar``, ``pax``, ``paxr``, ``raw`` and
-``zip``.  If ``FORMAT`` is not given, the default format is ``paxr``.
+  Use the ``FORMAT`` option to specify the archive format.  Supported values
+  for ``<format>`` are ``7zip``, ``gnutar``, ``pax``, ``paxr``, ``raw`` and
+  ``zip``.  If ``FORMAT`` is not given, the default format is ``paxr``.
 
-Some archive formats allow the type of compression to be specified.
-The ``7zip`` and ``zip`` archive formats already imply a specific type of
-compression.  The other formats use no compression by default, but can be
-directed to do so with the ``COMPRESSION`` option.  Valid values for
-``<compression>`` are ``None``, ``BZip2``, ``GZip``, ``XZ``, and ``Zstd``.
+  Some archive formats allow the type of compression to be specified.
+  The ``7zip`` and ``zip`` archive formats already imply a specific type of
+  compression.  The other formats use no compression by default, but can be
+  directed to do so with the ``COMPRESSION`` option.  Valid values for
+  ``<compression>`` are ``None``, ``BZip2``, ``GZip``, ``XZ``, and ``Zstd``.
 
-.. versionadded:: 3.19
-  The compression level can be specified with the ``COMPRESSION_LEVEL`` option.
-  The ``<compression-level>`` should be between 0-9, with the default being 0.
-  The ``COMPRESSION`` option must be present when ``COMPRESSION_LEVEL`` is given.
+  .. versionadded:: 3.19
+    The compression level can be specified with the ``COMPRESSION_LEVEL``
+    option.  The ``<compression-level>`` should be between 0-9, with the
+    default being 0.  The ``COMPRESSION`` option must be present when
+    ``COMPRESSION_LEVEL`` is given.
 
-.. versionadded:: 3.26
-  The ``<compression-level>`` of the ``Zstd`` algorithm can be set between 0-19.
+  .. versionadded:: 3.26
+    The ``<compression-level>`` of the ``Zstd`` algorithm can be set
+    between 0-19.
 
-.. note::
-  With ``FORMAT`` set to ``raw`` only one file will be compressed with the
-  compression type specified by ``COMPRESSION``.
+  .. note::
+    With ``FORMAT`` set to ``raw``, only one file will be compressed with the
+    compression type specified by ``COMPRESSION``.
 
-The ``VERBOSE`` option enables verbose output for the archive operation.
+  The ``VERBOSE`` option enables verbose output for the archive operation.
 
-To specify the modification time recorded in tarball entries, use
-the ``MTIME`` option.
+  To specify the modification time recorded in tarball entries, use
+  the ``MTIME`` option.
 
-.. _ARCHIVE_EXTRACT:
-
-.. code-block:: cmake
-
-  file(ARCHIVE_EXTRACT INPUT <archive>
+.. signature::
+  file(ARCHIVE_EXTRACT
+    INPUT <archive>
     [DESTINATION <dir>]
     [PATTERNS <patterns>...]
     [LIST_ONLY]
     [VERBOSE]
     [TOUCH])
+  :target: ARCHIVE_EXTRACT
 
-.. versionadded:: 3.18
+  .. versionadded:: 3.18
 
-Extracts or lists the content of the specified ``<archive>``.
+  Extracts or lists the content of the specified ``<archive>``.
 
-The directory where the content of the archive will be extracted to can
-be specified using the ``DESTINATION`` option.  If the directory does not
-exist, it will be created.  If ``DESTINATION`` is not given, the current
-binary directory will be used.
+  The directory where the content of the archive will be extracted to can
+  be specified using the ``DESTINATION`` option.  If the directory does not
+  exist, it will be created.  If ``DESTINATION`` is not given, the current
+  binary directory will be used.
 
-If required, you may select which files and directories to list or extract
-from the archive using the specified ``<patterns>``.  Wildcards are supported.
-If the ``PATTERNS`` option is not given, the entire archive will be listed or
-extracted.
+  If required, you may select which files and directories to list or extract
+  from the archive using the specified ``<patterns>``.  Wildcards are
+  supported.  If the ``PATTERNS`` option is not given, the entire archive will
+  be listed or extracted.
 
-``LIST_ONLY`` will list the files in the archive rather than extract them.
+  ``LIST_ONLY`` will list the files in the archive rather than extract them.
 
-.. versionadded:: 3.24
-  The ``TOUCH`` option gives extracted files a current local
-  timestamp instead of extracting file timestamps from the archive.
+  .. versionadded:: 3.24
+    The ``TOUCH`` option gives extracted files a current local
+    timestamp instead of extracting file timestamps from the archive.
 
-With ``VERBOSE``, the command will produce verbose output.
+  With ``VERBOSE``, the command will produce verbose output.
diff --git a/Help/command/find_file.rst b/Help/command/find_file.rst
index c5c4014..9f89f52 100644
--- a/Help/command/find_file.rst
+++ b/Help/command/find_file.rst
@@ -19,6 +19,13 @@
 .. |CMAKE_XXX_PATH| replace:: :variable:`CMAKE_INCLUDE_PATH`
 .. |CMAKE_XXX_MAC_PATH| replace:: :variable:`CMAKE_FRAMEWORK_PATH`
 
+.. |ENV_CMAKE_PREFIX_PATH_XXX| replace::
+   ``<prefix>/include/<arch>`` if :variable:`CMAKE_LIBRARY_ARCHITECTURE` is set,
+   and |ENV_CMAKE_PREFIX_PATH_XXX_SUBDIR|
+.. |ENV_CMAKE_XXX_PATH| replace:: :envvar:`CMAKE_INCLUDE_PATH`
+.. |ENV_CMAKE_XXX_MAC_PATH| replace:: :envvar:`CMAKE_FRAMEWORK_PATH`
+
+
 .. |SYSTEM_ENVIRONMENT_PATH_XXX| replace:: The directories in ``INCLUDE``
    and ``PATH``.
 .. |SYSTEM_ENVIRONMENT_PATH_WINDOWS_XXX| replace:: On Windows hosts:
diff --git a/Help/command/find_library.rst b/Help/command/find_library.rst
index c237e7f..99e36a4 100644
--- a/Help/command/find_library.rst
+++ b/Help/command/find_library.rst
@@ -19,6 +19,12 @@
 .. |CMAKE_XXX_PATH| replace:: :variable:`CMAKE_LIBRARY_PATH`
 .. |CMAKE_XXX_MAC_PATH| replace:: :variable:`CMAKE_FRAMEWORK_PATH`
 
+.. |ENV_CMAKE_PREFIX_PATH_XXX| replace::
+   ``<prefix>/lib/<arch>`` if :variable:`CMAKE_LIBRARY_ARCHITECTURE` is set,
+   and |ENV_CMAKE_PREFIX_PATH_XXX_SUBDIR|
+.. |ENV_CMAKE_XXX_PATH| replace:: :envvar:`CMAKE_LIBRARY_PATH`
+.. |ENV_CMAKE_XXX_MAC_PATH| replace:: :envvar:`CMAKE_FRAMEWORK_PATH`
+
 .. |SYSTEM_ENVIRONMENT_PATH_XXX| replace:: The directories in ``LIB``
    and ``PATH``.
 .. |SYSTEM_ENVIRONMENT_PATH_WINDOWS_XXX| replace:: On Windows hosts:
diff --git a/Help/command/find_package.rst b/Help/command/find_package.rst
index de4cb88..b0b6fe1 100644
--- a/Help/command/find_package.rst
+++ b/Help/command/find_package.rst
@@ -367,17 +367,37 @@
 steps.  If ``NO_DEFAULT_PATH`` is specified all ``NO_*`` options are
 enabled.
 
-1. .. versionadded:: 3.12
-    Search paths specified in the :variable:`<PackageName>_ROOT` CMake
-    variable and the :envvar:`<PackageName>_ROOT` environment variable,
-    where ``<PackageName>`` is the package to be found
-    (the case-preserved first argument to ``find_package``).
-    The package root variables are maintained as a stack so if
-    called from within a find module, root paths from the parent's find
-    module will also be searched after paths for the current package.
-    This can be skipped if ``NO_PACKAGE_ROOT_PATH`` is passed or by setting
-    the :variable:`CMAKE_FIND_USE_PACKAGE_ROOT_PATH` to ``FALSE``.
-    See policy :policy:`CMP0074`.
+1. Search prefixes unique to the current ``<PackageName>`` being found.
+   See policy :policy:`CMP0074`.
+
+   .. versionadded:: 3.12
+
+   Specifically, search prefixes specified by the following variables,
+   in order:
+
+   a. :variable:`<PackageName>_ROOT` CMake variable,
+      where ``<PackageName>`` is the case-preserved package name.
+
+   b. :variable:`<PACKAGENAME>_ROOT` CMake variable,
+      where ``<PACKAGENAME>`` is the upper-cased package name.
+      See policy :policy:`CMP0144`.
+
+      .. versionadded:: 3.27
+
+   c. :envvar:`<PackageName>_ROOT` environment variable,
+      where ``<PackageName>`` is the case-preserved package name.
+
+   d. :envvar:`<PACKAGENAME>_ROOT` environment variable,
+      where ``<PACKAGENAME>`` is the upper-cased package name.
+      See policy :policy:`CMP0144`.
+
+      .. versionadded:: 3.27
+
+   The package root variables are maintained as a stack so if
+   called from within a find module, root paths from the parent's find
+   module will also be searched after paths for the current package.
+   This can be skipped if ``NO_PACKAGE_ROOT_PATH`` is passed or by setting
+   the :variable:`CMAKE_FIND_USE_PACKAGE_ROOT_PATH` to ``FALSE``.
 
 2. Search paths specified in cmake-specific cache variables.  These
    are intended to be used on the command line with a :option:`-DVAR=VALUE <cmake -D>`.
@@ -398,8 +418,8 @@
 
    * ``<PackageName>_DIR``
    * :envvar:`CMAKE_PREFIX_PATH`
-   * ``CMAKE_FRAMEWORK_PATH``
-   * ``CMAKE_APPBUNDLE_PATH``
+   * :envvar:`CMAKE_FRAMEWORK_PATH`
+   * :envvar:`CMAKE_APPBUNDLE_PATH`
 
 4. Search paths specified by the ``HINTS`` option.  These should be paths
    computed by system introspection, such as a hint provided by the
diff --git a/Help/command/find_path.rst b/Help/command/find_path.rst
index 1d7648d..f0522f6 100644
--- a/Help/command/find_path.rst
+++ b/Help/command/find_path.rst
@@ -19,6 +19,12 @@
 .. |CMAKE_XXX_PATH| replace:: :variable:`CMAKE_INCLUDE_PATH`
 .. |CMAKE_XXX_MAC_PATH| replace:: :variable:`CMAKE_FRAMEWORK_PATH`
 
+.. |ENV_CMAKE_PREFIX_PATH_XXX| replace::
+   ``<prefix>/include/<arch>`` if :variable:`CMAKE_LIBRARY_ARCHITECTURE` is set,
+   and |ENV_CMAKE_PREFIX_PATH_XXX_SUBDIR|
+.. |ENV_CMAKE_XXX_PATH| replace:: :envvar:`CMAKE_INCLUDE_PATH`
+.. |ENV_CMAKE_XXX_MAC_PATH| replace:: :envvar:`CMAKE_FRAMEWORK_PATH`
+
 .. |SYSTEM_ENVIRONMENT_PATH_XXX| replace:: The directories in ``INCLUDE``
    and ``PATH``.
 .. |SYSTEM_ENVIRONMENT_PATH_WINDOWS_XXX| replace:: On Windows hosts:
diff --git a/Help/command/find_program.rst b/Help/command/find_program.rst
index f4149be..fe95a9a 100644
--- a/Help/command/find_program.rst
+++ b/Help/command/find_program.rst
@@ -17,6 +17,11 @@
 .. |CMAKE_XXX_PATH| replace:: :variable:`CMAKE_PROGRAM_PATH`
 .. |CMAKE_XXX_MAC_PATH| replace:: :variable:`CMAKE_APPBUNDLE_PATH`
 
+.. |ENV_CMAKE_PREFIX_PATH_XXX| replace::
+   |ENV_CMAKE_PREFIX_PATH_XXX_SUBDIR|
+.. |ENV_CMAKE_XXX_PATH| replace:: :envvar:`CMAKE_PROGRAM_PATH`
+.. |ENV_CMAKE_XXX_MAC_PATH| replace:: :envvar:`CMAKE_APPBUNDLE_PATH`
+
 .. |SYSTEM_ENVIRONMENT_PATH_XXX| replace:: The directories in ``PATH`` itself.
 .. |SYSTEM_ENVIRONMENT_PATH_WINDOWS_XXX| replace:: On Windows hosts no extra search paths are included
 
diff --git a/Help/command/get_filename_component.rst b/Help/command/get_filename_component.rst
index 899ede6..7d74a33 100644
--- a/Help/command/get_filename_component.rst
+++ b/Help/command/get_filename_component.rst
@@ -4,9 +4,9 @@
 Get a specific component of a full filename.
 
 .. versionchanged:: 3.20
-  This command has been superseded by :command:`cmake_path` command, except
-  ``REALPATH`` now offered by :ref:`file(REAL_PATH)<REAL_PATH>` command and
-  ``PROGRAM`` now available in :command:`separate_arguments(PROGRAM)` command.
+  This command has been superseded by the :command:`cmake_path` command, except
+  for ``REALPATH``, which is now offered by :command:`file(REAL_PATH)`, and
+  ``PROGRAM``, now available in :command:`separate_arguments(PROGRAM)`.
 
 .. versionchanged:: 3.24
   The undocumented feature offering the capability to query the ``Windows``
diff --git a/Help/command/if.rst b/Help/command/if.rst
index 684c113..be855e1 100644
--- a/Help/command/if.rst
+++ b/Help/command/if.rst
@@ -39,7 +39,7 @@
 
 Compound conditions are evaluated in the following order of precedence:
 
-1. Parentheses.
+1. `Parentheses`_.
 
 2. Unary tests such as `EXISTS`_, `COMMAND`_, and `DEFINED`_.
 
@@ -57,262 +57,284 @@
 Basic Expressions
 """""""""""""""""
 
-``if(<constant>)``
- True if the constant is ``1``, ``ON``, ``YES``, ``TRUE``, ``Y``,
- or a non-zero number (including floating point numbers).
- False if the constant is ``0``, ``OFF``,
- ``NO``, ``FALSE``, ``N``, ``IGNORE``, ``NOTFOUND``, the empty string,
- or ends in the suffix ``-NOTFOUND``.  Named boolean constants are
- case-insensitive.  If the argument is not one of these specific
- constants, it is treated as a variable or string (see `Variable Expansion`_
- further below) and one of the following two forms applies.
+.. signature:: if(<constant>)
+  :target: constant
 
-``if(<variable>)``
- True if given a variable that is defined to a value that is not a false
- constant.  False otherwise, including if the variable is undefined.
- Note that macro arguments are not variables.
- :ref:`Environment Variables <CMake Language Environment Variables>` also
- cannot be tested this way, e.g. ``if(ENV{some_var})`` will always evaluate
- to false.
+  True if the constant is ``1``, ``ON``, ``YES``, ``TRUE``, ``Y``,
+  or a non-zero number (including floating point numbers).
+  False if the constant is ``0``, ``OFF``,
+  ``NO``, ``FALSE``, ``N``, ``IGNORE``, ``NOTFOUND``, the empty string,
+  or ends in the suffix ``-NOTFOUND``.  Named boolean constants are
+  case-insensitive.  If the argument is not one of these specific
+  constants, it is treated as a variable or string (see `Variable Expansion`_
+  further below) and one of the following two forms applies.
 
-``if(<string>)``
- A quoted string always evaluates to false unless:
+.. signature:: if(<variable>)
+  :target: variable
 
- * The string's value is one of the true constants, or
- * Policy :policy:`CMP0054` is not set to ``NEW`` and the string's value
-   happens to be a variable name that is affected by :policy:`CMP0054`'s
-   behavior.
+  True if given a variable that is defined to a value that is not a false
+  constant.  False otherwise, including if the variable is undefined.
+  Note that macro arguments are not variables.
+  :ref:`Environment Variables <CMake Language Environment Variables>` also
+  cannot be tested this way, e.g. ``if(ENV{some_var})`` will always evaluate
+  to false.
+
+.. signature:: if(<string>)
+  :target: string
+
+  A quoted string always evaluates to false unless:
+
+  * The string's value is one of the true constants, or
+  * Policy :policy:`CMP0054` is not set to ``NEW`` and the string's value
+    happens to be a variable name that is affected by :policy:`CMP0054`'s
+    behavior.
 
 Logic Operators
 """""""""""""""
 
-.. _NOT:
+.. signature:: if(NOT <condition>)
 
-``if(NOT <condition>)``
- True if the condition is not true.
+  True if the condition is not true.
 
-.. _AND:
+.. signature:: if(<cond1> AND <cond2>)
+  :target: AND
 
-``if(<cond1> AND <cond2>)``
- True if both conditions would be considered true individually.
+  True if both conditions would be considered true individually.
 
-.. _OR:
+.. signature:: if(<cond1> OR <cond2>)
+  :target: OR
 
-``if(<cond1> OR <cond2>)``
- True if either condition would be considered true individually.
+  True if either condition would be considered true individually.
 
-``if((condition) AND (condition OR (condition)))``
- The conditions inside the parenthesis are evaluated first and then
- the remaining condition is evaluated as in the other examples.
- Where there are nested parenthesis the innermost are evaluated as part
- of evaluating the condition that contains them.
+.. signature:: if((condition) AND (condition OR (condition)))
+  :target: parentheses
+
+  The conditions inside the parenthesis are evaluated first and then
+  the remaining condition is evaluated as in the other examples.
+  Where there are nested parenthesis the innermost are evaluated as part
+  of evaluating the condition that contains them.
 
 Existence Checks
 """"""""""""""""
 
-.. _COMMAND:
+.. signature:: if(COMMAND <command-name>)
 
-``if(COMMAND command-name)``
- True if the given name is a command, macro or function that can be
- invoked.
+  True if the given name is a command, macro or function that can be
+  invoked.
 
-``if(POLICY policy-id)``
- True if the given name is an existing policy (of the form ``CMP<NNNN>``).
+.. signature:: if(POLICY <policy-id>)
 
-``if(TARGET target-name)``
- True if the given name is an existing logical target name created
- by a call to the :command:`add_executable`, :command:`add_library`,
- or :command:`add_custom_target` command that has already been invoked
- (in any directory).
+  True if the given name is an existing policy (of the form ``CMP<NNNN>``).
 
-``if(TEST test-name)``
- .. versionadded:: 3.3
+.. signature:: if(TARGET <target-name>)
+
+  True if the given name is an existing logical target name created
+  by a call to the :command:`add_executable`, :command:`add_library`,
+  or :command:`add_custom_target` command that has already been invoked
+  (in any directory).
+
+.. signature:: if(TEST <test-name>)
+
+  .. versionadded:: 3.3
+
   True if the given name is an existing test name created by the
   :command:`add_test` command.
 
-.. _DEFINED:
+.. signature:: if(DEFINED <name>|CACHE{<name>}|ENV{<name>})
 
-``if(DEFINED <name>|CACHE{<name>}|ENV{<name>})``
- True if a variable, cache variable or environment variable
- with given ``<name>`` is defined. The value of the variable
- does not matter. Note the following caveats:
+  True if a variable, cache variable or environment variable
+  with given ``<name>`` is defined. The value of the variable
+  does not matter. Note the following caveats:
 
- * Macro arguments are not variables.
- * It is not possible to test directly whether a `<name>` is a non-cache
-   variable.  The expression ``if(DEFINED someName)`` will evaluate to true
-   if either a cache or non-cache variable ``someName`` exists.  In
-   comparison, the expression ``if(DEFINED CACHE{someName})`` will only
-   evaluate to true if a cache variable ``someName`` exists.  Both expressions
-   need to be tested if you need to know whether a non-cache variable exists:
-   ``if(DEFINED someName AND NOT DEFINED CACHE{someName})``.
+  * Macro arguments are not variables.
+  * It is not possible to test directly whether a `<name>` is a non-cache
+    variable.  The expression ``if(DEFINED someName)`` will evaluate to true
+    if either a cache or non-cache variable ``someName`` exists.  In
+    comparison, the expression ``if(DEFINED CACHE{someName})`` will only
+    evaluate to true if a cache variable ``someName`` exists.  Both expressions
+    need to be tested if you need to know whether a non-cache variable exists:
+    ``if(DEFINED someName AND NOT DEFINED CACHE{someName})``.
 
  .. versionadded:: 3.14
   Added support for ``CACHE{<name>}`` variables.
 
-``if(<variable|string> IN_LIST <variable>)``
- .. versionadded:: 3.3
+.. signature:: if(<variable|string> IN_LIST <variable>)
+  :target: IN_LIST
+
+  .. versionadded:: 3.3
+
   True if the given element is contained in the named list variable.
 
 File Operations
 """""""""""""""
 
-.. _EXISTS:
+.. signature:: if(EXISTS <path-to-file-or-directory>)
 
-``if(EXISTS path-to-file-or-directory)``
- True if the named file or directory exists.  Behavior is well-defined
- only for explicit full paths (a leading ``~/`` is not expanded as
- a home directory and is considered a relative path).
- Resolves symbolic links, i.e. if the named file or directory is a
- symbolic link, returns true if the target of the symbolic link exists.
+  True if the named file or directory exists.  Behavior is well-defined
+  only for explicit full paths (a leading ``~/`` is not expanded as
+  a home directory and is considered a relative path).
+  Resolves symbolic links, i.e. if the named file or directory is a
+  symbolic link, returns true if the target of the symbolic link exists.
 
- False if the given path is an empty string.
+  False if the given path is an empty string.
 
-``if(file1 IS_NEWER_THAN file2)``
- True if ``file1`` is newer than ``file2`` or if one of the two files doesn't
- exist.  Behavior is well-defined only for full paths.  If the file
- time stamps are exactly the same, an ``IS_NEWER_THAN`` comparison returns
- true, so that any dependent build operations will occur in the event
- of a tie.  This includes the case of passing the same file name for
- both file1 and file2.
+.. signature:: if(<file1> IS_NEWER_THAN <file2>)
+  :target: IS_NEWER_THAN
 
-``if(IS_DIRECTORY path)``
- True if ``path`` is a directory.  Behavior is well-defined only
- for full paths.
+  True if ``file1`` is newer than ``file2`` or if one of the two files doesn't
+  exist.  Behavior is well-defined only for full paths.  If the file
+  time stamps are exactly the same, an ``IS_NEWER_THAN`` comparison returns
+  true, so that any dependent build operations will occur in the event
+  of a tie.  This includes the case of passing the same file name for
+  both file1 and file2.
 
- False if the given path is an empty string.
+.. signature:: if(IS_DIRECTORY <path>)
 
-``if(IS_SYMLINK file-name)``
- True if the given name is a symbolic link.  Behavior is well-defined
- only for full paths.
+  True if ``path`` is a directory.  Behavior is well-defined only
+  for full paths.
 
-``if(IS_ABSOLUTE path)``
- True if the given path is an absolute path.  Note the following special
- cases:
+  False if the given path is an empty string.
 
- * An empty ``path`` evaluates to false.
- * On Windows hosts, any ``path`` that begins with a drive letter and colon
-   (e.g. ``C:``), a forward slash or a backslash will evaluate to true.
-   This means a path like ``C:no\base\dir`` will evaluate to true, even
-   though the non-drive part of the path is relative.
- * On non-Windows hosts, any ``path`` that begins with a tilde (``~``)
-   evaluates to true.
+.. signature:: if(IS_SYMLINK <path>)
+
+  True if the given path is a symbolic link.  Behavior is well-defined
+  only for full paths.
+
+.. signature:: if(IS_ABSOLUTE <path>)
+
+  True if the given path is an absolute path.  Note the following special
+  cases:
+
+  * An empty ``path`` evaluates to false.
+  * On Windows hosts, any ``path`` that begins with a drive letter and colon
+    (e.g. ``C:``), a forward slash or a backslash will evaluate to true.
+    This means a path like ``C:no\base\dir`` will evaluate to true, even
+    though the non-drive part of the path is relative.
+  * On non-Windows hosts, any ``path`` that begins with a tilde (``~``)
+    evaluates to true.
 
 Comparisons
 """""""""""
 
-.. _MATCHES:
+.. signature:: if(<variable|string> MATCHES <regex>)
+  :target: MATCHES
 
-``if(<variable|string> MATCHES regex)``
- True if the given string or variable's value matches the given regular
- expression.  See :ref:`Regex Specification` for regex format.
+  True if the given string or variable's value matches the given regular
+  expression.  See :ref:`Regex Specification` for regex format.
 
- .. versionadded:: 3.9
-  ``()`` groups are captured in :variable:`CMAKE_MATCH_<n>` variables.
+  .. versionadded:: 3.9
+   ``()`` groups are captured in :variable:`CMAKE_MATCH_<n>` variables.
 
-.. _LESS:
+.. signature:: if(<variable|string> LESS <variable|string>)
+  :target: LESS
 
-``if(<variable|string> LESS <variable|string>)``
- True if the given string or variable's value is a valid number and less
- than that on the right.
+  True if the given string or variable's value is a valid number and less
+  than that on the right.
 
-.. _GREATER:
+.. signature:: if(<variable|string> GREATER <variable|string>)
+  :target: GREATER
 
-``if(<variable|string> GREATER <variable|string>)``
- True if the given string or variable's value is a valid number and greater
- than that on the right.
+  True if the given string or variable's value is a valid number and greater
+  than that on the right.
 
-.. _EQUAL:
+.. signature:: if(<variable|string> EQUAL <variable|string>)
+  :target: EQUAL
 
-``if(<variable|string> EQUAL <variable|string>)``
- True if the given string or variable's value is a valid number and equal
- to that on the right.
+  True if the given string or variable's value is a valid number and equal
+  to that on the right.
 
-.. _LESS_EQUAL:
+.. signature:: if(<variable|string> LESS_EQUAL <variable|string>)
+  :target: LESS_EQUAL
 
-``if(<variable|string> LESS_EQUAL <variable|string>)``
- .. versionadded:: 3.7
+  .. versionadded:: 3.7
+
   True if the given string or variable's value is a valid number and less
   than or equal to that on the right.
 
-.. _GREATER_EQUAL:
+.. signature:: if(<variable|string> GREATER_EQUAL <variable|string>)
+  :target: GREATER_EQUAL
 
-``if(<variable|string> GREATER_EQUAL <variable|string>)``
- .. versionadded:: 3.7
+  .. versionadded:: 3.7
+
   True if the given string or variable's value is a valid number and greater
   than or equal to that on the right.
 
-.. _STRLESS:
+.. signature:: if(<variable|string> STRLESS <variable|string>)
+  :target: STRLESS
 
-``if(<variable|string> STRLESS <variable|string>)``
- True if the given string or variable's value is lexicographically less
- than the string or variable on the right.
+  True if the given string or variable's value is lexicographically less
+  than the string or variable on the right.
 
-.. _STRGREATER:
+.. signature:: if(<variable|string> STRGREATER <variable|string>)
+  :target: STRGREATER
 
-``if(<variable|string> STRGREATER <variable|string>)``
- True if the given string or variable's value is lexicographically greater
- than the string or variable on the right.
+  True if the given string or variable's value is lexicographically greater
+  than the string or variable on the right.
 
-.. _STREQUAL:
+.. signature:: if(<variable|string> STREQUAL <variable|string>)
+  :target: STREQUAL
 
-``if(<variable|string> STREQUAL <variable|string>)``
- True if the given string or variable's value is lexicographically equal
- to the string or variable on the right.
+  True if the given string or variable's value is lexicographically equal
+  to the string or variable on the right.
 
-.. _STRLESS_EQUAL:
+.. signature:: if(<variable|string> STRLESS_EQUAL <variable|string>)
+  :target: STRLESS_EQUAL
 
-``if(<variable|string> STRLESS_EQUAL <variable|string>)``
- .. versionadded:: 3.7
+  .. versionadded:: 3.7
+
   True if the given string or variable's value is lexicographically less
   than or equal to the string or variable on the right.
 
-.. _STRGREATER_EQUAL:
+.. signature:: if(<variable|string> STRGREATER_EQUAL <variable|string>)
+  :target: STRGREATER_EQUAL
 
-``if(<variable|string> STRGREATER_EQUAL <variable|string>)``
- .. versionadded:: 3.7
+  .. versionadded:: 3.7
+
   True if the given string or variable's value is lexicographically greater
   than or equal to the string or variable on the right.
 
 Version Comparisons
 """""""""""""""""""
 
-.. _VERSION_LESS:
+.. signature:: if(<variable|string> VERSION_LESS <variable|string>)
+  :target: VERSION_LESS
 
-``if(<variable|string> VERSION_LESS <variable|string>)``
- Component-wise integer version number comparison (version format is
- ``major[.minor[.patch[.tweak]]]``, omitted components are treated as zero).
- Any non-integer version component or non-integer trailing part of a version
- component effectively truncates the string at that point.
-
-.. _VERSION_GREATER:
-
-``if(<variable|string> VERSION_GREATER <variable|string>)``
- Component-wise integer version number comparison (version format is
- ``major[.minor[.patch[.tweak]]]``, omitted components are treated as zero).
- Any non-integer version component or non-integer trailing part of a version
- component effectively truncates the string at that point.
-
-.. _VERSION_EQUAL:
-
-``if(<variable|string> VERSION_EQUAL <variable|string>)``
- Component-wise integer version number comparison (version format is
- ``major[.minor[.patch[.tweak]]]``, omitted components are treated as zero).
- Any non-integer version component or non-integer trailing part of a version
- component effectively truncates the string at that point.
-
-.. _VERSION_LESS_EQUAL:
-
-``if(<variable|string> VERSION_LESS_EQUAL <variable|string>)``
- .. versionadded:: 3.7
   Component-wise integer version number comparison (version format is
   ``major[.minor[.patch[.tweak]]]``, omitted components are treated as zero).
   Any non-integer version component or non-integer trailing part of a version
   component effectively truncates the string at that point.
 
-.. _VERSION_GREATER_EQUAL:
+.. signature:: if(<variable|string> VERSION_GREATER <variable|string>)
+  :target: VERSION_GREATER
 
-``if(<variable|string> VERSION_GREATER_EQUAL <variable|string>)``
- .. versionadded:: 3.7
+  Component-wise integer version number comparison (version format is
+  ``major[.minor[.patch[.tweak]]]``, omitted components are treated as zero).
+  Any non-integer version component or non-integer trailing part of a version
+  component effectively truncates the string at that point.
+
+.. signature:: if(<variable|string> VERSION_EQUAL <variable|string>)
+  :target: VERSION_EQUAL
+
+  Component-wise integer version number comparison (version format is
+  ``major[.minor[.patch[.tweak]]]``, omitted components are treated as zero).
+  Any non-integer version component or non-integer trailing part of a version
+  component effectively truncates the string at that point.
+
+.. signature:: if(<variable|string> VERSION_LESS_EQUAL <variable|string>)
+  :target: VERSION_LESS_EQUAL
+
+  .. versionadded:: 3.7
+
+  Component-wise integer version number comparison (version format is
+  ``major[.minor[.patch[.tweak]]]``, omitted components are treated as zero).
+  Any non-integer version component or non-integer trailing part of a version
+  component effectively truncates the string at that point.
+
+.. signature:: if(<variable|string> VERSION_GREATER_EQUAL <variable|string>)
+  :target: VERSION_GREATER_EQUAL
+
+  .. versionadded:: 3.7
+
   Component-wise integer version number comparison (version format is
   ``major[.minor[.patch[.tweak]]]``, omitted components are treated as zero).
   Any non-integer version component or non-integer trailing part of a version
@@ -321,9 +343,9 @@
 Path Comparisons
 """"""""""""""""
 
-.. _PATH_EQUAL:
+.. signature:: if(<variable|string> PATH_EQUAL <variable|string>)
+  :target: PATH_EQUAL
 
-``if(<variable|string> PATH_EQUAL <variable|string>)``
   .. versionadded:: 3.24
 
   Compares the two paths component-by-component.  Only if every component of
@@ -386,35 +408,35 @@
 Automatic evaluation applies in the other cases whenever the
 above-documented condition syntax accepts ``<variable|string>``:
 
-* The left hand argument to ``MATCHES`` is first checked to see if it is
-  a defined variable, if so the variable's value is used, otherwise the
+* The left hand argument to `MATCHES`_ is first checked to see if it is
+  a defined variable.  If so, the variable's value is used, otherwise the
   original value is used.
 
-* If the left hand argument to ``MATCHES`` is missing it returns false
+* If the left hand argument to `MATCHES`_ is missing it returns false
   without error
 
-* Both left and right hand arguments to ``LESS``, ``GREATER``, ``EQUAL``,
-  ``LESS_EQUAL``, and ``GREATER_EQUAL``, are independently tested to see if
-  they are defined variables, if so their defined values are used otherwise
+* Both left and right hand arguments to `LESS`_, `GREATER`_, `EQUAL`_,
+  `LESS_EQUAL`_, and `GREATER_EQUAL`_, are independently tested to see if
+  they are defined variables.  If so, their defined values are used otherwise
   the original value is used.
 
-* Both left and right hand arguments to ``STRLESS``, ``STRGREATER``,
-  ``STREQUAL``, ``STRLESS_EQUAL``, and ``STRGREATER_EQUAL`` are independently
-  tested to see if they are defined variables, if so their defined values are
+* Both left and right hand arguments to `STRLESS`_, `STRGREATER`_,
+  `STREQUAL`_, `STRLESS_EQUAL`_, and `STRGREATER_EQUAL`_ are independently
+  tested to see if they are defined variables.  If so, their defined values are
   used otherwise the original value is used.
 
-* Both left and right hand arguments to ``VERSION_LESS``,
-  ``VERSION_GREATER``, ``VERSION_EQUAL``, ``VERSION_LESS_EQUAL``, and
-  ``VERSION_GREATER_EQUAL`` are independently tested to see if they are defined
-  variables, if so their defined values are used otherwise the original value
+* Both left and right hand arguments to `VERSION_LESS`_,
+  `VERSION_GREATER`_, `VERSION_EQUAL`_, `VERSION_LESS_EQUAL`_, and
+  `VERSION_GREATER_EQUAL`_ are independently tested to see if they are defined
+  variables.  If so, their defined values are used otherwise the original value
   is used.
 
-* The right hand argument to ``NOT`` is tested to see if it is a boolean
-  constant, if so the value is used, otherwise it is assumed to be a
+* The right hand argument to `NOT`_ is tested to see if it is a boolean
+  constant.  If so, the value is used, otherwise it is assumed to be a
   variable and it is dereferenced.
 
-* The left and right hand arguments to ``AND`` and ``OR`` are independently
-  tested to see if they are boolean constants, if so they are used as
+* The left and right hand arguments to `AND`_ and `OR`_ are independently
+  tested to see if they are boolean constants.  If so, they are used as
   such, otherwise they are assumed to be variables and are dereferenced.
 
 .. versionchanged:: 3.1
diff --git a/Help/command/include_directories.rst b/Help/command/include_directories.rst
index d2948ed..e68bb81 100644
--- a/Help/command/include_directories.rst
+++ b/Help/command/include_directories.rst
@@ -25,7 +25,7 @@
 
 If the ``SYSTEM`` option is given, the compiler will be told the
 directories are meant as system include directories on some platforms.
-Signalling this setting might achieve effects such as the compiler
+Signaling this setting might achieve effects such as the compiler
 skipping warnings, or these fixed-install system files not being
 considered in dependency calculations - see compiler docs.
 
diff --git a/Help/command/install.rst b/Help/command/install.rst
index 126888a..d5092ae 100644
--- a/Help/command/install.rst
+++ b/Help/command/install.rst
@@ -158,6 +158,9 @@
     ``.lib``, in contrast to the ``.dll`` libraries that go to ``RUNTIME``);
   * On AIX, the *linker import file* created for executables with
     :prop_tgt:`ENABLE_EXPORTS` enabled.
+  * On macOS, the *linker import file* created for shared libraries with
+    :prop_tgt:`ENABLE_EXPORTS` enabled (except when marked as ``FRAMEWORK``,
+    see below).
 
 ``LIBRARY``
   Target artifacts of this kind include:
@@ -308,6 +311,11 @@
   value of ``COMPONENT``. It is an error to use this parameter outside of a
   ``LIBRARY`` block.
 
+  .. versionchanged:: 3.27
+    This parameter is also usable for an ``ARCHIVE`` block to manage
+    the linker import file created, on macOS, for shared libraries with
+    :prop_tgt:`ENABLE_EXPORTS` enabled.
+
   Consider the following example:
 
   .. code-block:: cmake
@@ -342,6 +350,11 @@
   option installs nothing. It is an error to use this parameter outside of a
   ``LIBRARY`` block.
 
+  .. versionchanged:: 3.27
+    This parameter is also usable for an ``ARCHIVE`` block to manage
+    the linker import file created, on macOS, for shared libraries with
+    :prop_tgt:`ENABLE_EXPORTS` enabled.
+
   When ``NAMELINK_ONLY`` is given, either ``NAMELINK_COMPONENT`` or
   ``COMPONENT`` may be used to specify the installation component of the
   namelink, but ``COMPONENT`` should generally be preferred.
@@ -355,6 +368,11 @@
   installs the library. It is an error to use this parameter outside of a
   ``LIBRARY`` block.
 
+  .. versionchanged:: 3.27
+    This parameter is also usable for an ``ARCHIVE`` block to manage
+    the linker import file created, on macOS, for shared libraries with
+    :prop_tgt:`ENABLE_EXPORTS` enabled.
+
   If ``NAMELINK_SKIP`` is specified, ``NAMELINK_COMPONENT`` has no effect. It
   is not recommended to use ``NAMELINK_SKIP`` in conjunction with
   ``NAMELINK_COMPONENT``.
diff --git a/Help/command/list.rst b/Help/command/list.rst
index 33c4f80..0c7a562 100644
--- a/Help/command/list.rst
+++ b/Help/command/list.rst
@@ -36,23 +36,25 @@
 Introduction
 ^^^^^^^^^^^^
 
-The list subcommands ``APPEND``, ``INSERT``, ``FILTER``, ``PREPEND``,
-``POP_BACK``, ``POP_FRONT``, ``REMOVE_AT``, ``REMOVE_ITEM``,
-``REMOVE_DUPLICATES``, ``REVERSE`` and ``SORT`` may create
-new values for the list within the current CMake variable scope.  Similar to
-the :command:`set` command, the LIST command creates new variable values in
-the current scope, even if the list itself is actually defined in a parent
-scope.  To propagate the results of these operations upwards, use
-:command:`set` with ``PARENT_SCOPE``, :command:`set` with
-``CACHE INTERNAL``, or some other means of value propagation.
+The list subcommands :cref:`APPEND`, :cref:`INSERT`, :cref:`FILTER`,
+:cref:`PREPEND`, :cref:`POP_BACK`, :cref:`POP_FRONT`, :cref:`REMOVE_AT`,
+:cref:`REMOVE_ITEM`, :cref:`REMOVE_DUPLICATES`, :cref:`REVERSE` and
+:cref:`SORT` may create new values for the list within the current CMake
+variable scope.  Similar to the :command:`set` command, the ``list`` command
+creates new variable values in the current scope, even if the list itself is
+actually defined in a parent scope.  To propagate the results of these
+operations upwards, use :command:`set` with ``PARENT_SCOPE``,
+:command:`set` with ``CACHE INTERNAL``, or some other means of value
+propagation.
 
 .. note::
 
   A list in cmake is a ``;`` separated group of strings.  To create a
-  list the set command can be used.  For example, ``set(var a b c d e)``
-  creates a list with ``a;b;c;d;e``, and ``set(var "a b c d e")`` creates a
-  string or a list with one item in it.   (Note macro arguments are not
-  variables, and therefore cannot be used in LIST commands.)
+  list, the :command:`set` command can be used.  For example,
+  ``set(var a b c d e)`` creates a list with ``a;b;c;d;e``, and
+  ``set(var "a b c d e")`` creates a string or a list with one item in it.
+  (Note that macro arguments are not variables, and therefore cannot be used
+  in ``LIST`` commands.)
 
 .. note::
 
@@ -66,76 +68,54 @@
 Reading
 ^^^^^^^
 
-.. _LENGTH:
-
-.. code-block:: cmake
-
+.. signature::
   list(LENGTH <list> <output variable>)
 
-Returns the list's length.
+  Returns the list's length.
 
-.. _GET:
-
-.. code-block:: cmake
-
+.. signature::
   list(GET <list> <element index> [<element index> ...] <output variable>)
 
-Returns the list of elements specified by indices from the list.
+  Returns the list of elements specified by indices from the list.
 
-.. _JOIN:
+.. signature:: list(JOIN <list> <glue> <output variable>)
 
-.. code-block:: cmake
+  .. versionadded:: 3.12
 
-  list(JOIN <list> <glue> <output variable>)
+  Returns a string joining all list's elements using the glue string.
+  To join multiple strings, which are not part of a list,
+  use :command:`string(JOIN)`.
 
-.. versionadded:: 3.12
-
-Returns a string joining all list's elements using the glue string.
-To join multiple strings, which are not part of a list, use ``JOIN`` operator
-from :command:`string` command.
-
-.. _SUBLIST:
-
-.. code-block:: cmake
-
+.. signature::
   list(SUBLIST <list> <begin> <length> <output variable>)
 
-.. versionadded:: 3.12
+  .. versionadded:: 3.12
 
-Returns a sublist of the given list.
-If ``<length>`` is 0, an empty list will be returned.
-If ``<length>`` is -1 or the list is smaller than ``<begin>+<length>`` then
-the remaining elements of the list starting at ``<begin>`` will be returned.
+  Returns a sublist of the given list.
+  If ``<length>`` is 0, an empty list will be returned.
+  If ``<length>`` is -1 or the list is smaller than ``<begin>+<length>`` then
+  the remaining elements of the list starting at ``<begin>`` will be returned.
 
 Search
 ^^^^^^
 
-.. _FIND:
-
-.. code-block:: cmake
-
+.. signature::
   list(FIND <list> <value> <output variable>)
 
-Returns the index of the element specified in the list or -1
-if it wasn't found.
+  Returns the index of the element specified in the list
+  or ``-1`` if it wasn't found.
 
 Modification
 ^^^^^^^^^^^^
 
-.. _APPEND:
-
-.. code-block:: cmake
-
+.. signature::
   list(APPEND <list> [<element> ...])
 
-Appends elements to the list. If no variable named ``<list>`` exists in the
-current scope its value is treated as empty and the elements are appended to
-that empty list.
+  Appends elements to the list. If no variable named ``<list>`` exists in the
+  current scope its value is treated as empty and the elements are appended to
+  that empty list.
 
-.. _FILTER:
-
-.. code-block:: cmake
-
+.. signature::
   list(FILTER <list> <INCLUDE|EXCLUDE> REGEX <regular_expression>)
 
 .. versionadded:: 3.6
@@ -146,219 +126,205 @@
 For more information on regular expressions look under
 :ref:`string(REGEX) <Regex Specification>`.
 
-.. _INSERT:
-
-.. code-block:: cmake
-
+.. signature::
   list(INSERT <list> <element_index> <element> [<element> ...])
 
-Inserts elements to the list to the specified index. It is an
-error to specify an out-of-range index. Valid indexes are 0 to `N`
-where `N` is the length of the list, inclusive. An empty list
-has length 0. If no variable named ``<list>`` exists in the
-current scope its value is treated as empty and the elements are
-inserted in that empty list.
+  Inserts elements to the list to the specified index. It is an
+  error to specify an out-of-range index. Valid indexes are 0 to `N`
+  where `N` is the length of the list, inclusive. An empty list
+  has length 0. If no variable named ``<list>`` exists in the
+  current scope its value is treated as empty and the elements are
+  inserted in that empty list.
 
-.. _POP_BACK:
-
-.. code-block:: cmake
-
+.. signature::
   list(POP_BACK <list> [<out-var>...])
 
-.. versionadded:: 3.15
+  .. versionadded:: 3.15
 
-If no variable name is given, removes exactly one element. Otherwise,
-with `N` variable names provided, assign the last `N` elements' values
-to the given variables and then remove the last `N` values from
-``<list>``.
+  If no variable name is given, removes exactly one element. Otherwise,
+  with `N` variable names provided, assign the last `N` elements' values
+  to the given variables and then remove the last `N` values from
+  ``<list>``.
 
-.. _POP_FRONT:
-
-.. code-block:: cmake
-
+.. signature::
   list(POP_FRONT <list> [<out-var>...])
 
-.. versionadded:: 3.15
+  .. versionadded:: 3.15
 
-If no variable name is given, removes exactly one element. Otherwise,
-with `N` variable names provided, assign the first `N` elements' values
-to the given variables and then remove the first `N` values from
-``<list>``.
+  If no variable name is given, removes exactly one element. Otherwise,
+  with `N` variable names provided, assign the first `N` elements' values
+  to the given variables and then remove the first `N` values from
+  ``<list>``.
 
-.. _PREPEND:
-
-.. code-block:: cmake
-
+.. signature::
   list(PREPEND <list> [<element> ...])
 
-.. versionadded:: 3.15
+  .. versionadded:: 3.15
 
-Insert elements to the 0th position in the list. If no variable named
-``<list>`` exists in the current scope its value is treated as empty and
-the elements are prepended to that empty list.
+  Insert elements to the 0th position in the list. If no variable named
+  ``<list>`` exists in the current scope its value is treated as empty and
+  the elements are prepended to that empty list.
 
-.. _REMOVE_ITEM:
-
-.. code-block:: cmake
-
+.. signature::
   list(REMOVE_ITEM <list> <value> [<value> ...])
 
-Removes all instances of the given items from the list.
+  Removes all instances of the given items from the list.
 
-.. _REMOVE_AT:
-
-.. code-block:: cmake
-
+.. signature::
   list(REMOVE_AT <list> <index> [<index> ...])
 
-Removes items at given indices from the list.
+  Removes items at given indices from the list.
 
-.. _REMOVE_DUPLICATES:
-
-.. code-block:: cmake
-
+.. signature::
   list(REMOVE_DUPLICATES <list>)
 
-Removes duplicated items in the list. The relative order of items is preserved,
-but if duplicates are encountered, only the first instance is preserved.
+  Removes duplicated items in the list. The relative order of items
+  is preserved, but if duplicates are encountered,
+  only the first instance is preserved.
 
-.. _TRANSFORM:
-
-.. code-block:: cmake
-
+.. signature::
   list(TRANSFORM <list> <ACTION> [<SELECTOR>]
-                        [OUTPUT_VARIABLE <output variable>])
+       [OUTPUT_VARIABLE <output variable>])
 
-.. versionadded:: 3.12
+  .. versionadded:: 3.12
 
-Transforms the list by applying an action to all or, by specifying a
-``<SELECTOR>``, to the selected elements of the list, storing the result
-in-place or in the specified output variable.
+  Transforms the list by applying an ``<ACTION>`` to all or, by specifying a
+  ``<SELECTOR>``, to the selected elements of the list, storing the result
+  in-place or in the specified output variable.
 
-.. note::
+  .. note::
 
-   The ``TRANSFORM`` sub-command does not change the number of elements in the
-   list. If a ``<SELECTOR>`` is specified, only some elements will be changed,
-   the other ones will remain the same as before the transformation.
+    The ``TRANSFORM`` sub-command does not change the number of elements in the
+    list. If a ``<SELECTOR>`` is specified, only some elements will be changed,
+    the other ones will remain the same as before the transformation.
 
-``<ACTION>`` specifies the action to apply to the elements of the list.
-The actions have exactly the same semantics as sub-commands of the
-:command:`string` command.  ``<ACTION>`` must be one of the following:
+  ``<ACTION>`` specifies the action to apply to the elements of the list.
+  The actions have exactly the same semantics as sub-commands of the
+  :command:`string` command.  ``<ACTION>`` must be one of the following:
 
-``APPEND``, ``PREPEND``: Append, prepend specified value to each element of
-the list.
+    :command:`APPEND <string(APPEND)>`, :command:`PREPEND <string(PREPEND)>`
+      Append, prepend specified value to each element of the list.
 
-  .. code-block:: cmake
+      .. signature::
+        list(TRANSFORM <list> (APPEND|PREPEND) <value> ...)
+        :target: TRANSFORM_APPEND
 
-    list(TRANSFORM <list> <APPEND|PREPEND> <value> ...)
+    :command:`TOLOWER <string(TOLOWER)>`, :command:`TOUPPER <string(TOUPPER)>`
+      Convert each element of the list to lower, upper characters.
 
-``TOUPPER``, ``TOLOWER``: Convert each element of the list to upper, lower
-characters.
+      .. signature::
+        list(TRANSFORM <list> (TOLOWER|TOUPPER) ...)
+        :target: TRANSFORM_TOLOWER
 
-  .. code-block:: cmake
+    :command:`STRIP <string(STRIP)>`
+      Remove leading and trailing spaces from each element of the list.
 
-    list(TRANSFORM <list> <TOLOWER|TOUPPER> ...)
+      .. signature::
+        list(TRANSFORM <list> STRIP ...)
+        :target: TRANSFORM_STRIP
 
-``STRIP``: Remove leading and trailing spaces from each element of the
-list.
+    :command:`GENEX_STRIP <string(GENEX_STRIP)>`
+      Strip any
+      :manual:`generator expressions <cmake-generator-expressions(7)>`
+      from each element of the list.
 
-  .. code-block:: cmake
+      .. signature::
+        list(TRANSFORM <list> GENEX_STRIP ...)
+        :target: TRANSFORM_GENEX_STRIP
 
-    list(TRANSFORM <list> STRIP ...)
+    :command:`REPLACE <string(REGEX REPLACE)>`:
+      Match the regular expression as many times as possible and substitute
+      the replacement expression for the match for each element of the list
+      (same semantic as :command:`string(REGEX REPLACE)`).
 
-``GENEX_STRIP``: Strip any
-:manual:`generator expressions <cmake-generator-expressions(7)>` from each
-element of the list.
+      .. signature::
+        list(TRANSFORM <list> REPLACE <regular_expression>
+                                      <replace_expression> ...)
+        :target: TRANSFORM_REPLACE
 
-  .. code-block:: cmake
+  ``<SELECTOR>`` determines which elements of the list will be transformed.
+  Only one type of selector can be specified at a time.
+  When given, ``<SELECTOR>`` must be one of the following:
 
-    list(TRANSFORM <list> GENEX_STRIP ...)
+    ``AT``
+      Specify a list of indexes.
 
-``REPLACE``: Match the regular expression as many times as possible and
-substitute the replacement expression for the match for each element
-of the list
-(Same semantic as ``REGEX REPLACE`` from :command:`string` command).
+      .. code-block:: cmake
 
-  .. code-block:: cmake
+        list(TRANSFORM <list> <ACTION> AT <index> [<index> ...] ...)
 
-    list(TRANSFORM <list> REPLACE <regular_expression>
-                                  <replace_expression> ...)
+    ``FOR``
+      Specify a range with, optionally,
+      an increment used to iterate over the range.
 
-``<SELECTOR>`` determines which elements of the list will be transformed.
-Only one type of selector can be specified at a time.  When given,
-``<SELECTOR>`` must be one of the following:
+      .. code-block:: cmake
 
-``AT``: Specify a list of indexes.
+        list(TRANSFORM <list> <ACTION> FOR <start> <stop> [<step>] ...)
 
-  .. code-block:: cmake
+    ``REGEX``
+      Specify a regular expression.
+      Only elements matching the regular expression will be transformed.
 
-    list(TRANSFORM <list> <ACTION> AT <index> [<index> ...] ...)
+      .. code-block:: cmake
 
-``FOR``: Specify a range with, optionally, an increment used to iterate over
-the range.
-
-  .. code-block:: cmake
-
-    list(TRANSFORM <list> <ACTION> FOR <start> <stop> [<step>] ...)
-
-``REGEX``: Specify a regular expression. Only elements matching the regular
-expression will be transformed.
-
-  .. code-block:: cmake
-
-    list(TRANSFORM <list> <ACTION> REGEX <regular_expression> ...)
+        list(TRANSFORM <list> <ACTION> REGEX <regular_expression> ...)
 
 
 Ordering
 ^^^^^^^^
 
-.. _REVERSE:
-
-.. code-block:: cmake
-
+.. signature::
   list(REVERSE <list>)
 
-Reverses the contents of the list in-place.
+  Reverses the contents of the list in-place.
 
-.. _SORT:
-
-.. code-block:: cmake
-
+.. signature::
   list(SORT <list> [COMPARE <compare>] [CASE <case>] [ORDER <order>])
 
-Sorts the list in-place alphabetically.
+  Sorts the list in-place alphabetically.
 
-.. versionadded:: 3.13
-  Added the ``COMPARE``, ``CASE``, and ``ORDER`` options.
+  .. versionadded:: 3.13
+    Added the ``COMPARE``, ``CASE``, and ``ORDER`` options.
 
-.. versionadded:: 3.18
-  Added the ``COMPARE NATURAL`` option.
+  .. versionadded:: 3.18
+    Added the ``COMPARE NATURAL`` option.
 
-Use the ``COMPARE`` keyword to select the comparison method for sorting.
-The ``<compare>`` option should be one of:
+  Use the ``COMPARE`` keyword to select the comparison method for sorting.
+  The ``<compare>`` option should be one of:
 
-* ``STRING``: Sorts a list of strings alphabetically.  This is the
-  default behavior if the ``COMPARE`` option is not given.
-* ``FILE_BASENAME``: Sorts a list of pathnames of files by their basenames.
-* ``NATURAL``: Sorts a list of strings using natural order
-  (see ``strverscmp(3)`` manual), i.e. such that contiguous digits
-  are compared as whole numbers.
-  For example: the following list `10.0 1.1 2.1 8.0 2.0 3.1`
-  will be sorted as `1.1 2.0 2.1 3.1 8.0 10.0` if the ``NATURAL``
-  comparison is selected where it will be sorted as
-  `1.1 10.0 2.0 2.1 3.1 8.0` with the ``STRING`` comparison.
+    ``STRING``
+      Sorts a list of strings alphabetically.
+      This is the default behavior if the ``COMPARE`` option is not given.
 
-Use the ``CASE`` keyword to select a case sensitive or case insensitive
-sort mode.  The ``<case>`` option should be one of:
+    ``FILE_BASENAME``
+      Sorts a list of pathnames of files by their basenames.
 
-* ``SENSITIVE``: List items are sorted in a case-sensitive manner.  This is
-  the default behavior if the ``CASE`` option is not given.
-* ``INSENSITIVE``: List items are sorted case insensitively.  The order of
-  items which differ only by upper/lowercase is not specified.
+    ``NATURAL``
+      Sorts a list of strings using natural order
+      (see ``strverscmp(3)`` manual), i.e. such that contiguous digits
+      are compared as whole numbers.
+      For example: the following list `10.0 1.1 2.1 8.0 2.0 3.1`
+      will be sorted as `1.1 2.0 2.1 3.1 8.0 10.0` if the ``NATURAL``
+      comparison is selected where it will be sorted as
+      `1.1 10.0 2.0 2.1 3.1 8.0` with the ``STRING`` comparison.
 
-To control the sort order, the ``ORDER`` keyword can be given.
-The ``<order>`` option should be one of:
+  Use the ``CASE`` keyword to select a case sensitive or case insensitive
+  sort mode.  The ``<case>`` option should be one of:
 
-* ``ASCENDING``: Sorts the list in ascending order.  This is the default
-  behavior when the ``ORDER`` option is not given.
-* ``DESCENDING``: Sorts the list in descending order.
+    ``SENSITIVE``
+      List items are sorted in a case-sensitive manner.
+      This is the default behavior if the ``CASE`` option is not given.
+
+    ``INSENSITIVE``
+      List items are sorted case insensitively.  The order of
+      items which differ only by upper/lowercase is not specified.
+
+  To control the sort order, the ``ORDER`` keyword can be given.
+  The ``<order>`` option should be one of:
+
+    ``ASCENDING``
+      Sorts the list in ascending order.
+      This is the default behavior when the ``ORDER`` option is not given.
+
+    ``DESCENDING``
+      Sorts the list in descending order.
diff --git a/Help/command/math.rst b/Help/command/math.rst
index 8386aab..6989ebc 100644
--- a/Help/command/math.rst
+++ b/Help/command/math.rst
@@ -9,7 +9,8 @@
 
 Evaluates a mathematical ``<expression>`` and sets ``<variable>`` to the
 resulting value.  The result of the expression must be representable as a
-64-bit signed integer.
+64-bit signed integer. Floating point inputs are invalid e.g. ``1.1 * 10``.
+Non-integer results e.g. ``3 / 2`` are truncated.
 
 The mathematical expression must be given as a string (i.e. enclosed in
 double quotation marks). An example is ``"5 * (10 + 13)"``.
diff --git a/Help/command/separate_arguments.rst b/Help/command/separate_arguments.rst
index f66af35..4f0b25e 100644
--- a/Help/command/separate_arguments.rst
+++ b/Help/command/separate_arguments.rst
@@ -69,7 +69,7 @@
 
   The contents of ``out`` will be: ``/path/to/cc;-c;main.c``
 
-.. _`Parsing C Command-Line Arguments`: https://msdn.microsoft.com/library/a1y7w461.aspx
+.. _`Parsing C Command-Line Arguments`: https://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments
 
 .. code-block:: cmake
 
diff --git a/Help/command/set.rst b/Help/command/set.rst
index c724844..aeb88b3 100644
--- a/Help/command/set.rst
+++ b/Help/command/set.rst
@@ -8,109 +8,119 @@
 
 Signatures of this command that specify a ``<value>...`` placeholder
 expect zero or more arguments.  Multiple arguments will be joined as
-a :ref:`semicolon-separated list <CMake Language Lists>` to form the actual variable
-value to be set.  Zero arguments will cause normal variables to be
-unset.  See the :command:`unset` command to unset variables explicitly.
+a :ref:`semicolon-separated list <CMake Language Lists>` to form the
+actual variable value to be set.
 
 Set Normal Variable
 ^^^^^^^^^^^^^^^^^^^
 
-.. code-block:: cmake
-
+.. signature::
   set(<variable> <value>... [PARENT_SCOPE])
+  :target: normal
 
-Sets the given ``<variable>`` in the current function or directory scope.
+  Set or unset ``<variable>`` in the current function or directory scope:
 
-If the ``PARENT_SCOPE`` option is given the variable will be set in
-the scope above the current scope.  Each new directory or :command:`function`
-command creates a new scope.  A scope can also be created with the
-:command:`block` command. This command will set the value of a variable into
-the parent directory, calling function or encompassing scope (whichever is
-applicable to the case at hand). The previous state of the variable's value
-stays the same in the current scope (e.g., if it was undefined before, it is
-still undefined and if it had a value, it is still that value).
+  * If at least one ``<value>...`` is given, set the variable to that value.
+  * If no value is given, unset the variable.  This is equivalent to
+    :command:`unset(<variable>) <unset>`.
 
-The :command:`block(PROPAGATE)` and :command:`return(PROPAGATE)` commands can
-be used as an alternate method to the :command:`set(PARENT_SCOPE)` and
-:command:`unset(PARENT_SCOPE)` commands to update the parent scope.
+  If the ``PARENT_SCOPE`` option is given the variable will be set in
+  the scope above the current scope.  Each new directory or :command:`function`
+  command creates a new scope.  A scope can also be created with the
+  :command:`block` command. This command will set the value of a variable into
+  the parent directory, calling function or encompassing scope (whichever is
+  applicable to the case at hand). The previous state of the variable's value
+  stays the same in the current scope (e.g., if it was undefined before, it is
+  still undefined and if it had a value, it is still that value).
+
+  The :command:`block(PROPAGATE)` and :command:`return(PROPAGATE)` commands
+  can be used as an alternate method to the :command:`set(PARENT_SCOPE)`
+  and :command:`unset(PARENT_SCOPE)` commands to update the parent scope.
+
+.. include:: UNSET_NOTE.txt
 
 Set Cache Entry
 ^^^^^^^^^^^^^^^
 
-.. code-block:: cmake
-
+.. signature::
   set(<variable> <value>... CACHE <type> <docstring> [FORCE])
+  :target: CACHE
 
-Sets the given cache ``<variable>`` (cache entry).  Since cache entries
-are meant to provide user-settable values this does not overwrite
-existing cache entries by default.  Use the ``FORCE`` option to
-overwrite existing entries.
+  Sets the given cache ``<variable>`` (cache entry).  Since cache entries
+  are meant to provide user-settable values this does not overwrite
+  existing cache entries by default.  Use the ``FORCE`` option to
+  overwrite existing entries.
 
-The ``<type>`` must be specified as one of:
+  The ``<type>`` must be specified as one of:
 
-``BOOL``
-  Boolean ``ON/OFF`` value.  :manual:`cmake-gui(1)` offers a checkbox.
+    ``BOOL``
+      Boolean ``ON/OFF`` value.
+      :manual:`cmake-gui(1)` offers a checkbox.
 
-``FILEPATH``
-  Path to a file on disk.  :manual:`cmake-gui(1)` offers a file dialog.
+    ``FILEPATH``
+      Path to a file on disk.
+      :manual:`cmake-gui(1)` offers a file dialog.
 
-``PATH``
-  Path to a directory on disk.  :manual:`cmake-gui(1)` offers a file dialog.
+    ``PATH``
+      Path to a directory on disk.
+      :manual:`cmake-gui(1)` offers a file dialog.
 
-``STRING``
-  A line of text.  :manual:`cmake-gui(1)` offers a text field or a
-  drop-down selection if the :prop_cache:`STRINGS` cache entry
-  property is set.
+    ``STRING``
+      A line of text.
+      :manual:`cmake-gui(1)` offers a text field or a drop-down selection
+      if the :prop_cache:`STRINGS` cache entry property is set.
 
-``INTERNAL``
-  A line of text.  :manual:`cmake-gui(1)` does not show internal entries.
-  They may be used to store variables persistently across runs.
-  Use of this type implies ``FORCE``.
+    ``INTERNAL``
+      A line of text.
+      :manual:`cmake-gui(1)` does not show internal entries.
+      They may be used to store variables persistently across runs.
+      Use of this type implies ``FORCE``.
 
-The ``<docstring>`` must be specified as a line of text providing
-a quick summary of the option for presentation to :manual:`cmake-gui(1)`
-users.
+  The ``<docstring>`` must be specified as a line of text
+  providing a quick summary of the option
+  for presentation to :manual:`cmake-gui(1)` users.
 
-If the cache entry does not exist prior to the call or the ``FORCE``
-option is given then the cache entry will be set to the given value.
+  If the cache entry does not exist prior to the call or the ``FORCE``
+  option is given then the cache entry will be set to the given value.
 
-.. note::
+  .. note::
 
-  The content of the cache variable will not be directly accessible if a normal
-  variable of the same name already exists (see :ref:`rules of variable
-  evaluation <CMake Language Variables>`). If policy :policy:`CMP0126` is set
-  to ``OLD``, any normal variable binding in the current scope will be removed.
+    The content of the cache variable will not be directly accessible
+    if a normal variable of the same name already exists
+    (see :ref:`rules of variable evaluation <CMake Language Variables>`).
+    If policy :policy:`CMP0126` is set to ``OLD``, any normal variable
+    binding in the current scope will be removed.
 
-It is possible for the cache entry to exist prior to the call but
-have no type set if it was created on the :manual:`cmake(1)` command
-line by a user through the :option:`-D\<var\>=\<value\> <cmake -D>` option without
-specifying a type.  In this case the ``set`` command will add the
-type.  Furthermore, if the ``<type>`` is ``PATH`` or ``FILEPATH``
-and the ``<value>`` provided on the command line is a relative path,
-then the ``set`` command will treat the path as relative to the
-current working directory and convert it to an absolute path.
+  It is possible for the cache entry to exist prior to the call but
+  have no type set if it was created on the :manual:`cmake(1)` command
+  line by a user through the :option:`-D\<var\>=\<value\> <cmake -D>` option
+  without specifying a type.  In this case the ``set`` command will add the
+  type.  Furthermore, if the ``<type>`` is ``PATH`` or ``FILEPATH``
+  and the ``<value>`` provided on the command line is a relative path,
+  then the ``set`` command will treat the path as relative to the
+  current working directory and convert it to an absolute path.
 
 Set Environment Variable
 ^^^^^^^^^^^^^^^^^^^^^^^^
 
-.. code-block:: cmake
-
+.. signature::
   set(ENV{<variable>} [<value>])
+  :target: ENV
 
-Sets an :manual:`Environment Variable <cmake-env-variables(7)>`
-to the given value.
-Subsequent calls of ``$ENV{<variable>}`` will return this new value.
+  Sets an :manual:`Environment Variable <cmake-env-variables(7)>`
+  to the given value.
+  Subsequent calls of ``$ENV{<variable>}`` will return this new value.
 
-This command affects only the current CMake process, not the process
-from which CMake was called, nor the system environment at large,
-nor the environment of subsequent build or test processes.
+  This command affects only the current CMake process, not the process
+  from which CMake was called, nor the system environment at large,
+  nor the environment of subsequent build or test processes.
 
-If no argument is given after ``ENV{<variable>}`` or if ``<value>`` is
-an empty string, then this command will clear any existing value of the
-environment variable.
+  If no argument is given after ``ENV{<variable>}`` or if ``<value>`` is
+  an empty string, then this command will clear any existing value of the
+  environment variable.
 
-Arguments after ``<value>`` are ignored. If extra arguments are found,
-then an author warning is issued.
+  Arguments after ``<value>`` are ignored. If extra arguments are found,
+  then an author warning is issued.
 
 See Also
 ^^^^^^^^
diff --git a/Help/command/set_property.rst b/Help/command/set_property.rst
index d446a2d..8dd4b94 100644
--- a/Help/command/set_property.rst
+++ b/Help/command/set_property.rst
@@ -82,15 +82,15 @@
   to the installation prefix.
 
 ``TEST``
-  Scope may name zero or more existing tests.
-  See also the :command:`set_tests_properties` command.
+  Scope is limited to the directory the command is called in. It may name zero
+  or more existing tests. See also command :command:`set_tests_properties`.
 
   Test property values may be specified using
   :manual:`generator expressions <cmake-generator-expressions(7)>`
   for tests created by the :command:`add_test(NAME)` signature.
 
 ``CACHE``
-  Scope must name zero or more cache existing entries.
+  Scope must name zero or more existing cache entries.
 
 The required ``PROPERTY`` option is immediately followed by the name of
 the property to set.  Remaining arguments are used to compose the
diff --git a/Help/command/string.rst b/Help/command/string.rst
index c24b9bc..0e69b27 100644
--- a/Help/command/string.rst
+++ b/Help/command/string.rst
@@ -32,7 +32,7 @@
     string(`COMPARE`_ <op> <string1> <string2> <out-var>)
 
   `Hashing`_
-    string(`\<HASH\> <HASH_>`_ <out-var> <input>)
+    string(`\<HASH\>`_ <out-var> <input>)
 
   `Generation`_
     string(`ASCII`_ <number>... <out-var>)
@@ -45,16 +45,16 @@
 
   `JSON`_
     string(JSON <out-var> [ERROR_VARIABLE <error-var>]
-           {:ref:`GET <JSON_GET>` | :ref:`TYPE <JSON_TYPE>` | :ref:`LENGTH <JSON_LENGTH>` | :ref:`REMOVE <JSON_REMOVE>`}
+           {`GET <JSON GET_>`_ | `TYPE <JSON TYPE_>`_ | `LENGTH <JSON LENGTH_>`_ | `REMOVE <JSON REMOVE_>`_}
            <json-string> <member|index> [<member|index> ...])
     string(JSON <out-var> [ERROR_VARIABLE <error-var>]
-           :ref:`MEMBER <JSON_MEMBER>` <json-string>
+           `MEMBER <JSON MEMBER_>`_ <json-string>
            [<member|index> ...] <index>)
     string(JSON <out-var> [ERROR_VARIABLE <error-var>]
-           :ref:`SET <JSON_SET>` <json-string>
+           `SET <JSON SET_>`_ <json-string>
            <member|index> [<member|index> ...] <value>)
     string(JSON <out-var> [ERROR_VARIABLE <error-var>]
-           :ref:`EQUAL <JSON_EQUAL>` <json-string1> <json-string2>)
+           `EQUAL <JSON EQUAL_>`_ <json-string1> <json-string2>)
 
 Search and Replace
 ^^^^^^^^^^^^^^^^^^
@@ -62,75 +62,60 @@
 Search and Replace With Plain Strings
 """""""""""""""""""""""""""""""""""""
 
-.. _FIND:
-
-.. code-block:: cmake
-
+.. signature::
   string(FIND <string> <substring> <output_variable> [REVERSE])
 
-Return the position where the given ``<substring>`` was found in
-the supplied ``<string>``.  If the ``REVERSE`` flag was used, the command will
-search for the position of the last occurrence of the specified
-``<substring>``.  If the ``<substring>`` is not found, a position of -1 is
-returned.
+  Return the position where the given ``<substring>`` was found in
+  the supplied ``<string>``.  If the ``REVERSE`` flag was used, the command
+  will search for the position of the last occurrence of the specified
+  ``<substring>``.  If the ``<substring>`` is not found, a position of -1 is
+  returned.
 
-The ``string(FIND)`` subcommand treats all strings as ASCII-only characters.
-The index stored in ``<output_variable>`` will also be counted in bytes,
-so strings containing multi-byte characters may lead to unexpected results.
+  The ``string(FIND)`` subcommand treats all strings as ASCII-only characters.
+  The index stored in ``<output_variable>`` will also be counted in bytes,
+  so strings containing multi-byte characters may lead to unexpected results.
 
-.. _REPLACE:
-
-.. code-block:: cmake
-
+.. signature::
   string(REPLACE <match_string>
          <replace_string> <output_variable>
          <input> [<input>...])
 
-Replace all occurrences of ``<match_string>`` in the ``<input>``
-with ``<replace_string>`` and store the result in the ``<output_variable>``.
+  Replace all occurrences of ``<match_string>`` in the ``<input>``
+  with ``<replace_string>`` and store the result in the ``<output_variable>``.
 
 Search and Replace With Regular Expressions
 """""""""""""""""""""""""""""""""""""""""""
 
-.. _`REGEX MATCH`:
-
-.. code-block:: cmake
-
+.. signature::
   string(REGEX MATCH <regular_expression>
          <output_variable> <input> [<input>...])
 
-Match the ``<regular_expression>`` once and store the match in the
-``<output_variable>``.
-All ``<input>`` arguments are concatenated before matching.
-Regular expressions are specified in the subsection just below.
+  Match the ``<regular_expression>`` once and store the match in the
+  ``<output_variable>``.
+  All ``<input>`` arguments are concatenated before matching.
+  Regular expressions are specified in the subsection just below.
 
-.. _`REGEX MATCHALL`:
-
-.. code-block:: cmake
-
+.. signature::
   string(REGEX MATCHALL <regular_expression>
          <output_variable> <input> [<input>...])
 
-Match the ``<regular_expression>`` as many times as possible and store the
-matches in the ``<output_variable>`` as a list.
-All ``<input>`` arguments are concatenated before matching.
+  Match the ``<regular_expression>`` as many times as possible and store the
+  matches in the ``<output_variable>`` as a list.
+  All ``<input>`` arguments are concatenated before matching.
 
-.. _`REGEX REPLACE`:
-
-.. code-block:: cmake
-
+.. signature::
   string(REGEX REPLACE <regular_expression>
          <replacement_expression> <output_variable>
          <input> [<input>...])
 
-Match the ``<regular_expression>`` as many times as possible and substitute
-the ``<replacement_expression>`` for the match in the output.
-All ``<input>`` arguments are concatenated before matching.
+  Match the ``<regular_expression>`` as many times as possible and substitute
+  the ``<replacement_expression>`` for the match in the output.
+  All ``<input>`` arguments are concatenated before matching.
 
-The ``<replacement_expression>`` may refer to parenthesis-delimited
-subexpressions of the match using ``\1``, ``\2``, ..., ``\9``.  Note that
-two backslashes (``\\1``) are required in CMake code to get a backslash
-through argument parsing.
+  The ``<replacement_expression>`` may refer to parenthesis-delimited
+  subexpressions of the match using ``\1``, ``\2``, ..., ``\9``.  Note that
+  two backslashes (``\\1``) are required in CMake code to get a backslash
+  through argument parsing.
 
 .. _`Regex Specification`:
 
@@ -201,130 +186,100 @@
 Manipulation
 ^^^^^^^^^^^^
 
-.. _APPEND:
-
-.. code-block:: cmake
-
+.. signature::
   string(APPEND <string_variable> [<input>...])
 
-.. versionadded:: 3.4
+  .. versionadded:: 3.4
 
-Append all the ``<input>`` arguments to the string.
+  Append all the ``<input>`` arguments to the string.
 
-.. _PREPEND:
-
-.. code-block:: cmake
-
+.. signature::
   string(PREPEND <string_variable> [<input>...])
 
-.. versionadded:: 3.10
+  .. versionadded:: 3.10
 
-Prepend all the ``<input>`` arguments to the string.
+  Prepend all the ``<input>`` arguments to the string.
 
-.. _CONCAT:
-
-.. code-block:: cmake
-
+.. signature::
   string(CONCAT <output_variable> [<input>...])
 
-Concatenate all the ``<input>`` arguments together and store
-the result in the named ``<output_variable>``.
+  Concatenate all the ``<input>`` arguments together and store
+  the result in the named ``<output_variable>``.
 
-.. _JOIN:
-
-.. code-block:: cmake
-
+.. signature::
   string(JOIN <glue> <output_variable> [<input>...])
 
-.. versionadded:: 3.12
+  .. versionadded:: 3.12
 
-Join all the ``<input>`` arguments together using the ``<glue>``
-string and store the result in the named ``<output_variable>``.
+  Join all the ``<input>`` arguments together using the ``<glue>``
+  string and store the result in the named ``<output_variable>``.
 
-To join a list's elements, prefer to use the ``JOIN`` operator
-from the :command:`list` command.  This allows for the elements to have
-special characters like ``;`` in them.
+  To join a list's elements, prefer to use the ``JOIN`` operator
+  from the :command:`list` command.  This allows for the elements to have
+  special characters like ``;`` in them.
 
-.. _TOLOWER:
-
-.. code-block:: cmake
-
+.. signature::
   string(TOLOWER <string> <output_variable>)
 
-Convert ``<string>`` to lower characters.
+  Convert ``<string>`` to lower characters.
 
-.. _TOUPPER:
-
-.. code-block:: cmake
-
+.. signature::
   string(TOUPPER <string> <output_variable>)
 
-Convert ``<string>`` to upper characters.
+  Convert ``<string>`` to upper characters.
 
-.. _LENGTH:
-
-.. code-block:: cmake
-
+.. signature::
   string(LENGTH <string> <output_variable>)
 
-Store in an ``<output_variable>`` a given string's length in bytes.
-Note that this means if ``<string>`` contains multi-byte characters, the
-result stored in ``<output_variable>`` will *not* be the number of characters.
+  Store in an ``<output_variable>`` a given string's length in bytes.
+  Note that this means if ``<string>`` contains multi-byte characters,
+  the result stored in ``<output_variable>`` will *not* be
+  the number of characters.
 
-.. _SUBSTRING:
-
-.. code-block:: cmake
-
+.. signature::
   string(SUBSTRING <string> <begin> <length> <output_variable>)
 
-Store in an ``<output_variable>`` a substring of a given ``<string>``.  If
-``<length>`` is ``-1`` the remainder of the string starting at ``<begin>``
-will be returned.
+  Store in an ``<output_variable>`` a substring of a given ``<string>``.  If
+  ``<length>`` is ``-1`` the remainder of the string starting at ``<begin>``
+  will be returned.
 
-.. versionchanged:: 3.2
-  If ``<string>`` is shorter than ``<length>`` then the end of the string
-  is used instead.  Previous versions of CMake reported an error in this case.
+  .. versionchanged:: 3.2
+    If ``<string>`` is shorter than ``<length>``
+    then the end of the string is used instead.
+    Previous versions of CMake reported an error in this case.
 
-Both ``<begin>`` and ``<length>`` are counted in bytes, so care must
-be exercised if ``<string>`` could contain multi-byte characters.
+  Both ``<begin>`` and ``<length>`` are counted in bytes, so care must
+  be exercised if ``<string>`` could contain multi-byte characters.
 
-.. _STRIP:
-
-.. code-block:: cmake
-
+.. signature::
   string(STRIP <string> <output_variable>)
 
-Store in an ``<output_variable>`` a substring of a given ``<string>`` with
-leading and trailing spaces removed.
+  Store in an ``<output_variable>`` a substring of a given ``<string>``
+  with leading and trailing spaces removed.
 
-.. _GENEX_STRIP:
-
-.. code-block:: cmake
-
+.. signature::
   string(GENEX_STRIP <string> <output_variable>)
 
-.. versionadded:: 3.1
+  .. versionadded:: 3.1
 
-Strip any :manual:`generator expressions <cmake-generator-expressions(7)>`
-from the input ``<string>`` and store the result in the ``<output_variable>``.
+  Strip any :manual:`generator expressions <cmake-generator-expressions(7)>`
+  from the input ``<string>`` and store the result
+  in the ``<output_variable>``.
 
-.. _REPEAT:
-
-.. code-block:: cmake
-
+.. signature::
   string(REPEAT <string> <count> <output_variable>)
 
-.. versionadded:: 3.15
+  .. versionadded:: 3.15
 
-Produce the output string as the input ``<string>`` repeated ``<count>`` times.
+  Produce the output string as the input ``<string>``
+  repeated ``<count>`` times.
 
 Comparison
 ^^^^^^^^^^
 
 .. _COMPARE:
 
-.. code-block:: cmake
-
+.. signature::
   string(COMPARE LESS <string1> <string2> <output_variable>)
   string(COMPARE GREATER <string1> <string2> <output_variable>)
   string(COMPARE EQUAL <string1> <string2> <output_variable>)
@@ -332,240 +287,217 @@
   string(COMPARE LESS_EQUAL <string1> <string2> <output_variable>)
   string(COMPARE GREATER_EQUAL <string1> <string2> <output_variable>)
 
-Compare the strings and store true or false in the ``<output_variable>``.
+  Compare the strings and store true or false in the ``<output_variable>``.
 
-.. versionadded:: 3.7
-  Added the ``LESS_EQUAL`` and ``GREATER_EQUAL`` options.
+  .. versionadded:: 3.7
+    Added the ``LESS_EQUAL`` and ``GREATER_EQUAL`` options.
 
 .. _`Supported Hash Algorithms`:
 
 Hashing
 ^^^^^^^
 
-.. _`HASH`:
-
-.. code-block:: cmake
-
+.. signature::
   string(<HASH> <output_variable> <input>)
+  :target: <HASH>
 
-Compute a cryptographic hash of the ``<input>`` string.
-The supported ``<HASH>`` algorithm names are:
+  Compute a cryptographic hash of the ``<input>`` string.
+  The supported ``<HASH>`` algorithm names are:
 
-``MD5``
-  Message-Digest Algorithm 5, RFC 1321.
-``SHA1``
-  US Secure Hash Algorithm 1, RFC 3174.
-``SHA224``
-  US Secure Hash Algorithms, RFC 4634.
-``SHA256``
-  US Secure Hash Algorithms, RFC 4634.
-``SHA384``
-  US Secure Hash Algorithms, RFC 4634.
-``SHA512``
-  US Secure Hash Algorithms, RFC 4634.
-``SHA3_224``
-  Keccak SHA-3.
-``SHA3_256``
-  Keccak SHA-3.
-``SHA3_384``
-  Keccak SHA-3.
-``SHA3_512``
-  Keccak SHA-3.
+  ``MD5``
+    Message-Digest Algorithm 5, RFC 1321.
+  ``SHA1``
+    US Secure Hash Algorithm 1, RFC 3174.
+  ``SHA224``
+    US Secure Hash Algorithms, RFC 4634.
+  ``SHA256``
+    US Secure Hash Algorithms, RFC 4634.
+  ``SHA384``
+    US Secure Hash Algorithms, RFC 4634.
+  ``SHA512``
+    US Secure Hash Algorithms, RFC 4634.
+  ``SHA3_224``
+    Keccak SHA-3.
+  ``SHA3_256``
+    Keccak SHA-3.
+  ``SHA3_384``
+    Keccak SHA-3.
+  ``SHA3_512``
+    Keccak SHA-3.
 
-.. versionadded:: 3.8
-  Added the ``SHA3_*`` hash algorithms.
+  .. versionadded:: 3.8
+    Added the ``SHA3_*`` hash algorithms.
 
 Generation
 ^^^^^^^^^^
 
-.. _ASCII:
-
-.. code-block:: cmake
-
+.. signature::
   string(ASCII <number> [<number> ...] <output_variable>)
 
-Convert all numbers into corresponding ASCII characters.
+  Convert all numbers into corresponding ASCII characters.
 
-.. _HEX:
-
-.. code-block:: cmake
-
+.. signature::
   string(HEX <string> <output_variable>)
 
-.. versionadded:: 3.18
+  .. versionadded:: 3.18
 
-Convert each byte in the input ``<string>`` to its hexadecimal representation
-and store the concatenated hex digits in the ``<output_variable>``. Letters in
-the output (``a`` through ``f``) are in lowercase.
+  Convert each byte in the input ``<string>`` to its hexadecimal representation
+  and store the concatenated hex digits in the ``<output_variable>``.
+  Letters in the output (``a`` through ``f``) are in lowercase.
 
-.. _CONFIGURE:
-
-.. code-block:: cmake
-
+.. signature::
   string(CONFIGURE <string> <output_variable>
          [@ONLY] [ESCAPE_QUOTES])
 
-Transform a ``<string>`` like :command:`configure_file` transforms a file.
+  Transform a ``<string>`` like :command:`configure_file` transforms a file.
 
-.. _MAKE_C_IDENTIFIER:
-
-.. code-block:: cmake
-
+.. signature::
   string(MAKE_C_IDENTIFIER <string> <output_variable>)
 
-Convert each non-alphanumeric character in the input ``<string>`` to an
-underscore and store the result in the ``<output_variable>``.  If the first
-character of the ``<string>`` is a digit, an underscore will also be prepended
-to the result.
+  Convert each non-alphanumeric character in the input ``<string>`` to an
+  underscore and store the result in the ``<output_variable>``.  If the first
+  character of the ``<string>`` is a digit, an underscore will also be
+  prepended to the result.
 
-.. _RANDOM:
-
-.. code-block:: cmake
-
+.. signature::
   string(RANDOM [LENGTH <length>] [ALPHABET <alphabet>]
          [RANDOM_SEED <seed>] <output_variable>)
 
-Return a random string of given ``<length>`` consisting of
-characters from the given ``<alphabet>``.  Default length is 5 characters
-and default alphabet is all numbers and upper and lower case letters.
-If an integer ``RANDOM_SEED`` is given, its value will be used to seed the
-random number generator.
+  Return a random string of given ``<length>`` consisting of
+  characters from the given ``<alphabet>``.  Default length is 5 characters
+  and default alphabet is all numbers and upper and lower case letters.
+  If an integer ``RANDOM_SEED`` is given, its value will be used to seed the
+  random number generator.
 
-.. _TIMESTAMP:
-
-.. code-block:: cmake
-
+.. signature::
   string(TIMESTAMP <output_variable> [<format_string>] [UTC])
 
-Write a string representation of the current date
-and/or time to the ``<output_variable>``.
+  Write a string representation of the current date
+  and/or time to the ``<output_variable>``.
 
-If the command is unable to obtain a timestamp, the ``<output_variable>``
-will be set to the empty string ``""``.
+  If the command is unable to obtain a timestamp, the ``<output_variable>``
+  will be set to the empty string ``""``.
 
-The optional ``UTC`` flag requests the current date/time representation to
-be in Coordinated Universal Time (UTC) rather than local time.
+  The optional ``UTC`` flag requests the current date/time representation to
+  be in Coordinated Universal Time (UTC) rather than local time.
 
-The optional ``<format_string>`` may contain the following format
-specifiers:
+  The optional ``<format_string>`` may contain the following format
+  specifiers:
 
-``%%``
+  ``%%``
+    .. versionadded:: 3.8
+
+    A literal percent sign (%).
+
+  ``%d``
+    The day of the current month (01-31).
+
+  ``%H``
+    The hour on a 24-hour clock (00-23).
+
+  ``%I``
+    The hour on a 12-hour clock (01-12).
+
+  ``%j``
+    The day of the current year (001-366).
+
+  ``%m``
+    The month of the current year (01-12).
+
+  ``%b``
+    .. versionadded:: 3.7
+
+    Abbreviated month name (e.g. Oct).
+
+  ``%B``
+    .. versionadded:: 3.10
+
+    Full month name (e.g. October).
+
+  ``%M``
+    The minute of the current hour (00-59).
+
+  ``%s``
+    .. versionadded:: 3.6
+
+    Seconds since midnight (UTC) 1-Jan-1970 (UNIX time).
+
+  ``%S``
+    The second of the current minute.  60 represents a leap second. (00-60)
+
+  ``%f``
+    .. versionadded:: 3.23
+
+    The microsecond of the current second (000000-999999).
+
+  ``%U``
+    The week number of the current year (00-53).
+
+  ``%V``
+    .. versionadded:: 3.22
+
+    The ISO 8601 week number of the current year (01-53).
+
+  ``%w``
+    The day of the current week. 0 is Sunday. (0-6)
+
+  ``%a``
+    .. versionadded:: 3.7
+
+    Abbreviated weekday name (e.g. Fri).
+
+  ``%A``
+    .. versionadded:: 3.10
+
+    Full weekday name (e.g. Friday).
+
+  ``%y``
+    The last two digits of the current year (00-99).
+
+  ``%Y``
+    The current year.
+
+  ``%z``
+    .. versionadded:: 3.26
+
+    The offset of the time zone from UTC, in hours and minutes,
+    with format ``+hhmm`` or ``-hhmm``.
+
+  ``%Z``
+    .. versionadded:: 3.26
+
+    The time zone name.
+
+  Unknown format specifiers will be ignored and copied to the output
+  as-is.
+
+  If no explicit ``<format_string>`` is given, it will default to:
+
+  ::
+
+    %Y-%m-%dT%H:%M:%S    for local time.
+    %Y-%m-%dT%H:%M:%SZ   for UTC.
+
   .. versionadded:: 3.8
+    If the ``SOURCE_DATE_EPOCH`` environment variable is set,
+    its value will be used instead of the current time.
+    See https://reproducible-builds.org/specs/source-date-epoch/ for details.
 
-  A literal percent sign (%).
-
-``%d``
-  The day of the current month (01-31).
-
-``%H``
-  The hour on a 24-hour clock (00-23).
-
-``%I``
-  The hour on a 12-hour clock (01-12).
-
-``%j``
-  The day of the current year (001-366).
-
-``%m``
-  The month of the current year (01-12).
-
-``%b``
-  .. versionadded:: 3.7
-
-  Abbreviated month name (e.g. Oct).
-
-``%B``
-  .. versionadded:: 3.10
-
-  Full month name (e.g. October).
-
-``%M``
-  The minute of the current hour (00-59).
-
-``%s``
-  .. versionadded:: 3.6
-
-  Seconds since midnight (UTC) 1-Jan-1970 (UNIX time).
-
-``%S``
-  The second of the current minute.  60 represents a leap second. (00-60)
-
-``%f``
-  .. versionadded:: 3.23
-
-  The microsecond of the current second (000000-999999).
-
-``%U``
-  The week number of the current year (00-53).
-
-``%V``
-  .. versionadded:: 3.22
-
-  The ISO 8601 week number of the current year (01-53).
-
-``%w``
-  The day of the current week. 0 is Sunday. (0-6)
-
-``%a``
-  .. versionadded:: 3.7
-
-  Abbreviated weekday name (e.g. Fri).
-
-``%A``
-  .. versionadded:: 3.10
-
-  Full weekday name (e.g. Friday).
-
-``%y``
-  The last two digits of the current year (00-99).
-
-``%Y``
-  The current year.
-
-``%z``
-  .. versionadded:: 3.26
-
-  The offset of the time zone from UTC, in hours and minutes,
-  with format ``+hhmm`` or ``-hhmm``.
-
-``%Z``
-  .. versionadded:: 3.26
-
-  The time zone name.
-
-Unknown format specifiers will be ignored and copied to the output
-as-is.
-
-If no explicit ``<format_string>`` is given, it will default to:
-
-::
-
-   %Y-%m-%dT%H:%M:%S    for local time.
-   %Y-%m-%dT%H:%M:%SZ   for UTC.
-
-.. versionadded:: 3.8
-  If the ``SOURCE_DATE_EPOCH`` environment variable is set,
-  its value will be used instead of the current time.
-  See https://reproducible-builds.org/specs/source-date-epoch/ for details.
-
-.. _UUID:
-
-.. code-block:: cmake
-
+.. signature::
   string(UUID <output_variable> NAMESPACE <namespace> NAME <name>
          TYPE <MD5|SHA1> [UPPER])
 
-.. versionadded:: 3.1
+  .. versionadded:: 3.1
 
-Create a universally unique identifier (aka GUID) as per RFC4122
-based on the hash of the combined values of ``<namespace>``
-(which itself has to be a valid UUID) and ``<name>``.
-The hash algorithm can be either ``MD5`` (Version 3 UUID) or
-``SHA1`` (Version 5 UUID).
-A UUID has the format ``xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx``
-where each ``x`` represents a lower case hexadecimal character.
-Where required, an uppercase representation can be requested
-with the optional ``UPPER`` flag.
+  Create a universally unique identifier (aka GUID) as per RFC4122
+  based on the hash of the combined values of ``<namespace>``
+  (which itself has to be a valid UUID) and ``<name>``.
+  The hash algorithm can be either ``MD5`` (Version 3 UUID) or
+  ``SHA1`` (Version 5 UUID).
+  A UUID has the format ``xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx``
+  where each ``x`` represents a lower case hexadecimal character.
+  Where required, an uppercase representation can be requested
+  with the optional ``UPPER`` flag.
 
 .. _JSON:
 
@@ -586,78 +518,75 @@
   option is not present, a fatal error message is generated.  If no error
   occurs, the ``<error-variable>`` will be set to ``NOTFOUND``.
 
-.. _JSON_GET:
-.. code-block:: cmake
-
+.. signature::
   string(JSON <out-var> [ERROR_VARIABLE <error-variable>]
          GET <json-string> <member|index> [<member|index> ...])
+  :target: JSON GET
 
-Get an element from ``<json-string>`` at the location given
-by the list of ``<member|index>`` arguments.
-Array and object elements will be returned as a JSON string.
-Boolean elements will be returned as ``ON`` or ``OFF``.
-Null elements will be returned as an empty string.
-Number and string types will be returned as strings.
+  Get an element from ``<json-string>`` at the location given
+  by the list of ``<member|index>`` arguments.
+  Array and object elements will be returned as a JSON string.
+  Boolean elements will be returned as ``ON`` or ``OFF``.
+  Null elements will be returned as an empty string.
+  Number and string types will be returned as strings.
 
-.. _JSON_TYPE:
-.. code-block:: cmake
-
+.. signature::
   string(JSON <out-var> [ERROR_VARIABLE <error-variable>]
          TYPE <json-string> <member|index> [<member|index> ...])
+  :target: JSON TYPE
 
-Get the type of an element in ``<json-string>`` at the location
-given by the list of ``<member|index>`` arguments. The ``<out-var>``
-will be set to one of ``NULL``, ``NUMBER``, ``STRING``, ``BOOLEAN``,
-``ARRAY``, or ``OBJECT``.
+  Get the type of an element in ``<json-string>`` at the location
+  given by the list of ``<member|index>`` arguments. The ``<out-var>``
+  will be set to one of ``NULL``, ``NUMBER``, ``STRING``, ``BOOLEAN``,
+  ``ARRAY``, or ``OBJECT``.
 
-.. _JSON_MEMBER:
-.. code-block:: cmake
-
+.. signature::
   string(JSON <out-var> [ERROR_VARIABLE <error-var>]
          MEMBER <json-string>
          [<member|index> ...] <index>)
+  :target: JSON MEMBER
 
-Get the name of the ``<index>``-th member in ``<json-string>`` at the location
-given by the list of ``<member|index>`` arguments.
-Requires an element of object type.
+  Get the name of the ``<index>``-th member in ``<json-string>``
+  at the location given by the list of ``<member|index>`` arguments.
+  Requires an element of object type.
 
-.. _JSON_LENGTH:
-.. code-block:: cmake
-
+.. signature::
   string(JSON <out-var> [ERROR_VARIABLE <error-variable>]
          LENGTH <json-string> [<member|index> ...])
+  :target: JSON LENGTH
 
-Get the length of an element in ``<json-string>`` at the location
-given by the list of ``<member|index>`` arguments.
-Requires an element of array or object type.
+  Get the length of an element in ``<json-string>`` at the location
+  given by the list of ``<member|index>`` arguments.
+  Requires an element of array or object type.
 
-.. _JSON_REMOVE:
-.. code-block:: cmake
-
+.. signature::
   string(JSON <out-var> [ERROR_VARIABLE <error-variable>]
          REMOVE <json-string> <member|index> [<member|index> ...])
+  :target: JSON REMOVE
 
-Remove an element from ``<json-string>`` at the location
-given by the list of ``<member|index>`` arguments. The JSON string
-without the removed element will be stored in ``<out-var>``.
+  Remove an element from ``<json-string>`` at the location
+  given by the list of ``<member|index>`` arguments. The JSON string
+  without the removed element will be stored in ``<out-var>``.
 
-.. _JSON_SET:
-.. code-block:: cmake
-
+.. signature::
   string(JSON <out-var> [ERROR_VARIABLE <error-variable>]
          SET <json-string> <member|index> [<member|index> ...] <value>)
+  :target: JSON SET
 
-Set an element in ``<json-string>`` at the location
-given by the list of ``<member|index>`` arguments to ``<value>``.
-The contents of ``<value>`` should be valid JSON.
+  Set an element in ``<json-string>`` at the location
+  given by the list of ``<member|index>`` arguments to ``<value>``.
+  The contents of ``<value>`` should be valid JSON.
+  If ``<json-string>`` is an array, ``<value>`` can be appended to the end of
+  the array by using a number greater or equal to the array length as the
+  ``<member|index>`` argument.
 
-.. _JSON_EQUAL:
-.. code-block:: cmake
-
+.. signature::
   string(JSON <out-var> [ERROR_VARIABLE <error-var>]
          EQUAL <json-string1> <json-string2>)
+  :target: JSON EQUAL
 
-Compare the two JSON objects given by ``<json-string1>`` and ``<json-string2>``
-for equality.  The contents of ``<json-string1>`` and ``<json-string2>``
-should be valid JSON.  The ``<out-var>`` will be set to a true value if the
-JSON objects are considered equal, or a false value otherwise.
+  Compare the two JSON objects given by ``<json-string1>``
+  and ``<json-string2>`` for equality.  The contents of ``<json-string1>``
+  and ``<json-string2>`` should be valid JSON.  The ``<out-var>``
+  will be set to a true value if the JSON objects are considered equal,
+  or a false value otherwise.
diff --git a/Help/command/target_compile_options.rst b/Help/command/target_compile_options.rst
index 698f62d..7cfb24b 100644
--- a/Help/command/target_compile_options.rst
+++ b/Help/command/target_compile_options.rst
@@ -15,6 +15,11 @@
 created by a command such as :command:`add_executable` or
 :command:`add_library` and must not be an :ref:`ALIAS target <Alias Targets>`.
 
+.. note::
+
+  These options are not used when linking the target.
+  See the :command:`target_link_options` command for that.
+
 Arguments
 ^^^^^^^^^
 
@@ -50,9 +55,17 @@
 
 * For file-specific settings, there is the source file property :prop_sf:`COMPILE_OPTIONS`.
 
+* This command adds compile options for all languages in a target.
+  Use the :genex:`COMPILE_LANGUAGE` generator expression to specify
+  per-language compile options.
+
 * :command:`target_compile_features`
 * :command:`target_link_libraries`
 * :command:`target_link_directories`
 * :command:`target_link_options`
 * :command:`target_precompile_headers`
 * :command:`target_sources`
+
+* :variable:`CMAKE_<LANG>_FLAGS` and :variable:`CMAKE_<LANG>_FLAGS_<CONFIG>`
+  add language-wide flags passed to all invocations of the compiler.
+  This includes invocations that drive compiling and those that drive linking.
diff --git a/Help/command/target_link_options.rst b/Help/command/target_link_options.rst
index 0d026f2..dca9598 100644
--- a/Help/command/target_link_options.rst
+++ b/Help/command/target_link_options.rst
@@ -62,3 +62,7 @@
 * :command:`target_link_directories`
 * :command:`target_precompile_headers`
 * :command:`target_sources`
+
+* :variable:`CMAKE_<LANG>_FLAGS` and :variable:`CMAKE_<LANG>_FLAGS_<CONFIG>`
+  add language-wide flags passed to all invocations of the compiler.
+  This includes invocations that drive compiling and those that drive linking.
diff --git a/Help/command/unset.rst b/Help/command/unset.rst
index 1cd1398..522be89 100644
--- a/Help/command/unset.rst
+++ b/Help/command/unset.rst
@@ -12,19 +12,14 @@
 
 Removes a normal variable from the current scope, causing it
 to become undefined.  If ``CACHE`` is present, then a cache variable
-is removed instead of a normal variable.  Note that when evaluating
-:ref:`Variable References` of the form ``${VAR}``, CMake first searches
-for a normal variable with that name.  If no such normal variable exists,
-CMake will then search for a cache entry with that name.  Because of this
-unsetting a normal variable can expose a cache variable that was previously
-hidden.  To force a variable reference of the form ``${VAR}`` to return an
-empty string, use ``set(<variable> "")``, which clears the normal variable
-but leaves it defined.
+is removed instead of a normal variable.
 
 If ``PARENT_SCOPE`` is present then the variable is removed from the scope
 above the current scope.  See the same option in the :command:`set` command
 for further details.
 
+.. include:: UNSET_NOTE.txt
+
 Unset Environment Variable
 ^^^^^^^^^^^^^^^^^^^^^^^^^^
 
diff --git a/Help/cpack_gen/innosetup.rst b/Help/cpack_gen/innosetup.rst
new file mode 100644
index 0000000..f48e7f5
--- /dev/null
+++ b/Help/cpack_gen/innosetup.rst
@@ -0,0 +1,420 @@
+CPack Inno Setup Generator
+--------------------------
+
+.. versionadded:: 3.27
+
+Inno Setup is a free installer for Windows programs by Jordan Russell and
+Martijn Laan (https://jrsoftware.org/isinfo.php).
+
+This documentation explains Inno Setup generator specific options.
+
+The generator provides a lot of options like components. Unfortunately, not
+all features (e.g. component dependencies) are currently supported by
+Inno Setup and they're ignored by the generator for now.
+
+CPack requires Inno Setup 6 or greater and only works on Windows.
+
+Variables specific to CPack Inno Setup generator
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+You can use the following variables to change the behavior of the CPack
+``INNOSETUP`` generator:
+
+
+General
+"""""""
+
+None of the following variables is required to be set for the Inno Setup
+generator to work. If a variable is marked as mandatory below but not set,
+its default value is taken.
+
+The variables can also contain Inno Setup constants like ``{app}``. Please
+refer to the documentation of Inno Setup for more information.
+
+If you're asked to provide the path to any file, you can always give an
+absolute path or in most cases the relative path from the top-level directory
+where all files being installed by an :command:`install` instruction reside.
+
+CPack tries to escape quotes and other special characters for you. However,
+using special characters could cause problems.
+
+The following variable simplifies the usage of Inno Setup in CMake:
+
+.. variable:: CPACK_INNOSETUP_USE_CMAKE_BOOL_FORMAT
+
+ Inno Setup only uses ``yes`` or ``no`` as boolean formats meanwhile CMake
+ uses a lot of alternative formats like ``ON`` or ``OFF``. Having this option
+ turned on enables an automatic conversion.
+
+ Consider the following example:
+
+ .. code-block:: cmake
+
+  set(CMAKE_INNOSETUP_SETUP_AllowNoIcons OFF)
+
+ If this option is turned on, the following line will be created in the output
+ script: ``AllowNoIcons=no``.
+ Else, the following erroneous line will be created: ``AllowNoIcons=OFF``
+
+ The conversion is enabled in every Inno Setup specific variable.
+
+ :Mandatory: Yes
+ :Default: ``ON``
+
+
+Setup Specific Variables
+""""""""""""""""""""""""
+
+.. variable:: CPACK_INNOSETUP_ARCHITECTURE
+
+ One of ``x86``, ``x64``, ``arm64`` or ``ia64``. This variable specifies the
+ target architecture of the installer. This also affects the Program Files
+ folder or registry keys being used.
+
+ CPack tries to determine the correct value with a try compile (see
+ :variable:`CMAKE_SIZEOF_VOID_P`), but this option can be manually specified
+ too (especially when using ``ia64`` or cross-platform compilation).
+
+ :Mandatory: Yes
+ :Default: Either ``x86`` or ``x64`` depending on the results of the try-compile
+
+.. variable:: CPACK_INNOSETUP_INSTALL_ROOT
+
+ If you don't want the installer to create the installation directory under
+ Program Files, you've to specify the installation root here.
+
+ The full directory of the installation will be:
+ ``${CPACK_INNOSETUP_INSTALL_ROOT}/${CPACK_PACKAGE_INSTALL_DIRECTORY}``.
+
+ :Mandatory: Yes
+ :Default: ``{autopf}``
+
+.. variable:: CPACK_INNOSETUP_ALLOW_CUSTOM_DIRECTORY
+
+ If turned on, the installer allows the user to change the installation
+ directory providing an extra wizard page.
+
+ :Mandatory: Yes
+ :Default: ``ON``
+
+.. variable:: CPACK_INNOSETUP_PROGRAM_MENU_FOLDER
+
+ The initial name of the start menu folder being created.
+
+ If this variable is set to ``.``, then no separate folder is created,
+ application shortcuts will appear in the top-level start menu folder.
+
+ :Mandatory: Yes
+ :Default: The value of :variable:`CPACK_PACKAGE_NAME`
+
+.. variable:: CPACK_INNOSETUP_LANGUAGES
+
+ A :ref:`semicolon-separated list <CMake Language Lists>` of languages you want
+ Inno Setup to include.
+
+ Currently available: ``armenian``, ``brazilianPortuguese``, ``bulgarian``,
+ ``catalan``, ``corsican``, ``czech``, ``danish``, ``dutch``, ``english``,
+ ``finnish``, ``french``, ``german``, ``hebrew``, ``icelandic``, ``italian``,
+ ``japanese``, ``norwegian``, ``polish``, ``portuguese``, ``russian``,
+ ``slovak``, ``slovenian``, ``spanish``, ``turkish`` and ``ukrainian``.
+ This list might differ depending on the version of Inno Setup.
+
+ :Mandatory: Yes
+ :Default: ``english``
+
+.. variable:: CPACK_INNOSETUP_IGNORE_LICENSE_PAGE
+
+ If you don't specify a license file using
+ :variable:`CPACK_RESOURCE_FILE_LICENSE`, CPack uses a file for demonstration
+ purposes. If you want the installer to ignore license files at all, you can
+ enable this option.
+
+ :Mandatory: Yes
+ :Default: ``OFF``
+
+.. variable:: CPACK_INNOSETUP_IGNORE_README_PAGE
+
+ If you don't specify a readme file using
+ :variable:`CPACK_RESOURCE_FILE_README`, CPack uses a file for demonstration
+ purposes. If you want the installer to ignore readme files at all, you can
+ enable this option. Make sure the option is disabled when using
+ a custom readme file.
+
+ :Mandatory: Yes
+ :Default: ``ON``
+
+.. variable:: CPACK_INNOSETUP_PASSWORD
+
+ Enables password protection and file encryption with the given password.
+
+ :Mandatory: No
+
+.. variable:: CPACK_INNOSETUP_USE_MODERN_WIZARD
+
+ Enables the modern look and feel provided by Inno Setup. If this option is
+ turned off, the classic style is used instead. Images and icon files are
+ also affected.
+
+ :Mandatory: Yes
+ :Default: ``OFF`` because of compatibility reasons
+
+.. variable:: CPACK_INNOSETUP_ICON_FILE
+
+ The path to a custom installer ``.ico`` file.
+
+ Use :variable:`CPACK_PACKAGE_ICON` to customize the bitmap file being shown
+ in the wizard.
+
+ :Mandatory: No
+
+.. variable:: CPACK_INNOSETUP_SETUP_<directive>
+
+ This group allows adapting any of the ``[Setup]`` section directives provided
+ by Inno Setup where ``directive`` is its name.
+
+ Here are some examples:
+
+ .. code-block:: cmake
+
+  set(CPACK_INNOSETUP_SETUP_WizardSmallImageFile "my_bitmap.bmp")
+  set(CPACK_INNOSETUP_SETUP_AllowNoIcons OFF) # This requires CPACK_INNOSETUP_USE_CMAKE_BOOL_FORMAT to be on
+
+ All of these variables have higher priority than the others.
+ Consider the following example:
+
+ .. code-block:: cmake
+
+  set(CPACK_INNOSETUP_SETUP_Password "admin")
+  set(CPACK_INNOSETUP_PASSWORD "secret")
+
+ The password will be ``admin`` at the end because ``CPACK_INNOSETUP_PASSWORD``
+ has less priority than ``CPACK_INNOSETUP_SETUP_Password``.
+
+ :Mandatory: No
+
+
+File Specific Variables
+"""""""""""""""""""""""
+
+Although all files being installed by an :command:`install` instruction are
+automatically processed and added to the installer, there are some variables
+to customize the installation process.
+
+Before using executables (only ``.exe`` or ``.com``) in shortcuts
+(e.g. :variable:`CPACK_CREATE_DESKTOP_LINKS`) or ``[Run]`` entries, you've to
+add the raw file name (without path and extension) to
+:variable:`CPACK_PACKAGE_EXECUTABLES` and create a start menu shortcut
+for them.
+
+If you have two files with the same raw name (e.g. ``a/executable.exe`` and
+``b/executable.com``), an entry in the section is created twice. This will
+result in undefined behavior and is not recommended.
+
+.. variable:: CPACK_INNOSETUP_CUSTOM_INSTALL_INSTRUCTIONS
+
+ This variable should contain a
+ :ref:`semicolon-separated list <CMake Language Lists>` of pairs ``path``,
+ ``instruction`` and can be used to customize the install command being
+ automatically created for each file or directory.
+
+ CPack creates the following Inno Setup instruction for every file...
+
+ .. code-block::
+
+  Source: "absolute\path\to\my_file.txt"; DestDir: "{app}"; Flags: ignoreversion
+
+ ...and the following line for every directory:
+
+ .. code-block::
+
+  Name: "{app}\my_folder"
+
+ You might want to change the destination directory or the flags of
+ ``my_file.txt``. Since we can also provide a relative path, the line you'd
+ like to have, is the following:
+
+ .. code-block::
+
+  Source: "my_file.txt"; DestDir: "{userdocs}"; Flags: ignoreversion uninsneveruninstall
+
+ You would do this by using ``my_file.txt`` as ``path`` and
+ ``Source: "my_file.txt"; DestDir: "{userdocs}"; Flags: ignoreversion uninsneveruninstall``
+ as ``instruction``.
+
+ You've to take care of the `escaping problem <https://cmake.org/cmake/help/book/mastering-cmake/chapter/Packaging%20With%20CPack.html#adding-custom-cpack-options>`_.
+ So the CMake command would be:
+
+ .. code-block:: cmake
+
+  set(CPACK_INNOSETUP_CUSTOM_INSTALL_INSTRUCTIONS "my_file.txt;Source: \\\"my_file.txt\\\"\\; DestDir: \\\"{userdocs}\\\"\\; Flags: ignoreversion uninsneveruninstall")
+
+ To improve readability, you should go around the escaping problem by using
+ :variable:`CPACK_VERBATIM_VARIABLES` or by placing the instruction into a
+ separate CPack project config file.
+
+ If you customize the install instruction of a specific file, you lose the
+ connection to its component. To go around, manually add
+ ``Components: <component>``. You also need to add its shortcuts and ``[Run]``
+ entries by yourself in a custom section, since the executable won't be found
+ anymore by :variable:`CPACK_PACKAGE_EXECUTABLES`.
+
+ Here's another example (Note: You've to go around the escaping problem for
+ the example to work):
+
+ .. code-block:: cmake
+
+  set(CPACK_INNOSETUP_CUSTOM_INSTALL_INSTRUCTIONS
+      "component1/my_folder" "Name: \"{userdocs}\\my_folder\"\; Components: component1"
+      "component2/my_folder2/my_file.txt" "Source: \"component2\\my_folder2\\my_file.txt\"\; DestDir: \"{app}\\my_folder2\\my_file.txt\"\; Flags: ignoreversion uninsneveruninstall\; Components: component2")
+
+ :Mandatory: No
+
+.. variable:: CPACK_INNOSETUP_MENU_LINKS
+
+ This variable should contain a
+ :ref:`semicolon-separated list <CMake Language Lists>` of pairs ``link``,
+ ``link name`` and can be used to add shortcuts into the start menu folder
+ beside those of the executables (see :variable:`CPACK_PACKAGE_EXECUTABLES`).
+ While ``link name`` is the label, ``link`` can be a URL or a path relative to
+ the installation directory.
+
+ Here's an example:
+
+ .. code-block:: cmake
+
+  set(CPACK_INNOSETUP_MENU_LINKS
+      "doc/cmake-@CMake_VERSION_MAJOR@.@CMake_VERSION_MINOR@/cmake.html"
+      "CMake Help" "https://cmake.org" "CMake Web Site")
+
+ :Mandatory: No
+
+.. variable:: CPACK_INNOSETUP_CREATE_UNINSTALL_LINK
+
+ If this option is turned on, a shortcut to the application's uninstaller is
+ automatically added to the start menu folder.
+
+ :Mandatory: Yes
+ :Default: ``OFF``
+
+.. variable:: CPACK_INNOSETUP_RUN_EXECUTABLES
+
+ A :ref:`semicolon-separated list <CMake Language Lists>` of executables being
+ specified in :variable:`CPACK_PACKAGE_EXECUTABLES` which the user can run
+ when the installer finishes.
+
+ They're internally added to the ``[Run]`` section.
+
+ :Mandatory: No
+
+
+Components Specific Variables
+"""""""""""""""""""""""""""""
+
+The generator supports components and also downloaded components. However,
+there are some features of components that aren't supported yet (especially
+component dependencies). These variables are ignored for now.
+
+CPack will change a component's name in Inno Setup if it has a parent group
+for technical reasons. Consider using ``group\component`` as component name in
+Inno Setup scripts if you have the component ``component`` and its parent
+group ``group``.
+
+Here are some additional variables for components:
+
+.. variable::  CPACK_INNOSETUP_<compName>_INSTALL_DIRECTORY
+
+ If you don't want the component ``compName`` to be installed under ``{app}``,
+ you've to specify its installation directory here.
+
+ :Mandatory: No
+
+.. variable:: CPACK_INNOSETUP_VERIFY_DOWNLOADS
+
+ This option only affects downloaded components.
+
+ If this option is turned on, the hashes of the downloaded archives are
+ calculated during compile and
+ download time. The installer will only proceed if they match.
+
+ :Mandatory: Yes
+ :Default: ``ON``
+
+
+Compilation and Scripting Specific Variables
+""""""""""""""""""""""""""""""""""""""""""""
+
+.. variable:: CPACK_INNOSETUP_EXECUTABLE
+
+ The filename of the Inno Setup Script Compiler command.
+
+ :Mandatory: Yes
+ :Default: ``ISCC``
+
+.. variable:: CPACK_INNOSETUP_EXECUTABLE_ARGUMENTS
+
+ A :ref:`semicolon-separated list <CMake Language Lists>` of extra
+ command-line options for the Inno Setup Script Compiler command.
+
+ For example: ``/Qp;/Smysigntool=$p``
+
+ Take care of the `escaping problem <https://cmake.org/cmake/help/book/mastering-cmake/chapter/Packaging%20With%20CPack.html#adding-custom-cpack-options>`_.
+
+ :Mandatory: No
+
+.. variable:: CPACK_INNOSETUP_DEFINE_<macro>
+
+ This group allows to add custom define directives as command-line options to
+ the Inno Setup Preprocessor command. Each entry emulates a
+ ``#define public <macro>`` directive. Its macro is accessible from anywhere
+ (``public``), so it can also be used in extra script files.
+
+ Macro names must not contain any special characters. Refer to the Inno Setup
+ Preprocessor documentation for the detailed rules.
+
+ Consider the following example:
+
+ .. code-block:: cmake
+
+  # The following line emulates: #define public MyMacro "Hello, World!"
+  set(CPACK_INNOSETUP_DEFINE_MyMacro "Hello, World!")
+
+ At this point, you can use ``MyMacro`` anywhere. For example in the following
+ extra script:
+
+ .. code-block::
+
+  AppComments={#emit "'My Macro' has the value: " + MyMacro}
+
+ Take care of the `escaping problem <https://cmake.org/cmake/help/book/mastering-cmake/chapter/Packaging%20With%20CPack.html#adding-custom-cpack-options>`_.
+
+ :Mandatory: No
+
+.. variable:: CPACK_INNOSETUP_EXTRA_SCRIPTS
+
+ A :ref:`semicolon-separated list <CMake Language Lists>` of paths to
+ additional ``.iss`` script files to be processed.
+
+ They're internally included at the top of the output script file using a
+ ``#include`` directive.
+
+ You can add any section in your file to extend the installer (e.g. adding
+ additional tasks or registry keys). Prefer using
+ :variable:`CPACK_INNOSETUP_SETUP_<directive>` when extending the
+ ``[Setup]`` section.
+
+ :Mandatory: No
+
+.. variable:: CPACK_INNOSETUP_CODE_FILES
+
+ A :ref:`semicolon-separated list <CMake Language Lists>` of paths to
+ additional Pascal files to be processed.
+
+ This variable is actually the same as
+ :variable:`CPACK_INNOSETUP_EXTRA_SCRIPTS`, except you don't have to
+ add ``[Code]`` at the top of your file. Never change the current section in
+ a code file. This will result in undefined behavior! Treat them as normal
+ Pascal scripts instead.
+
+ Code files are included at the very bottom of the output script.
+
+ :Mandatory: No
diff --git a/Help/cpack_gen/nuget.rst b/Help/cpack_gen/nuget.rst
index 1f2e762..8ee2816 100644
--- a/Help/cpack_gen/nuget.rst
+++ b/Help/cpack_gen/nuget.rst
@@ -258,7 +258,7 @@
 
 .. _nuget.org: https://www.nuget.org
 .. _version specification: https://learn.microsoft.com/en-us/nuget/concepts/package-versioning#version-ranges
-.. _SPDX license identifier: https://spdx.github.io/spdx-spec/SPDX-license-list
-.. _SPDX specification: https://spdx.github.io/spdx-spec/SPDX-license-expressions
+.. _SPDX license identifier: https://spdx.org/licenses
+.. _SPDX specification: https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions
 
 .. NuGet spec docs https://docs.microsoft.com/en-us/nuget/reference/nuspec
diff --git a/Help/cpack_gen/wix.rst b/Help/cpack_gen/wix.rst
index c880049..af01252 100644
--- a/Help/cpack_gen/wix.rst
+++ b/Help/cpack_gen/wix.rst
@@ -111,7 +111,7 @@
  simply provide the name of the culture.  If you specify more than one
  culture identifier in a comma or semicolon delimited list, the first one
  that is found will be used.  You can find a list of supported languages at:
- https://wixtoolset.org//documentation/manual/v3/wixui/wixui_localization.html
+ https://wixtoolset.org/docs/v3/wixui/wixui_localization/
 
 .. variable:: CPACK_WIX_TEMPLATE
 
@@ -319,7 +319,7 @@
  for using WiX extensions. Each declaration should be in the form name=url, where
  name is the plain namespace without the usual xmlns: prefix and url is an unquoted
  namespace url. A list of commonly known WiX schemata can be found here:
- https://wixtoolset.org/documentation/manual/v3/xsd/
+ https://wixtoolset.org/docs/v3/xsd/
 
 .. variable:: CPACK_WIX_SKIP_WIX_UI_EXTENSION
 
diff --git a/Help/dev/documentation.rst b/Help/dev/documentation.rst
index db92022..c6fb7a6 100644
--- a/Help/dev/documentation.rst
+++ b/Help/dev/documentation.rst
@@ -168,46 +168,175 @@
  See the `cmake-variables(7)`_ manual
  and the `set()`_ command.
 
-Documentation objects in the CMake Domain come from two sources.
-First, the CMake extension to Sphinx transforms every document named
-with the form ``Help/<type>/<file-name>.rst`` to a domain object with
-type ``<type>``.  The object name is extracted from the document title,
-which is expected to be of the form::
+Documentation objects in the CMake Domain come from two sources:
 
- <object-name>
- -------------
+1. The CMake extension to Sphinx transforms every document named
+   with the form ``Help/<type>/<file-name>.rst`` to a domain object with
+   type ``<type>``.  The object name is extracted from the document title,
+   which is expected to be of the form::
 
-and to appear at or near the top of the ``.rst`` file before any other
-lines starting in a letter, digit, ``<``, or ``$``.  If no such title appears
-literally in the ``.rst`` file, the object name is the ``<file-name>``.
-If a title does appear, it is expected that ``<file-name>`` is equal
-to ``<object-name>`` with any ``<`` and ``>`` characters removed,
-or in the case of a ``$<genex-name>`` or ``$<genex-name:...>``, the
-``genex-name``.
+    <object-name>
+    -------------
 
-Second, the CMake Domain provides directives to define objects inside
-other documents:
+   and to appear at or near the top of the ``.rst`` file before any other lines
+   starting in a letter, digit, ``<``, or ``$``.  If no such title appears
+   literally in the ``.rst`` file, the object name is the ``<file-name>``.
+   If a title does appear, it is expected that ``<file-name>`` is equal
+   to ``<object-name>`` with any ``<`` and ``>`` characters removed,
+   or in the case of a ``$<genex-name>`` or ``$<genex-name:...>``, the
+   ``genex-name``.
+
+2. `CMake Domain directives`_ may be used in documents to explicitly define
+   some object types:
+
+   * `command directive`_
+   * `envvar directive`_
+   * `genex directive`_
+   * `variable directive`_
+
+   Object types for which no directive is available must be defined using
+   the document transform above.
+
+CMake Domain Directives
+-----------------------
+
+The CMake Domain provides the following directives.
+
+``command`` directive
+^^^^^^^^^^^^^^^^^^^^^
+
+Document a "command" object:
 
 .. code-block:: rst
 
- .. command:: <command-name>
+  .. command:: <command-name>
 
-  This indented block documents <command-name>.
+    This indented block documents <command-name>.
 
- .. envvar:: <envvar-name>
+The directive requires a single argument, the command name.
 
-  This indented block documents <envvar-name>.
+``envvar`` directive
+^^^^^^^^^^^^^^^^^^^^
+
+Document an "envvar" object:
+
+.. code-block:: rst
+
+  .. envvar:: <envvar-name>
+
+    This indented block documents <envvar-name>.
+
+The directive requires a single argument, the environment variable name.
+
+``genex`` directive
+^^^^^^^^^^^^^^^^^^^
+
+Document a "genex" object:
+
+.. code-block:: rst
 
  .. genex:: <genex-name>
 
   This indented block documents <genex-name>.
 
+The directive requires a single argument, the generator expression name.
+
+The optional ``:target:`` option allows a custom target name to be specified.
+Because this will affect the ability to reference the "genex" object using the
+``:genex:`` role, this option should be used very sparingly.
+
+``signature`` directive
+^^^^^^^^^^^^^^^^^^^^^^^
+
+Document `CMake Command Signatures <Style: CMake Command Signatures_>`_
+within a ``Help/command/<command-name>.rst`` document.
+
+.. code-block:: rst
+
+  .. signature:: <command-name>(<signature>)
+
+    This indented block documents one or more signatures of a CMake command.
+
+The ``signature`` directive requires one argument, the signature summary:
+
+* One or more signatures must immediately follow the ``::``.
+  The first signature may optionally be placed on the same line.
+  A blank line following the ``signature`` directive will result in a
+  documentation generation error: ``1 argument(s) required, 0 supplied``.
+
+* Signatures may be split across multiple lines, but the final ``)`` of each
+  signature must be the last character on its line.
+
+* Blank lines between signatures are not allowed.  (Content after a blank line
+  is treated as part of the description.)
+
+* Whitespace in signatures is not preserved.  To document a complex signature,
+  abbreviate it in the ``signature`` directive argument and specify the full
+  signature in a ``code-block`` in the description.
+
+The ``signature`` directive generates a hyperlink target for each signature:
+
+* Default target names are automatically extracted from leading "keyword"
+  arguments in the signatures, where a keyword is any sequence of
+  non-space starting with a letter.  For example, the signature
+  ``string(REGEX REPLACE <match-regex> ...)`` generates the target
+  ``REGEX REPLACE``, similar to ``.. _`REGEX REPLACE`:``.
+
+* Custom target names may be specified using a ``:target:`` option.
+  For example:
+
+  .. code-block:: rst
+
+    .. signature::
+      cmake_path(GET <path-var> ROOT_NAME <out-var>)
+      cmake_path(GET <path-var> ROOT_PATH <out-var>)
+      :target:
+        GET ROOT_NAME
+        GET ROOT_PATH
+
+  Provide a custom target name for each signature, one per line.
+  The first target may optionally be placed on the same line as ``:target:``.
+
+* If a target name is already in use earlier in the document, no hyperlink
+  target will be generated.
+
+* The targets may be referenced from within the same document using
+  ```REF`_`` or ```TEXT <REF_>`_`` syntax.  Like reStructuredText section
+  headers, the targets do not work with Sphinx ``:ref:`` syntax, however
+  they can be globally referenced using e.g. ``:command:`string(APPEND)```.
+
+Although whitespace in the signature is not preserved, by default, line breaks
+are suppressed inside of square- or angle-brackets.  This behavior can be
+controlled using the ``:break:`` option; note, however, that there is no way
+to *force* a line break.  The default value is 'smart'.  Allowable values are:
+
+  ``all``
+    Allow line breaks at any whitespace.
+
+  ``smart`` (default)
+    Allow line breaks at whitespace, except between matched square- or
+    angle-brackets.  For example, if a signature contains the text
+    ``<input>... [OUTPUT_VARIABLE <out-var>]``, a line break would be allowed
+    after ``<input>...`` but not between ``OUTPUT_VARIABLE`` and ``<out-var>``.
+
+  ``verbatim``
+    Allow line breaks only where the source document contains a newline.
+
+The directive treats its content as the documentation of the signature(s).
+Indent the signature documentation accordingly.
+
+``variable`` directive
+^^^^^^^^^^^^^^^^^^^^^^
+
+Document a "variable" object:
+
+.. code-block:: rst
+
  .. variable:: <variable-name>
 
   This indented block documents <variable-name>.
 
-Object types for which no directive is available must be defined using
-the first approach above.
+The directive requires a single argument, the variable name.
 
 .. _`Sphinx Domain`: http://sphinx-doc.org/domains.html
 .. _`cmake(1)`: https://cmake.org/cmake/help/latest/manual/cmake.1.html
@@ -266,6 +395,10 @@
 with a space preceding ``<``, is still interpreted as a link text
 with an explicit target.
 
+Additionally, the ``cref`` role may be used to create references
+to local targets that have literal styling.  This is especially
+useful for referencing a subcommand in the command's documentation.
+
 .. _`list()`: https://cmake.org/cmake/help/latest/command/list.html
 .. _`list(APPEND)`: https://cmake.org/cmake/help/latest/command/list.html
 .. _`list(APPEND) sub-command`: https://cmake.org/cmake/help/latest/command/list.html
@@ -329,11 +462,11 @@
 Style: CMake Command Signatures
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Command signatures should be marked up as plain literal blocks, not as
-cmake ``code-blocks``.
-
-Signatures are separated from preceding content by a section header.
-That is, use:
+A ``Help/command/<command-name>.rst`` document defines one ``command``
+object in the `CMake Domain`_, but some commands have multiple signatures.
+Use the CMake Domain's `signature directive`_ to document each signature.
+Separate signatures from preceding content by a section header.
+For example:
 
 .. code-block:: rst
 
@@ -342,17 +475,23 @@
   Normal Libraries
   ^^^^^^^^^^^^^^^^
 
-  ::
-
+  .. signature::
     add_library(<lib> ...)
 
-  This signature is used for ...
+    This signature is used for ...
 
-Signatures of commands should wrap optional parts with square brackets,
-and should mark list of optional arguments with an ellipsis (``...``).
-Elements of the signature which are specified by the user should be
-specified with angle brackets, and may be referred to in prose using
-``inline-literal`` syntax.
+Use the following conventions in command signature documentation:
+
+* Use an angle-bracket ``<placeholder>`` for arguments to be specified
+  by the caller.  Refer to them in prose using
+  `inline literal <Style: Inline Literals_>`_ syntax.
+
+* Wrap optional parts with square brackets.
+
+* Mark repeatable parts with a trailing ellipsis (``...``).
+
+The ``signature`` directive may be used multiple times for different
+signatures of the same command.
 
 Style: Boolean Constants
 ^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/Help/envvar/CMAKE_APPBUNDLE_PATH.rst b/Help/envvar/CMAKE_APPBUNDLE_PATH.rst
new file mode 100644
index 0000000..d80e08d
--- /dev/null
+++ b/Help/envvar/CMAKE_APPBUNDLE_PATH.rst
@@ -0,0 +1,14 @@
+CMAKE_APPBUNDLE_PATH
+--------------------
+
+.. include:: ENV_VAR.txt
+
+The ``CMAKE_APPBUNDLE_PATH`` environment variable may be set to a list of
+directories to be searched for macOS application bundles
+by the :command:`find_program` and :command:`find_package` commands.
+
+This variable may hold a single directory or a list of directories separated
+by ``:`` on UNIX or ``;`` on Windows (the same as the ``PATH`` environment
+variable convention on those platforms).
+
+See also the :variable:`CMAKE_APPBUNDLE_PATH` CMake variable.
diff --git a/Help/envvar/CMAKE_FRAMEWORK_PATH.rst b/Help/envvar/CMAKE_FRAMEWORK_PATH.rst
new file mode 100644
index 0000000..f543132
--- /dev/null
+++ b/Help/envvar/CMAKE_FRAMEWORK_PATH.rst
@@ -0,0 +1,15 @@
+CMAKE_FRAMEWORK_PATH
+--------------------
+
+.. include:: ENV_VAR.txt
+
+The ``CMAKE_FRAMEWORK_PATH`` environment variable may be set to a list of
+directories to be searched for macOS frameworks by the :command:`find_library`,
+:command:`find_package`, :command:`find_path` and :command:`find_file` commands.
+
+
+This variable may hold a single directory or a list of directories separated
+by ``:`` on UNIX or ``;`` on Windows (the same as the ``PATH`` environment
+variable convention on those platforms).
+
+See also the :variable:`CMAKE_FRAMEWORK_PATH` CMake variable.
diff --git a/Help/envvar/CMAKE_INCLUDE_PATH.rst b/Help/envvar/CMAKE_INCLUDE_PATH.rst
new file mode 100644
index 0000000..a42460d
--- /dev/null
+++ b/Help/envvar/CMAKE_INCLUDE_PATH.rst
@@ -0,0 +1,13 @@
+CMAKE_INCLUDE_PATH
+------------------
+
+.. include:: ENV_VAR.txt
+
+The ``CMAKE_INCLUDE_PATH`` environment variable may be set to a list of
+directories to be searched by the :command:`find_file` and :command:`find_path` commands.
+
+This variable may hold a single directory or a list of directories separated
+by ``:`` on UNIX or ``;`` on Windows (the same as the ``PATH`` environment
+variable convention on those platforms).
+
+See also the :variable:`CMAKE_INCLUDE_PATH` CMake variable.
diff --git a/Help/envvar/CMAKE_LANG_IMPLICIT_LINK_DIRECTORIES_EXCLUDE.rst b/Help/envvar/CMAKE_LANG_IMPLICIT_LINK_DIRECTORIES_EXCLUDE.rst
new file mode 100644
index 0000000..36c79fa
--- /dev/null
+++ b/Help/envvar/CMAKE_LANG_IMPLICIT_LINK_DIRECTORIES_EXCLUDE.rst
@@ -0,0 +1,13 @@
+CMAKE_<LANG>_IMPLICIT_LINK_DIRECTORIES_EXCLUDE
+----------------------------------------------
+
+.. versionadded:: 3.27
+
+.. include:: ENV_VAR.txt
+
+A :ref:`semicolon-separated list <CMake Language Lists>` of directories
+to exclude from the :variable:`CMAKE_<LANG>_IMPLICIT_LINK_DIRECTORIES`
+variable when it is automatically detected from the ``<LANG>`` compiler.
+
+This may be used to work around misconfigured compiler drivers that pass
+extraneous implicit link directories to their linker.
diff --git a/Help/envvar/CMAKE_LIBRARY_PATH.rst b/Help/envvar/CMAKE_LIBRARY_PATH.rst
new file mode 100644
index 0000000..a51100d
--- /dev/null
+++ b/Help/envvar/CMAKE_LIBRARY_PATH.rst
@@ -0,0 +1,13 @@
+CMAKE_LIBRARY_PATH
+------------------
+
+.. include:: ENV_VAR.txt
+
+The ``CMAKE_LIBRARY_PATH`` environment variable may be set to a list of
+directories to be searched by the :command:`find_library` command.
+
+This variable may hold a single directory or a list of directories separated
+by ``:`` on UNIX or ``;`` on Windows (the same as the ``PATH`` environment
+variable convention on those platforms).
+
+See also the :variable:`CMAKE_LIBRARY_PATH` CMake variable.
diff --git a/Help/envvar/CMAKE_MAXIMUM_RECURSION_DEPTH.rst b/Help/envvar/CMAKE_MAXIMUM_RECURSION_DEPTH.rst
new file mode 100644
index 0000000..2d65b60
--- /dev/null
+++ b/Help/envvar/CMAKE_MAXIMUM_RECURSION_DEPTH.rst
@@ -0,0 +1,10 @@
+CMAKE_MAXIMUM_RECURSION_DEPTH
+-----------------------------
+
+.. versionadded:: 3.27
+
+.. include:: ENV_VAR.txt
+
+Maximum recursion depth for CMake scripts.  This environment variable is
+used if the :variable:`CMAKE_MAXIMUM_RECURSION_DEPTH` variable is not set.
+See that variable's documentation for details.
diff --git a/Help/envvar/CMAKE_PROGRAM_PATH.rst b/Help/envvar/CMAKE_PROGRAM_PATH.rst
new file mode 100644
index 0000000..bfc7a30
--- /dev/null
+++ b/Help/envvar/CMAKE_PROGRAM_PATH.rst
@@ -0,0 +1,13 @@
+CMAKE_PROGRAM_PATH
+------------------
+
+.. include:: ENV_VAR.txt
+
+The ``CMAKE_PROGRAM_PATH`` environment variable may be set to a list of
+directories to be searched by the :command:`find_program` command.
+
+This variable may hold a single directory or a list of directories separated
+by ``:`` on UNIX or ``;`` on Windows (the same as the ``PATH`` environment
+variable convention on those platforms).
+
+See also the :variable:`CMAKE_PROGRAM_PATH` CMake variable.
diff --git a/Help/envvar/PackageName_ROOT.rst b/Help/envvar/PackageName_ROOT.rst
index fa8c385..6e9c744 100644
--- a/Help/envvar/PackageName_ROOT.rst
+++ b/Help/envvar/PackageName_ROOT.rst
@@ -17,3 +17,17 @@
 variable convention on those platforms).
 
 See also the :variable:`<PackageName>_ROOT` CMake variable.
+
+.. envvar:: <PACKAGENAME>_ROOT
+
+  .. versionadded:: 3.27
+
+  Calls to :command:`find_package(<PackageName>)` will also search in
+  prefixes specified by the upper-case ``<PACKAGENAME>_ROOT`` environment
+  variable.  See policy :policy:`CMP0144`.
+
+.. note::
+
+  Note that the ``<PackageName>_ROOT`` and ``<PACKAGENAME>_ROOT``
+  environment variables are distinct only on platforms that have
+  case-sensitive environments.
diff --git a/Help/generator/CodeBlocks.rst b/Help/generator/CodeBlocks.rst
index 85da715..5c48bc9 100644
--- a/Help/generator/CodeBlocks.rst
+++ b/Help/generator/CodeBlocks.rst
@@ -1,6 +1,12 @@
 CodeBlocks
 ----------
 
+.. deprecated:: 3.27
+
+  Support for :ref:`Extra Generators` is deprecated and will be removed from
+  a future version of CMake.  IDEs may use the :manual:`cmake-file-api(7)`
+  to view CMake-generated project build trees.
+
 Generates CodeBlocks project files.
 
 Project files for CodeBlocks will be created in the top directory and
diff --git a/Help/generator/CodeLite.rst b/Help/generator/CodeLite.rst
index 4f276df..faec9b9 100644
--- a/Help/generator/CodeLite.rst
+++ b/Help/generator/CodeLite.rst
@@ -1,6 +1,12 @@
 CodeLite
 ----------
 
+.. deprecated:: 3.27
+
+  Support for :ref:`Extra Generators` is deprecated and will be removed from
+  a future version of CMake.  IDEs may use the :manual:`cmake-file-api(7)`
+  to view CMake-generated project build trees.
+
 Generates CodeLite project files.
 
 Project files for CodeLite will be created in the top directory and
diff --git a/Help/generator/Eclipse CDT4.rst b/Help/generator/Eclipse CDT4.rst
index 634e2b6..9c1610d 100644
--- a/Help/generator/Eclipse CDT4.rst
+++ b/Help/generator/Eclipse CDT4.rst
@@ -1,6 +1,12 @@
 Eclipse CDT4
 ------------
 
+.. deprecated:: 3.27
+
+  Support for :ref:`Extra Generators` is deprecated and will be removed from
+  a future version of CMake.  IDEs may use the :manual:`cmake-file-api(7)`
+  to view CMake-generated project build trees.
+
 Generates Eclipse CDT 4.0 project files.
 
 Project files for Eclipse will be created in the top directory.  In
diff --git a/Help/generator/Kate.rst b/Help/generator/Kate.rst
index 129bf63..11354f2 100644
--- a/Help/generator/Kate.rst
+++ b/Help/generator/Kate.rst
@@ -1,6 +1,12 @@
 Kate
 ----
 
+.. deprecated:: 3.27
+
+  Support for :ref:`Extra Generators` is deprecated and will be removed from
+  a future version of CMake.  IDEs may use the :manual:`cmake-file-api(7)`
+  to view CMake-generated project build trees.
+
 Generates Kate project files.
 
 A project file for Kate will be created in the top directory in the top level
@@ -22,5 +28,8 @@
 ``Kate - Ninja``
  Generate with :generator:`Ninja`.
 
+``Kate - Ninja Multi-Config``
+ Generate with :generator:`Ninja Multi-Config`.
+
 ``Kate - Unix Makefiles``
  Generate with :generator:`Unix Makefiles`.
diff --git a/Help/generator/Sublime Text 2.rst b/Help/generator/Sublime Text 2.rst
index 0a07ea9..a45ab08 100644
--- a/Help/generator/Sublime Text 2.rst
+++ b/Help/generator/Sublime Text 2.rst
@@ -1,6 +1,12 @@
 Sublime Text 2
 --------------
 
+.. deprecated:: 3.27
+
+  Support for :ref:`Extra Generators` is deprecated and will be removed from
+  a future version of CMake.  IDEs may use the :manual:`cmake-file-api(7)`
+  to view CMake-generated project build trees.
+
 Generates Sublime Text 2 project files.
 
 Project files for Sublime Text 2 will be created in the top directory
diff --git a/Help/generator/Visual Studio 9 2008.rst b/Help/generator/Visual Studio 9 2008.rst
index 3434956..816969d 100644
--- a/Help/generator/Visual Studio 9 2008.rst
+++ b/Help/generator/Visual Studio 9 2008.rst
@@ -1,7 +1,15 @@
 Visual Studio 9 2008
 --------------------
 
-Generates Visual Studio 9 2008 project files.
+Deprecated.  Generates Visual Studio 9 2008 project files.
+
+.. note::
+  This generator is deprecated and will be removed in a future version
+  of CMake.  It will still be possible to build with VS 9 2008 tools
+  using the :generator:`Visual Studio 12 2013` generator (or above,
+  and with VS 10 2010 also installed) with
+  :variable:`CMAKE_GENERATOR_TOOLSET` set to ``v90``,
+  or by using the :generator:`NMake Makefiles` generator.
 
 Platform Selection
 ^^^^^^^^^^^^^^^^^^
diff --git a/Help/guide/ide-integration/index.rst b/Help/guide/ide-integration/index.rst
index e198789..3a11a34 100644
--- a/Help/guide/ide-integration/index.rst
+++ b/Help/guide/ide-integration/index.rst
@@ -145,7 +145,7 @@
 * `VSCode`_ (via a plugin)
 
 .. _CLion: https://www.jetbrains.com/clion/
-.. _KDevelop: https://www.kdevelop.org/
+.. _KDevelop: https://kdevelop.org/
 .. _QtCreator: https://www.qt.io/product/development-tools
 .. _Vim: https://www.vim.org/
 .. _Visual Studio: https://visualstudio.microsoft.com/
diff --git a/Help/guide/importing-exporting/index.rst b/Help/guide/importing-exporting/index.rst
index 51a09c0..b1812c1 100644
--- a/Help/guide/importing-exporting/index.rst
+++ b/Help/guide/importing-exporting/index.rst
@@ -285,9 +285,9 @@
   :end-before: # include CMakePackageConfigHelpers macro
 
 This command generates the ``MathFunctionsTargets.cmake`` file and arranges
-to install it to ``lib/cmake``. The file contains code suitable for
-use by downstreams to import all targets listed in the install command from
-the installation tree.
+to install it to ``${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions``. The file
+contains code suitable for use by downstreams to import all targets listed in
+the install command from the installation tree.
 
 The ``NAMESPACE`` option will prepend ``MathFunctions::`` to  the target names
 as they are written to the export file. This convention of double-colons
@@ -317,7 +317,8 @@
 .. code-block:: cmake
   :linenos:
 
-   include(${INSTALL_PREFIX}/lib/cmake/MathFunctionTargets.cmake)
+   include(GNUInstallDirs)
+   include(${INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/cmake/MathFunctions/MathFunctionTargets.cmake)
    add_executable(myexe src1.c src2.c )
    target_link_libraries(myexe PRIVATE MathFunctions::MathFunctions)
 
diff --git a/Help/guide/tutorial/A Basic Starting Point.rst b/Help/guide/tutorial/A Basic Starting Point.rst
index 3dac68a..37b0668 100644
--- a/Help/guide/tutorial/A Basic Starting Point.rst
+++ b/Help/guide/tutorial/A Basic Starting Point.rst
@@ -160,7 +160,7 @@
   :name: CMakeLists.txt-add_executable
   :language: cmake
   :start-after: # add the executable
-  :end-before: # TODO 9:
+  :end-before: # TODO 3:
 
 .. raw:: html
 
@@ -240,7 +240,7 @@
   :name: tutorial.cxx-cxx11
   :language: c++
   :start-after: // convert input to double
-  :end-before: // TODO 12:
+  :end-before: // TODO 6:
 
 .. raw:: html
 
@@ -265,7 +265,7 @@
   :name: CMakeLists.txt-CXX_STANDARD
   :language: cmake
   :start-after: # specify the C++ standard
-  :end-before: # TODO 7:
+  :end-before: # configure a header file
 
 .. raw:: html
 
@@ -375,7 +375,7 @@
   :name: CMakeLists.txt-configure_file
   :language: cmake
   :start-after: # to the source code
-  :end-before: # TODO 8:
+  :end-before: # TODO 2:
 
 .. raw:: html
 
@@ -420,7 +420,6 @@
   :caption: TODO 10: TutorialConfig.h.in
   :name: TutorialConfig.h.in
   :language: c++
-  :end-before: // TODO 13:
 
 .. raw:: html
 
diff --git a/Help/guide/tutorial/Adding Generator Expressions.rst b/Help/guide/tutorial/Adding Generator Expressions.rst
index 6f9714e..3dab97f 100644
--- a/Help/guide/tutorial/Adding Generator Expressions.rst
+++ b/Help/guide/tutorial/Adding Generator Expressions.rst
@@ -27,149 +27,7 @@
 empty string, and ``<1:...>`` results in the content of ``...``.  They can also
 be nested.
 
-Exercise 1 - Setting the C++ Standard with Interface Libraries
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Before we use :manual:`generator expressions <cmake-generator-expressions(7)>`
-let's refactor our existing code to use an ``INTERFACE`` library. We will
-use that library in the next step to demonstrate a common use for
-:manual:`generator expressions <cmake-generator-expressions(7)>`.
-
-Goal
-----
-
-Add an ``INTERFACE`` library target to specify the required C++ standard.
-
-Helpful Resources
------------------
-
-* :command:`add_library`
-* :command:`target_compile_features`
-* :command:`target_link_libraries`
-
-Files to Edit
--------------
-
-* ``CMakeLists.txt``
-* ``MathFunctions/CMakeLists.txt``
-
-Getting Started
----------------
-
-In this exercise, we will refactor our code to use an ``INTERFACE`` library to
-specify the C++ standard.
-
-The starting source code is provided in the ``Step4`` directory. In this
-exercise, complete ``TODO 1`` through ``TODO 3``.
-
-Start by editing the top level ``CMakeLists.txt`` file. Construct an
-``INTERFACE`` library target called ``tutorial_compiler_flags`` and
-specify ``cxx_std_11`` as a target compiler feature.
-
-Modify ``CMakeLists.txt`` and ``MathFunctions/CMakeLists.txt`` so that all
-targets have a :command:`target_link_libraries` call to
-``tutorial_compiler_flags``.
-
-Build and Run
--------------
-
-Make a new directory called ``Step4_build``, run the :manual:`cmake <cmake(1)>`
-executable or the :manual:`cmake-gui <cmake-gui(1)>` to configure the project
-and then build it with your chosen build tool or by using ``cmake --build .``
-from the build directory.
-
-Here's a refresher of what that looks like from the command line:
-
-.. code-block:: console
-
-  mkdir Step4_build
-  cd Step4_build
-  cmake ../Step4
-  cmake --build .
-
-Next, use the newly built ``Tutorial`` and verify that it is working as
-expected.
-
-Solution
---------
-
-Let's update our code from the previous step to use interface libraries
-to set our C++ requirements.
-
-To start, we need to remove the two :command:`set` calls on the variables
-:variable:`CMAKE_CXX_STANDARD` and :variable:`CMAKE_CXX_STANDARD_REQUIRED`.
-The specific lines to remove are as follows:
-
-.. literalinclude:: Step4/CMakeLists.txt
-  :caption: CMakeLists.txt
-  :name: CMakeLists.txt-CXX_STANDARD-variable-remove
-  :language: cmake
-  :start-after: # specify the C++ standard
-  :end-before: # TODO 5: Create helper variables
-
-Next, we need to create an interface library, ``tutorial_compiler_flags``. And
-then use :command:`target_compile_features` to add the compiler feature
-``cxx_std_11``.
-
-
-.. raw:: html
-
-  <details><summary>TODO 1: Click to show/hide answer</summary>
-
-.. literalinclude:: Step5/CMakeLists.txt
-  :caption: TODO 1: CMakeLists.txt
-  :name: CMakeLists.txt-cxx_std-feature
-  :language: cmake
-  :start-after: # specify the C++ standard
-  :end-before: # add compiler warning flags just
-
-.. raw:: html
-
-  </details>
-
-Finally, with our interface library set up, we need to link our
-executable ``Target`` and our ``MathFunctions`` library to our new
-``tutorial_compiler_flags`` library. Respectively, the code will look like
-this:
-
-.. raw:: html
-
-  <details><summary>TODO 2: Click to show/hide answer</summary>
-
-.. literalinclude:: Step5/CMakeLists.txt
-  :caption: TODO 2: CMakeLists.txt
-  :name: CMakeLists.txt-target_link_libraries-step4
-  :language: cmake
-  :start-after: add_executable(Tutorial tutorial.cxx)
-  :end-before: # add the binary tree to the search path for include file
-
-.. raw:: html
-
-  </details>
-
-and this:
-
-.. raw:: html
-
-  <details><summary>TODO 3: Click to show/hide answer</summary>
-
-.. literalinclude:: Step5/MathFunctions/CMakeLists.txt
-  :caption: TODO 3: MathFunctions/CMakeLists.txt
-  :name: MathFunctions-CMakeLists.txt-target_link_libraries-step4
-  :language: cmake
-  :start-after: # link our compiler flags interface library
-  :end-before: # TODO 1
-
-.. raw:: html
-
-  </details>
-
-With this, all of our code still requires C++ 11 to build. Notice
-though that with this method, it gives us the ability to be specific about
-which targets get specific requirements. In addition, we create a single
-source of truth in our interface library.
-
-Exercise 2 - Adding Compiler Warning Flags with Generator Expressions
+Exercise 1 - Adding Compiler Warning Flags with Generator Expressions
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 A common usage of
@@ -199,8 +57,8 @@
 Getting Started
 ---------------
 
-Start with the resulting files from Exercise 1. Complete ``TODO 4`` through
-``TODO 7``.
+Open the file ``Step4/CMakeLists.txt`` and complete ``TODO 1`` through
+``TODO 4``.
 
 First, in the top level ``CMakeLists.txt`` file, we need to set the
 :command:`cmake_minimum_required` to ``3.15``. In this exercise we are going
@@ -214,12 +72,16 @@
 Build and Run
 -------------
 
-Since we have our build directory already configured from Exercise 1, simply
-rebuild our code by calling the following:
+Make a new directory called ``Step4_build``, run the :manual:`cmake <cmake(1)>`
+executable or the :manual:`cmake-gui <cmake-gui(1)>` to configure the project
+and then build it with your chosen build tool or by using ``cmake --build .``
+from the build directory.
 
 .. code-block:: console
 
+  mkdir Step4_build
   cd Step4_build
+  cmake ../Step4
   cmake --build .
 
 Solution
@@ -230,10 +92,10 @@
 
 .. raw:: html
 
-  <details><summary>TODO 4: Click to show/hide answer</summary>
+  <details><summary>TODO 1: Click to show/hide answer</summary>
 
 .. literalinclude:: Step5/CMakeLists.txt
-  :caption: TODO 4: CMakeLists.txt
+  :caption: TODO 1: CMakeLists.txt
   :name: MathFunctions-CMakeLists.txt-minimum-required-step4
   :language: cmake
   :end-before: # set the project name and version
@@ -249,10 +111,10 @@
 
 .. raw:: html
 
-  <details><summary>TODO 5: Click to show/hide answer</summary>
+  <details><summary>TODO 2: Click to show/hide answer</summary>
 
 .. literalinclude:: Step5/CMakeLists.txt
-  :caption: TODO 5: CMakeLists.txt
+  :caption: TODO 2: CMakeLists.txt
   :name: CMakeLists.txt-compile_lang_and_id
   :language: cmake
   :start-after: # the BUILD_INTERFACE genex
@@ -270,10 +132,10 @@
 
 .. raw:: html
 
-  <details><summary>TODO 6: Click to show/hide answer</summary>
+  <details><summary>TODO 3: Click to show/hide answer</summary>
 
 .. code-block:: cmake
-  :caption: TODO 6: CMakeLists.txt
+  :caption: TODO 3: CMakeLists.txt
   :name: CMakeLists.txt-compile_flags
 
   target_compile_options(tutorial_compiler_flags INTERFACE
@@ -292,14 +154,14 @@
 
 .. raw:: html
 
-  <details><summary>TODO 7: Click to show/hide answer</summary>
+  <details><summary>TODO 4: Click to show/hide answer</summary>
 
 .. literalinclude:: Step5/CMakeLists.txt
-  :caption: TODO 7: CMakeLists.txt
+  :caption: TODO 4: CMakeLists.txt
   :name: CMakeLists.txt-target_compile_options-genex
   :language: cmake
   :start-after: set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")
-  :end-before: # should we use our own math functions
+  :end-before: # configure a header file to pass some of the CMake settings
 
 .. raw:: html
 
diff --git a/Help/guide/tutorial/Adding System Introspection.rst b/Help/guide/tutorial/Adding System Introspection.rst
index b69abd2..b314773 100644
--- a/Help/guide/tutorial/Adding System Introspection.rst
+++ b/Help/guide/tutorial/Adding System Introspection.rst
@@ -119,7 +119,7 @@
   :name: MathFunctions/CMakeLists.txt-target_compile_definitions
   :language: cmake
   :start-after: # add compile definitions
-  :end-before: # install libs
+  :end-before: # state
 
 .. raw:: html
 
@@ -136,7 +136,8 @@
   :caption: TODO 4: MathFunctions/mysqrt.cxx
   :name: MathFunctions/mysqrt.cxx-include-cmath
   :language: c++
-  :end-before: #include <iostream>
+  :start-after: #include "mysqrt.h"
+  :end-before: include <iostream>
 
 .. raw:: html
 
diff --git a/Help/guide/tutorial/Adding Usage Requirements for a Library.rst b/Help/guide/tutorial/Adding Usage Requirements for a Library.rst
index 4aef050..2273063 100644
--- a/Help/guide/tutorial/Adding Usage Requirements for a Library.rst
+++ b/Help/guide/tutorial/Adding Usage Requirements for a Library.rst
@@ -100,7 +100,7 @@
   :name: MathFunctions/CMakeLists.txt-target_include_directories-INTERFACE
   :language: cmake
   :start-after: # to find MathFunctions.h
-  :end-before: # TODO 3: Link to
+  :end-before: # should we use our own
 
 .. raw:: html
 
@@ -108,24 +108,26 @@
 
 Now that we've specified usage requirements for ``MathFunctions`` we can
 safely remove our uses of the ``EXTRA_INCLUDES`` variable from the top-level
-``CMakeLists.txt``, here:
+``CMakeLists.txt``.
+
+Remove this line:
 
 .. raw:: html
 
   <details><summary>TODO 2: Click to show/hide answer</summary>
 
-.. literalinclude:: Step4/CMakeLists.txt
+.. literalinclude:: Step3/CMakeLists.txt
   :caption: TODO 2: CMakeLists.txt
   :name: CMakeLists.txt-remove-EXTRA_INCLUDES
   :language: cmake
-  :start-after: # add the MathFunctions library
+  :start-after: add_subdirectory(MathFunctions)
   :end-before: # add the executable
 
 .. raw:: html
 
   </details>
 
-And here:
+And the lines:
 
 .. raw:: html
 
@@ -141,7 +143,181 @@
 
   </details>
 
+The remaining code looks like:
+
+.. raw:: html
+
+  <details><summary>Click to show/hide the resulting code</summary>
+
+.. literalinclude:: Step4/CMakeLists.txt
+  :caption: Remaining code after removing EXTRA_INCLUDES
+  :name: CMakeLists.txt-after-removing-EXTRA_INCLUDES
+  :language: cmake
+  :start-after: add_subdirectory(MathFunctions)
+
+.. raw:: html
+
+  </details>
+
+
 Notice that with this technique, the only thing our executable target does to
 use our library is call :command:`target_link_libraries` with the name
 of the library target. In larger projects, the classic method of specifying
 library dependencies manually becomes very complicated very quickly.
+
+Exercise 2 - Setting the C++ Standard with Interface Libraries
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Now that we have switched our code to a more modern approach, let's demonstrate
+a modern technique to set properties to multiple targets.
+
+Let's refactor our existing code to use an ``INTERFACE`` library. We will
+use that library in the next step to demonstrate a common use for
+:manual:`generator expressions <cmake-generator-expressions(7)>`.
+
+Goal
+----
+
+Add an ``INTERFACE`` library target to specify the required C++ standard.
+
+Helpful Resources
+-----------------
+
+* :command:`add_library`
+* :command:`target_compile_features`
+* :command:`target_link_libraries`
+
+Files to Edit
+-------------
+
+* ``CMakeLists.txt``
+* ``MathFunctions/CMakeLists.txt``
+
+Getting Started
+---------------
+
+In this exercise, we will refactor our code to use an ``INTERFACE`` library to
+specify the C++ standard.
+
+Start this exercise from what we left at the end of Step3 exercise 1. You will
+have to complete ``TODO 4`` through ``TODO 7``.
+
+Start by editing the top level ``CMakeLists.txt`` file. Construct an
+``INTERFACE`` library target called ``tutorial_compiler_flags`` and
+specify ``cxx_std_11`` as a target compiler feature.
+
+Modify ``CMakeLists.txt`` and ``MathFunctions/CMakeLists.txt`` so that all
+targets have a :command:`target_link_libraries` call to
+``tutorial_compiler_flags``.
+
+Build and Run
+-------------
+
+Since we have our build directory already configured from Exercise 1, simply
+rebuild our code by calling the following:
+
+.. code-block:: console
+
+  cd Step3_build
+  cmake --build .
+
+Next, use the newly built ``Tutorial`` and verify that it is working as
+expected.
+
+Solution
+--------
+
+Let's update our code from the previous step to use interface libraries
+to set our C++ requirements.
+
+To start, we need to remove the two :command:`set` calls on the variables
+:variable:`CMAKE_CXX_STANDARD` and :variable:`CMAKE_CXX_STANDARD_REQUIRED`.
+The specific lines to remove are as follows:
+
+.. literalinclude:: Step3/CMakeLists.txt
+  :caption: CMakeLists.txt
+  :name: CMakeLists.txt-CXX_STANDARD-variable-remove
+  :language: cmake
+  :start-after: # specify the C++ standard
+  :end-before: # configure a header file
+
+Next, we need to create an interface library, ``tutorial_compiler_flags``. And
+then use :command:`target_compile_features` to add the compiler feature
+``cxx_std_11``.
+
+
+.. raw:: html
+
+  <details><summary>TODO 4: Click to show/hide answer</summary>
+
+.. literalinclude:: Step4/CMakeLists.txt
+  :caption: TODO 4: CMakeLists.txt
+  :name: CMakeLists.txt-cxx_std-feature
+  :language: cmake
+  :start-after: # specify the C++ standard
+  :end-before: # TODO 2: Create helper
+
+.. raw:: html
+
+  </details>
+
+Finally, with our interface library set up, we need to link our
+executable ``Target``, our ``MathFunctions`` library, and our ``SqrtLibrary``
+library to our new
+``tutorial_compiler_flags`` library. Respectively, the code will look like
+this:
+
+.. raw:: html
+
+  <details><summary>TODO 5: Click to show/hide answer</summary>
+
+.. literalinclude:: Step4/CMakeLists.txt
+  :caption: TODO 5: CMakeLists.txt
+  :name: CMakeLists.txt-target_link_libraries-step4
+  :language: cmake
+  :start-after: add_executable(Tutorial tutorial.cxx)
+  :end-before: # add the binary tree to the search path for include file
+
+.. raw:: html
+
+  </details>
+
+this:
+
+.. raw:: html
+
+  <details><summary>TODO 6: Click to show/hide answer</summary>
+
+.. literalinclude:: Step4/MathFunctions/CMakeLists.txt
+  :caption: TODO 6: MathFunctions/CMakeLists.txt
+  :name: MathFunctions-CMakeLists.txt-target_link_libraries-step4
+  :language: cmake
+  :start-after: # link our compiler flags interface library
+  :end-before: target_link_libraries(MathFunctions
+
+.. raw:: html
+
+  </details>
+
+and this:
+
+.. raw:: html
+
+  <details><summary>TODO 7: Click to show/hide answer</summary>
+
+.. literalinclude:: Step4/MathFunctions/CMakeLists.txt
+  :caption: TODO 7: MathFunctions/CMakeLists.txt
+  :name: MathFunctions-SqrtLibrary-target_link_libraries-step4
+  :language: cmake
+  :start-after: target_link_libraries(SqrtLibrary
+  :end-before: endif()
+
+.. raw:: html
+
+  </details>
+
+
+With this, all of our code still requires C++ 11 to build. Notice
+though that with this method, it gives us the ability to be specific about
+which targets get specific requirements. In addition, we create a single
+source of truth in our interface library.
diff --git a/Help/guide/tutorial/Adding a Custom Command and Generated File.rst b/Help/guide/tutorial/Adding a Custom Command and Generated File.rst
index b1f9840..c71a889 100644
--- a/Help/guide/tutorial/Adding a Custom Command and Generated File.rst
+++ b/Help/guide/tutorial/Adding a Custom Command and Generated File.rst
@@ -18,41 +18,49 @@
 After reviewing the file, we can see that the table is produced as valid C++
 code and that the output filename is passed in as an argument.
 
-The next step is to add the appropriate commands to the
-``MathFunctions/CMakeLists.txt`` file to build the MakeTable executable and
+The next step is to create ``MathFunctions/MakeTable.cmake``. Then, add the
+appropriate commands to the file to build the ``MakeTable`` executable and
 then run it as part of the build process. A few commands are needed to
 accomplish this.
 
-First, at the top of ``MathFunctions/CMakeLists.txt``, the executable for
-``MakeTable`` is added as any other executable would be added.
+First, we add an executable for ``MakeTable``.
 
-.. literalinclude:: Step9/MathFunctions/CMakeLists.txt
-  :caption: MathFunctions/CMakeLists.txt
-  :name: MathFunctions/CMakeLists.txt-add_executable-MakeTable
+.. literalinclude:: Step9/MathFunctions/MakeTable.cmake
+  :caption: MathFunctions/MakeTable.cmake
+  :name: MathFunctions/MakeTable.cmake-add_executable-MakeTable
   :language: cmake
   :start-after: # first we add the executable that generates the table
-  :end-before: # add the command to generate the source code
+  :end-before: target_link_libraries
+
+After creating the executable, we add the ``tutorial_compiler_flags`` to our
+executable using :command:`target_link_libraries`.
+
+.. literalinclude:: Step9/MathFunctions/MakeTable.cmake
+  :caption: MathFunctions/MakeTable.cmake
+  :name: MathFunctions/MakeTable.cmake-link-tutorial-compiler-flags
+  :language: cmake
+  :start-after: add_executable
+  :end-before: # add the command to generate
 
 Then we add a custom command that specifies how to produce ``Table.h``
 by running MakeTable.
 
-.. literalinclude:: Step9/MathFunctions/CMakeLists.txt
-  :caption: MathFunctions/CMakeLists.txt
-  :name: MathFunctions/CMakeLists.txt-add_custom_command-Table.h
+.. literalinclude:: Step9/MathFunctions/MakeTable.cmake
+  :caption: MathFunctions/MakeTable.cmake
+  :name: MathFunctions/MakeTable.cmake-add_custom_command-Table.h
   :language: cmake
   :start-after: # add the command to generate the source code
-  :end-before: # add the main library
 
 Next we have to let CMake know that ``mysqrt.cxx`` depends on the generated
 file ``Table.h``. This is done by adding the generated ``Table.h`` to the list
-of sources for the library MathFunctions.
+of sources for the library ``SqrtLibrary``.
 
 .. literalinclude:: Step9/MathFunctions/CMakeLists.txt
   :caption: MathFunctions/CMakeLists.txt
   :name: MathFunctions/CMakeLists.txt-add_library-Table.h
   :language: cmake
-  :start-after: # add the main library
-  :end-before: # state that anybody linking
+  :start-after:   # library that just does sqrt
+  :end-before: # state that we depend on
 
 We also have to add the current binary directory to the list of include
 directories so that ``Table.h`` can be found and included by ``mysqrt.cxx``.
@@ -62,7 +70,17 @@
   :name: MathFunctions/CMakeLists.txt-target_include_directories-Table.h
   :language: cmake
   :start-after: # state that we depend on our bin
-  :end-before: # install libs
+  :end-before: target_link_libraries
+
+As the last step, we need to include
+``MakeTable.cmake`` at the top of the ``MathFunctions/CMakeLists.txt``.
+
+.. literalinclude:: Step9/MathFunctions/CMakeLists.txt
+  :caption: MathFunctions/CMakeLists.txt
+  :name: MathFunctions/CMakeLists.txt-include-MakeTable.cmake
+  :language: cmake
+  :start-after: # generate Table.h
+  :end-before: # library that just does sqrt
 
 Now let's use the generated table. First, modify ``mysqrt.cxx`` to include
 ``Table.h``. Next, we can rewrite the ``mysqrt`` function to use the table:
diff --git a/Help/guide/tutorial/Adding a Library.rst b/Help/guide/tutorial/Adding a Library.rst
index a56c327..694dfaf 100644
--- a/Help/guide/tutorial/Adding a Library.rst
+++ b/Help/guide/tutorial/Adding a Library.rst
@@ -51,11 +51,13 @@
 the compiler.
 
 For this tutorial we will put the library into a subdirectory called
-``MathFunctions``. This directory already contains a header file,
-``MathFunctions.h``, and a source file ``mysqrt.cxx``. We will not need to
-modify either of these files. The source file has one function called
+``MathFunctions``. This directory already contains the header files
+``MathFunctions.h`` and ``mysqrt.h``. Their respective source files
+``MathFunctions.cxx`` and ``mysqrt.cxx`` are also provided. We will not need
+to modify any of these files. ``mysqrt.cxx`` has one function called
 ``mysqrt`` that provides similar functionality to the compiler's ``sqrt``
-function.
+function. ``MathFunctions.cxx`` contains one function ``sqrt`` which serves
+to hide the implementation details of ``sqrt``.
 
 From the ``Help/guide/tutorial/Step2`` directory, start with ``TODO 1`` and
 complete through ``TODO 6``.
@@ -91,18 +93,18 @@
 
 In the ``CMakeLists.txt`` file in the ``MathFunctions`` directory, we create
 a library target called ``MathFunctions`` with :command:`add_library`. The
-source file for the library is passed as an argument to
+source files for the library are passed as an argument to
 :command:`add_library`. This looks like the following line:
 
-.. raw:: html
+.. raw:: html/
 
   <details><summary>TODO 1: Click to show/hide answer</summary>
 
-.. literalinclude:: Step3/MathFunctions/CMakeLists.txt
+.. code-block:: cmake
   :caption: TODO 1: MathFunctions/CMakeLists.txt
   :name: MathFunctions/CMakeLists.txt-add_library
-  :language: cmake
-  :end-before: # TODO 1
+
+  add_library(MathFunctions MathFunctions.cxx mysqrt.cxx)
 
 .. raw:: html
 
@@ -171,36 +173,40 @@
 
   <details><summary>TODO 5: Click to show/hide answer</summary>
 
-.. code-block:: c++
-  :caption: TODO 5 : tutorial.cxx
-  :name: tutorial.cxx-include_MathFunctions.h
-
-  #include "MathFunctions.h"
+.. literalinclude:: Step3/tutorial.cxx
+  :caption: TODO 5: tutorial.cxx
+  :name: CMakeLists.txt-include-MathFunctions.h
+  :language: cmake
+  :start-after: #include <string>
+  :end-before: #include "TutorialConfig.h"
 
 .. raw:: html
 
   </details>
 
-Lastly, replace ``sqrt`` with our library function ``mysqrt``.
+Lastly, replace ``sqrt`` with our library function ``mathfunctions::mysqrt``.
 
 .. raw:: html
 
   <details><summary>TODO 6: Click to show/hide answer</summary>
 
-.. code-block:: c++
-  :caption: TODO 6 : tutorial.cxx
-  :name: tutorial.cxx-call_mysqrt
-
-  const double outputValue = mysqrt(inputValue);
+.. literalinclude:: Step3/tutorial.cxx
+  :caption: TODO 6: tutorial.cxx
+  :name: CMakeLists.txt-option
+  :language: cmake
+  :start-after:   const double inputValue = std::stod(argv[1]);
+  :end-before: std::cout
 
 .. raw:: html
 
   </details>
 
-Exercise 2 - Making Our Library Optional
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Exercise 2 - Adding an Option
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Now let us make the MathFunctions library optional. While for the tutorial
+Now let us add an option in the MathFunctions library to allow developers to
+select either the custom square root implementation or the built in standard
+implementation. While for the tutorial
 there really isn't any need to do so, for larger projects this is a common
 occurrence.
 
@@ -219,30 +225,32 @@
 -----------------
 
 * :command:`if`
-* :command:`list`
 * :command:`option`
-* :command:`cmakedefine <configure_file>`
+* :command:`target_compile_definitions`
 
 Files to Edit
 -------------
 
-* ``CMakeLists.txt``
-* ``tutorial.cxx``
-* ``TutorialConfig.h.in``
+* ``MathFunctions/CMakeLists.txt``
+* ``MathFunctions/MathFunctions.cxx``
 
 Getting Started
 ---------------
 
 Start with the resulting files from Exercise 1. Complete ``TODO 7`` through
-``TODO 13``.
+``TODO 14``.
 
 First create a variable ``USE_MYMATH`` using the :command:`option` command
-in the top-level ``CMakeLists.txt`` file. In that same file, use that option
-to determine whether to build and use the ``MathFunctions`` library.
+in ``MathFunctions/CMakeLists.txt``. In that same file, use that option
+to pass a compile definition to the ``MathFunctions`` library.
 
-Then, update ``tutorial.cxx`` and ``TutorialConfig.h.in`` to use
+Then, update ``MathFunctions.cxx`` to redirect compilation based on
 ``USE_MYMATH``.
 
+Lastly, prevent ``mysqrt.cxx`` from being compiled when ``USE_MYMATH`` is on
+by making it its own library inside of the ``USE_MYMATH`` block of
+``MathFunctions/CMakeLists.txt``.
+
 Build and Run
 -------------
 
@@ -279,7 +287,7 @@
 Solution
 --------
 
-The first step is to add an option to the top-level ``CMakeLists.txt`` file.
+The first step is to add an option to ``MathFunctions/CMakeLists.txt``.
 This option will be displayed in the :manual:`cmake-gui <cmake-gui(1)>` and
 :manual:`ccmake <ccmake(1)>` with a default value of ``ON`` that can be
 changed by the user.
@@ -288,172 +296,160 @@
 
   <details><summary>TODO 7: Click to show/hide answer</summary>
 
-.. literalinclude:: Step3/CMakeLists.txt
-  :caption: TODO 7: CMakeLists.txt
-  :name: CMakeLists.txt-option
+.. literalinclude:: Step3/MathFunctions/CMakeLists.txt
+  :caption: TODO 7: MathFunctions/CMakeLists.txt
+  :name: CMakeLists.txt-option-library-level
   :language: cmake
   :start-after: # should we use our own math functions
-  :end-before: # configure a header file to pass some of the CMake settings
+  :end-before: if (USE_MYMATH)
 
 .. raw:: html
 
   </details>
 
-Next, make building and linking the ``MathFunctions`` library
-conditional.
+Next, make building and linking our library with ``mysqrt`` function
+conditional using this new option.
 
-Start by creating a :command:`list` of the optional library targets for our
-project. At the moment, it is just ``MathFunctions``. Let's name our list
-``EXTRA_LIBS``.
-
-Similarly, we need to make a :command:`list` for the optional includes which
-we will call ``EXTRA_INCLUDES``. In this list, we will ``APPEND`` the path of
-the header file needed for our library.
-
-Next, create an :command:`if` statement which checks the value of
+Create an :command:`if` statement which checks the value of
 ``USE_MYMATH``. Inside the :command:`if` block, put the
-:command:`add_subdirectory` command from Exercise 1 with the additional
-:command:`list` commands.
-
-When ``USE_MYMATH`` is ``ON``, the lists will be generated and will be added to
-our project. When ``USE_MYMATH`` is ``OFF``, the lists stay empty. With this
-strategy, we allow users to toggle ``USE_MYMATH`` to manipulate what library is
-used in the build.
-
-The top-level CMakeLists.txt file will now look like the following:
+:command:`target_compile_definitions` command with the compile
+definition ``USE_MYMATH``.
 
 .. raw:: html
 
   <details><summary>TODO 8: Click to show/hide answer</summary>
 
-.. literalinclude:: Step3/CMakeLists.txt
-  :caption: TODO 8: CMakeLists.txt
+.. code-block:: cmake
+  :caption: TODO 8: MathFunctions/CMakeLists.txt
   :name: CMakeLists.txt-USE_MYMATH
-  :language: cmake
-  :start-after: # add the MathFunctions library
-  :end-before: # add the executable
+
+  if (USE_MYMATH)
+    target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
+  endif()
 
 .. raw:: html
 
   </details>
 
-Now that we have these two lists, we need to update
-:command:`target_link_libraries` and :command:`target_include_directories` to
-use them. Changing them is fairly straightforward.
+When ``USE_MYMATH`` is ``ON``, the compile definition ``USE_MYMATH`` will
+be set. We can then use this compile definition to enable or disable
+sections of our source code.
 
-For :command:`target_link_libraries`, we replace the written out
-library names with ``EXTRA_LIBS``. This looks like the following:
+The corresponding changes to the source code are fairly straightforward.
+In ``MathFunctions.cxx``, we make ``USE_MYMATH`` control which square root
+function is used:
 
 .. raw:: html
 
   <details><summary>TODO 9: Click to show/hide answer</summary>
 
-.. literalinclude:: Step3/CMakeLists.txt
-  :caption: TODO 9: CMakeLists.txt
-  :name: CMakeLists.txt-target_link_libraries-EXTRA_LIBS
-  :language: cmake
-  :start-after: add_executable(Tutorial tutorial.cxx)
-  :end-before: # TODO 3
+.. literalinclude:: Step3/MathFunctions/MathFunctions.cxx
+  :caption: TODO 9: MathFunctions/MathFunctions.cxx
+  :name: MathFunctions-USE_MYMATH-if
+  :language: c++
+  :start-after: which square root function should we use?
+  :end-before: }
 
 .. raw:: html
 
   </details>
 
-Then, we do the same thing with :command:`target_include_directories` and
-``EXTRA_INCLUDES``.
+Next, we need to include ``mysqrt.h`` if ``USE_MYMATH`` is defined.
 
 .. raw:: html
 
   <details><summary>TODO 10: Click to show/hide answer</summary>
 
-.. literalinclude:: Step3/CMakeLists.txt
-  :caption: TODO 10 : CMakeLists.txt
-  :name: CMakeLists.txt-target_link_libraries-EXTRA_INCLUDES
-  :language: cmake
-  :start-after: # so that we will find TutorialConfig.h
+.. literalinclude:: Step3/MathFunctions/MathFunctions.cxx
+  :caption: TODO 10: MathFunctions/MathFunctions.cxx
+  :name: MathFunctions-USE_MYMATH-if-include
+  :language: c++
+  :start-after: include <cmath>
+  :end-before: namespace mathfunctions
 
 .. raw:: html
 
   </details>
 
-Note that this is a classic approach when dealing with many components. We
-will cover the modern approach in the Step 3 of the tutorial.
-
-The corresponding changes to the source code are fairly straightforward.
-First, in ``tutorial.cxx``, we include the ``MathFunctions.h`` header if
-``USE_MYMATH`` is defined.
+Finally, we need to include ``cmath`` now that we are using ``std::sqrt``.
 
 .. raw:: html
 
   <details><summary>TODO 11: Click to show/hide answer</summary>
 
-.. literalinclude:: Step3/tutorial.cxx
-  :caption: TODO 11 : tutorial.cxx
-  :name: tutorial.cxx-ifdef-include
-  :language: c++
-  :start-after: // should we include the MathFunctions header
-  :end-before: int main
+.. code-block:: c++
+  :caption: TODO 11 : MathFunctions/MathFunctions.cxx
+  :name: tutorial.cxx-include_cmath
+
+  #include <cmath>
 
 .. raw:: html
 
   </details>
 
-Then, in the same file, we make ``USE_MYMATH`` control which square root
-function is used:
+At this point, if ``USE_MYMATH`` is ``OFF``, ``mysqrt.cxx`` would not be used
+but it will still be compiled because the ``MathFunctions`` target has
+``mysqrt.cxx`` listed under sources.
+
+There are a few ways to fix this. The first option is to use
+:command:`target_sources` to add ``mysqrt.cxx`` from within the ``USE_MYMATH``
+block. Another option is to create an additional library within the
+``USE_MYMATH`` block which is responsible for compiling ``mysqrt.cxx``. For
+the sake of this tutorial, we are going to create an additional library.
+
+First, from within ``USE_MYMATH`` create a library called ``SqrtLibrary``
+that has sources ``mysqrt.cxx``.
 
 .. raw:: html
 
   <details><summary>TODO 12: Click to show/hide answer</summary>
 
-.. literalinclude:: Step3/tutorial.cxx
-  :caption: TODO 12 : tutorial.cxx
-  :name: tutorial.cxx-ifdef-const
-  :language: c++
-  :start-after: // which square root function should we use?
-  :end-before: std::cout << "The square root of
+.. literalinclude:: Step3/MathFunctions/CMakeLists.txt
+  :caption: TODO 12 : MathFunctions/CMakeLists.txt
+  :name: MathFunctions/CMakeLists.txt-add_library-SqrtLibrary
+  :language: cmake
+  :start-after: # library that just does sqrt
+  :end-before: # TODO 7: Link
 
 .. raw:: html
 
   </details>
 
-Since the source code now requires ``USE_MYMATH`` we can add it to
-``TutorialConfig.h.in`` with the following line:
+Next, we link ``SqrtLibrary`` onto ``MathFunctions`` when ``USE_MYMATH`` is
+enabled.
 
 .. raw:: html
 
   <details><summary>TODO 13: Click to show/hide answer</summary>
 
-.. literalinclude:: Step3/TutorialConfig.h.in
-  :caption: TODO 13 : TutorialConfig.h.in
-  :name: TutorialConfig.h.in-cmakedefine
-  :language: c++
-  :lines: 4
+.. literalinclude:: Step3/MathFunctions/CMakeLists.txt
+  :caption: TODO 13 : MathFunctions/CMakeLists.txt
+  :name: MathFunctions/CMakeLists.txt-target_link_libraries-SqrtLibrary
+  :language: cmake
+  :start-after: to tutorial_compiler_flags
+  :end-before: endif()
 
 .. raw:: html
 
   </details>
 
-With these changes, our library is now completely optional to whoever is
-building and using it.
-
-Bonus Question
---------------
-
-Why is it important that we configure ``TutorialConfig.h.in``
-after the option for ``USE_MYMATH``? What would happen if we inverted the two?
-
-Answer
-------
+Finally, we can remove ``mysqrt.cxx`` from our ``MathFunctions`` library
+source list because it will be pulled in when ``SqrtLibrary`` is included.
 
 .. raw:: html
 
-  <details><summary>Click to show/hide answer</summary>
+  <details><summary>TODO 14: Click to show/hide answer</summary>
 
-We configure after because ``TutorialConfig.h.in`` uses the value of
-``USE_MYMATH``. If we configure the file before
-calling :command:`option`, we won't be using the expected value of
-``USE_MYMATH``.
+.. literalinclude:: Step3/MathFunctions/CMakeLists.txt
+  :caption: TODO 14 : MathFunctions/CMakeLists.txt
+  :name: MathFunctions/CMakeLists.txt-remove-mysqrt.cxx-MathFunctions
+  :language: cmake
+  :end-before: # TODO 1:
 
 .. raw:: html
 
   </details>
+
+With these changes, the ``mysqrt`` function is now completely optional to
+whoever is building and using the ``MathFunctions`` library. Users can toggle
+``USE_MYMATH`` to manipulate what library is used in the build.
diff --git a/Help/guide/tutorial/Complete/MathFunctions/CMakeLists.txt b/Help/guide/tutorial/Complete/MathFunctions/CMakeLists.txt
index d256db2..b221506 100644
--- a/Help/guide/tutorial/Complete/MathFunctions/CMakeLists.txt
+++ b/Help/guide/tutorial/Complete/MathFunctions/CMakeLists.txt
@@ -15,16 +15,7 @@
 
   target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
 
-  # first we add the executable that generates the table
-  add_executable(MakeTable MakeTable.cxx)
-  target_link_libraries(MakeTable PRIVATE tutorial_compiler_flags)
-
-  # add the command to generate the source code
-  add_custom_command(
-    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
-    COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
-    DEPENDS MakeTable
-    )
+  include(MakeTable.cmake) # generates Table.h
 
   # library that just does sqrt
   add_library(SqrtLibrary STATIC
diff --git a/Help/guide/tutorial/Complete/MathFunctions/MakeTable.cmake b/Help/guide/tutorial/Complete/MathFunctions/MakeTable.cmake
new file mode 100644
index 0000000..12865a9
--- /dev/null
+++ b/Help/guide/tutorial/Complete/MathFunctions/MakeTable.cmake
@@ -0,0 +1,10 @@
+# first we add the executable that generates the table
+add_executable(MakeTable MakeTable.cxx)
+target_link_libraries(MakeTable PRIVATE tutorial_compiler_flags)
+
+# add the command to generate the source code
+add_custom_command(
+  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
+  COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
+  DEPENDS MakeTable
+  )
diff --git a/Help/guide/tutorial/Complete/MathFunctions/MathFunctions.cxx b/Help/guide/tutorial/Complete/MathFunctions/MathFunctions.cxx
index 0145300..c0991b9 100644
--- a/Help/guide/tutorial/Complete/MathFunctions/MathFunctions.cxx
+++ b/Help/guide/tutorial/Complete/MathFunctions/MathFunctions.cxx
@@ -10,6 +10,7 @@
 namespace mathfunctions {
 double sqrt(double x)
 {
+// which square root function should we use?
 #ifdef USE_MYMATH
   return detail::mysqrt(x);
 #else
diff --git a/Help/guide/tutorial/Selecting Static or Shared Libraries.rst b/Help/guide/tutorial/Selecting Static or Shared Libraries.rst
index 7befe1d..504e42f 100644
--- a/Help/guide/tutorial/Selecting Static or Shared Libraries.rst
+++ b/Help/guide/tutorial/Selecting Static or Shared Libraries.rst
@@ -10,49 +10,22 @@
 top-level ``CMakeLists.txt``. We use the :command:`option` command as it allows
 users to optionally select if the value should be ``ON`` or ``OFF``.
 
-Next we are going to refactor ``MathFunctions`` to become a real library that
-encapsulates using ``mysqrt`` or ``sqrt``, instead of requiring the calling
-code to do this logic. This will also mean that ``USE_MYMATH`` will not control
-building ``MathFunctions``, but instead will control the behavior of this
-library.
-
-The first step is to update the starting section of the top-level
-``CMakeLists.txt`` to look like:
-
 .. literalinclude:: Step11/CMakeLists.txt
   :caption: CMakeLists.txt
   :name: CMakeLists.txt-option-BUILD_SHARED_LIBS
   :language: cmake
-  :end-before: # add the binary tree
+  :start-after: set(CMAKE_RUNTIME_OUTPUT_DIRECTORY
+  :end-before: # configure a header file to pass the version number only
 
-Now that we have made ``MathFunctions`` always be used, we will need to update
-the logic of that library. So, in ``MathFunctions/CMakeLists.txt`` we need to
-create a SqrtLibrary that will conditionally be built and installed when
-``USE_MYMATH`` is enabled. Now, since this is a tutorial, we are going to
-explicitly require that SqrtLibrary is built statically.
+Next, we need to specify output directories for our static and shared
+libraries.
 
-The end result is that ``MathFunctions/CMakeLists.txt`` should look like:
-
-.. literalinclude:: Step11/MathFunctions/CMakeLists.txt
-  :caption: MathFunctions/CMakeLists.txt
-  :name: MathFunctions/CMakeLists.txt-add_library-STATIC
+.. literalinclude:: Step11/CMakeLists.txt
+  :caption: CMakeLists.txt
+  :name: CMakeLists.txt-cmake-output-directories
   :language: cmake
-  :lines: 1-36,42-
-
-Next, update ``MathFunctions/mysqrt.cxx`` to use the ``mathfunctions`` and
-``detail`` namespaces:
-
-.. literalinclude:: Step11/MathFunctions/mysqrt.cxx
-  :caption: MathFunctions/mysqrt.cxx
-  :name: MathFunctions/mysqrt.cxx-namespace
-  :language: c++
-
-We also need to make some changes in ``tutorial.cxx``, so that it no longer
-uses ``USE_MYMATH``:
-
-#. Always include ``MathFunctions.h``
-#. Always use ``mathfunctions::sqrt``
-#. Don't include ``cmath``
+  :start-after: # we don't need to tinker with the path to run the executable
+  :end-before: # configure a header file to pass the version number only
 
 Finally, update ``MathFunctions/MathFunctions.h`` to use dll export defines:
 
diff --git a/Help/guide/tutorial/Step10/CMakeLists.txt b/Help/guide/tutorial/Step10/CMakeLists.txt
index 5c661aa..2dd6db5 100644
--- a/Help/guide/tutorial/Step10/CMakeLists.txt
+++ b/Help/guide/tutorial/Step10/CMakeLists.txt
@@ -16,22 +16,15 @@
   "$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
 )
 
-# should we use our own math functions
-option(USE_MYMATH "Use tutorial provided math implementation" ON)
-
-# configure a header file to pass some of the CMake settings
-# to the source code
+# configure a header file to pass the version number only
 configure_file(TutorialConfig.h.in TutorialConfig.h)
 
 # add the MathFunctions library
-if(USE_MYMATH)
-  add_subdirectory(MathFunctions)
-  list(APPEND EXTRA_LIBS MathFunctions)
-endif()
+add_subdirectory(MathFunctions)
 
 # add the executable
 add_executable(Tutorial tutorial.cxx)
-target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS} tutorial_compiler_flags)
+target_link_libraries(Tutorial PUBLIC MathFunctions tutorial_compiler_flags)
 
 # add the binary tree to the search path for include files
 # so that we will find TutorialConfig.h
diff --git a/Help/guide/tutorial/Step10/MathFunctions/CMakeLists.txt b/Help/guide/tutorial/Step10/MathFunctions/CMakeLists.txt
index fa73321..36b3fe1 100644
--- a/Help/guide/tutorial/Step10/MathFunctions/CMakeLists.txt
+++ b/Help/guide/tutorial/Step10/MathFunctions/CMakeLists.txt
@@ -1,32 +1,42 @@
-# first we add the executable that generates the table
-add_executable(MakeTable MakeTable.cxx)
-
-# add the command to generate the source code
-add_custom_command(
-  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
-  COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
-  DEPENDS MakeTable
-  )
-
-# add the main library
-add_library(MathFunctions
-            mysqrt.cxx
-            ${CMAKE_CURRENT_BINARY_DIR}/Table.h
-            )
+# add the library that runs
+add_library(MathFunctions MathFunctions.cxx)
 
 # state that anybody linking to us needs to include the current source dir
 # to find MathFunctions.h, while we don't.
-# state that we depend on our binary dir to find Table.h
 target_include_directories(MathFunctions
-          INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
-          PRIVATE   ${CMAKE_CURRENT_BINARY_DIR}
-          )
+                           INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
+                           )
 
-# link our compiler flags interface library
-target_link_libraries(MathFunctions tutorial_compiler_flags)
+# should we use our own math functions
+option(USE_MYMATH "Use tutorial provided math implementation" ON)
+if(USE_MYMATH)
+
+  target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
+
+  include(MakeTable.cmake) # generates Table.h
+
+  # library that just does sqrt
+  add_library(SqrtLibrary STATIC
+              mysqrt.cxx
+              ${CMAKE_CURRENT_BINARY_DIR}/Table.h
+              )
+
+  # state that we depend on our binary dir to find Table.h
+  target_include_directories(SqrtLibrary PRIVATE
+                             ${CMAKE_CURRENT_BINARY_DIR}
+                             )
+
+  target_link_libraries(SqrtLibrary PUBLIC tutorial_compiler_flags)
+  target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
+endif()
+
+target_link_libraries(MathFunctions PUBLIC tutorial_compiler_flags)
 
 # install libs
 set(installable_libs MathFunctions tutorial_compiler_flags)
+if(TARGET SqrtLibrary)
+  list(APPEND installable_libs SqrtLibrary)
+endif()
 install(TARGETS ${installable_libs} DESTINATION lib)
 # install include headers
 install(FILES MathFunctions.h DESTINATION include)
diff --git a/Help/guide/tutorial/Step10/MathFunctions/MakeTable.cmake b/Help/guide/tutorial/Step10/MathFunctions/MakeTable.cmake
new file mode 100644
index 0000000..12865a9
--- /dev/null
+++ b/Help/guide/tutorial/Step10/MathFunctions/MakeTable.cmake
@@ -0,0 +1,10 @@
+# first we add the executable that generates the table
+add_executable(MakeTable MakeTable.cxx)
+target_link_libraries(MakeTable PRIVATE tutorial_compiler_flags)
+
+# add the command to generate the source code
+add_custom_command(
+  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
+  COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
+  DEPENDS MakeTable
+  )
diff --git a/Help/guide/tutorial/Step10/MathFunctions/MathFunctions.cxx b/Help/guide/tutorial/Step10/MathFunctions/MathFunctions.cxx
index 0145300..c0991b9 100644
--- a/Help/guide/tutorial/Step10/MathFunctions/MathFunctions.cxx
+++ b/Help/guide/tutorial/Step10/MathFunctions/MathFunctions.cxx
@@ -10,6 +10,7 @@
 namespace mathfunctions {
 double sqrt(double x)
 {
+// which square root function should we use?
 #ifdef USE_MYMATH
   return detail::mysqrt(x);
 #else
diff --git a/Help/guide/tutorial/Step10/MathFunctions/MathFunctions.h b/Help/guide/tutorial/Step10/MathFunctions/MathFunctions.h
index cd36bcc..1e916e1 100644
--- a/Help/guide/tutorial/Step10/MathFunctions/MathFunctions.h
+++ b/Help/guide/tutorial/Step10/MathFunctions/MathFunctions.h
@@ -1 +1,3 @@
-double mysqrt(double x);
+namespace mathfunctions {
+double sqrt(double x);
+}
diff --git a/Help/guide/tutorial/Step10/MathFunctions/mysqrt.cxx b/Help/guide/tutorial/Step10/MathFunctions/mysqrt.cxx
index 7d80ee9..8153f18 100644
--- a/Help/guide/tutorial/Step10/MathFunctions/mysqrt.cxx
+++ b/Help/guide/tutorial/Step10/MathFunctions/mysqrt.cxx
@@ -5,6 +5,8 @@
 // include the generated table
 #include "Table.h"
 
+namespace mathfunctions {
+namespace detail {
 // a hack square root calculation using simple operations
 double mysqrt(double x)
 {
@@ -31,3 +33,5 @@
 
   return result;
 }
+}
+}
diff --git a/Help/guide/tutorial/Step10/TutorialConfig.h.in b/Help/guide/tutorial/Step10/TutorialConfig.h.in
index e23f521..7e4d7fa 100644
--- a/Help/guide/tutorial/Step10/TutorialConfig.h.in
+++ b/Help/guide/tutorial/Step10/TutorialConfig.h.in
@@ -1,4 +1,3 @@
 // the configured options and settings for Tutorial
 #define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
 #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
-#cmakedefine USE_MYMATH
diff --git a/Help/guide/tutorial/Step10/tutorial.cxx b/Help/guide/tutorial/Step10/tutorial.cxx
index b3c6a4f..37a0333 100644
--- a/Help/guide/tutorial/Step10/tutorial.cxx
+++ b/Help/guide/tutorial/Step10/tutorial.cxx
@@ -1,15 +1,11 @@
 // A simple program that computes the square root of a number
-#include <cmath>
 #include <iostream>
+#include <sstream>
 #include <string>
 
+#include "MathFunctions.h"
 #include "TutorialConfig.h"
 
-// should we include the MathFunctions header?
-#ifdef USE_MYMATH
-#  include "MathFunctions.h"
-#endif
-
 int main(int argc, char* argv[])
 {
   if (argc < 2) {
@@ -23,12 +19,7 @@
   // convert input to double
   const double inputValue = std::stod(argv[1]);
 
-  // which square root function should we use?
-#ifdef USE_MYMATH
-  const double outputValue = mysqrt(inputValue);
-#else
-  const double outputValue = sqrt(inputValue);
-#endif
+  const double outputValue = mathfunctions::sqrt(inputValue);
 
   std::cout << "The square root of " << inputValue << " is " << outputValue
             << std::endl;
diff --git a/Help/guide/tutorial/Step11/MathFunctions/CMakeLists.txt b/Help/guide/tutorial/Step11/MathFunctions/CMakeLists.txt
index a60fb63..813bf90 100644
--- a/Help/guide/tutorial/Step11/MathFunctions/CMakeLists.txt
+++ b/Help/guide/tutorial/Step11/MathFunctions/CMakeLists.txt
@@ -13,16 +13,7 @@
 
   target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
 
-  # first we add the executable that generates the table
-  add_executable(MakeTable MakeTable.cxx)
-  target_link_libraries(MakeTable PRIVATE tutorial_compiler_flags)
-
-  # add the command to generate the source code
-  add_custom_command(
-    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
-    COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
-    DEPENDS MakeTable
-    )
+  include(MakeTable.cmake) # generates Table.h
 
   # library that just does sqrt
   add_library(SqrtLibrary STATIC
diff --git a/Help/guide/tutorial/Step11/MathFunctions/MakeTable.cmake b/Help/guide/tutorial/Step11/MathFunctions/MakeTable.cmake
new file mode 100644
index 0000000..12865a9
--- /dev/null
+++ b/Help/guide/tutorial/Step11/MathFunctions/MakeTable.cmake
@@ -0,0 +1,10 @@
+# first we add the executable that generates the table
+add_executable(MakeTable MakeTable.cxx)
+target_link_libraries(MakeTable PRIVATE tutorial_compiler_flags)
+
+# add the command to generate the source code
+add_custom_command(
+  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
+  COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
+  DEPENDS MakeTable
+  )
diff --git a/Help/guide/tutorial/Step11/MathFunctions/MathFunctions.cxx b/Help/guide/tutorial/Step11/MathFunctions/MathFunctions.cxx
index 0145300..c0991b9 100644
--- a/Help/guide/tutorial/Step11/MathFunctions/MathFunctions.cxx
+++ b/Help/guide/tutorial/Step11/MathFunctions/MathFunctions.cxx
@@ -10,6 +10,7 @@
 namespace mathfunctions {
 double sqrt(double x)
 {
+// which square root function should we use?
 #ifdef USE_MYMATH
   return detail::mysqrt(x);
 #else
diff --git a/Help/guide/tutorial/Step12/MathFunctions/CMakeLists.txt b/Help/guide/tutorial/Step12/MathFunctions/CMakeLists.txt
index a85f3cb..38694dd 100644
--- a/Help/guide/tutorial/Step12/MathFunctions/CMakeLists.txt
+++ b/Help/guide/tutorial/Step12/MathFunctions/CMakeLists.txt
@@ -15,16 +15,7 @@
 
   target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
 
-  # first we add the executable that generates the table
-  add_executable(MakeTable MakeTable.cxx)
-  target_link_libraries(MakeTable PRIVATE tutorial_compiler_flags)
-
-  # add the command to generate the source code
-  add_custom_command(
-    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
-    COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
-    DEPENDS MakeTable
-    )
+  include(MakeTable.cmake) # generates Table.h
 
   # library that just does sqrt
   add_library(SqrtLibrary STATIC
diff --git a/Help/guide/tutorial/Step12/MathFunctions/MakeTable.cmake b/Help/guide/tutorial/Step12/MathFunctions/MakeTable.cmake
new file mode 100644
index 0000000..12865a9
--- /dev/null
+++ b/Help/guide/tutorial/Step12/MathFunctions/MakeTable.cmake
@@ -0,0 +1,10 @@
+# first we add the executable that generates the table
+add_executable(MakeTable MakeTable.cxx)
+target_link_libraries(MakeTable PRIVATE tutorial_compiler_flags)
+
+# add the command to generate the source code
+add_custom_command(
+  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
+  COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
+  DEPENDS MakeTable
+  )
diff --git a/Help/guide/tutorial/Step12/MathFunctions/MathFunctions.cxx b/Help/guide/tutorial/Step12/MathFunctions/MathFunctions.cxx
index 0145300..c0991b9 100644
--- a/Help/guide/tutorial/Step12/MathFunctions/MathFunctions.cxx
+++ b/Help/guide/tutorial/Step12/MathFunctions/MathFunctions.cxx
@@ -10,6 +10,7 @@
 namespace mathfunctions {
 double sqrt(double x)
 {
+// which square root function should we use?
 #ifdef USE_MYMATH
   return detail::mysqrt(x);
 #else
diff --git a/Help/guide/tutorial/Step2/CMakeLists.txt b/Help/guide/tutorial/Step2/CMakeLists.txt
index 2b96128..0a06ed7 100644
--- a/Help/guide/tutorial/Step2/CMakeLists.txt
+++ b/Help/guide/tutorial/Step2/CMakeLists.txt
@@ -7,36 +7,20 @@
 set(CMAKE_CXX_STANDARD 11)
 set(CMAKE_CXX_STANDARD_REQUIRED True)
 
-# TODO 7: Create a variable USE_MYMATH using option and set default to ON
-
 # configure a header file to pass some of the CMake settings
 # to the source code
 configure_file(TutorialConfig.h.in TutorialConfig.h)
 
-# TODO 8: Use list() and APPEND to create a list of optional libraries
-# called  EXTRA_LIBS and a list of optional include directories called
-# EXTRA_INCLUDES. Add the MathFunctions library and source directory to
-# the appropriate lists.
-#
-# Only call add_subdirectory and only add MathFunctions specific values
-# to EXTRA_LIBS and EXTRA_INCLUDES if USE_MYMATH is true.
-
 # TODO 2: Use add_subdirectory() to add MathFunctions to this project
 
 # add the executable
 add_executable(Tutorial tutorial.cxx)
 
-# TODO 9: Use EXTRA_LIBS instead of the MathFunctions specific values
-# in target_link_libraries.
-
 # TODO 3: Use target_link_libraries to link the library to our executable
 
 # TODO 4: Add MathFunctions to Tutorial's target_include_directories()
 # Hint: ${PROJECT_SOURCE_DIR} is a path to the project source. AKA This folder!
 
-# TODO 10: Use EXTRA_INCLUDES instead of the MathFunctions specific values
-# in target_include_directories.
-
 # add the binary tree to the search path for include files
 # so that we will find TutorialConfig.h
 target_include_directories(Tutorial PUBLIC
diff --git a/Help/guide/tutorial/Step2/MathFunctions/CMakeLists.txt b/Help/guide/tutorial/Step2/MathFunctions/CMakeLists.txt
index b7779b7..c3cd806 100644
--- a/Help/guide/tutorial/Step2/MathFunctions/CMakeLists.txt
+++ b/Help/guide/tutorial/Step2/MathFunctions/CMakeLists.txt
@@ -1,2 +1,15 @@
-# TODO 1: Add a library called MathFunctions
+# TODO 14: Remove mysqrt.cxx from the list of sources
+
+# TODO 1: Add a library called MathFunctions with sources MathFunctions.cxx
+# and mysqrt.cxx
 # Hint: You will need the add_library command
+
+# TODO 7: Create a variable USE_MYMATH using option and set default to ON
+
+# TODO 8: If USE_MYMATH is ON, use target_compile_definitions to pass
+# USE_MYMATH as a precompiled definition to our source files
+
+# TODO 12: When USE_MYMATH is ON, add a library for SqrtLibrary with
+# source mysqrt.cxx
+
+# TODO 13: When USE_MYMATH is ON, link SqrtLibrary to the MathFunctions Library
diff --git a/Help/guide/tutorial/Step2/MathFunctions/MathFunctions.cxx b/Help/guide/tutorial/Step2/MathFunctions/MathFunctions.cxx
new file mode 100644
index 0000000..781d0ec
--- /dev/null
+++ b/Help/guide/tutorial/Step2/MathFunctions/MathFunctions.cxx
@@ -0,0 +1,15 @@
+#include "MathFunctions.h"
+
+// TODO 11: include cmath
+
+// TODO 10: Wrap the mysqrt include in a precompiled ifdef based on USE_MYMATH
+#include "mysqrt.h"
+
+namespace mathfunctions {
+double sqrt(double x)
+{
+  // TODO 9: If USE_MYMATH is defined, use detail::mysqrt.
+  // Otherwise, use std::sqrt.
+  return detail::mysqrt(x);
+}
+}
diff --git a/Help/guide/tutorial/Step2/MathFunctions/MathFunctions.h b/Help/guide/tutorial/Step2/MathFunctions/MathFunctions.h
index cd36bcc..d5c2f22 100644
--- a/Help/guide/tutorial/Step2/MathFunctions/MathFunctions.h
+++ b/Help/guide/tutorial/Step2/MathFunctions/MathFunctions.h
@@ -1 +1,5 @@
-double mysqrt(double x);
+#pragma once
+
+namespace mathfunctions {
+double sqrt(double x);
+}
diff --git a/Help/guide/tutorial/Step2/MathFunctions/mysqrt.cxx b/Help/guide/tutorial/Step2/MathFunctions/mysqrt.cxx
index 1e4d97a..ba0ac64 100644
--- a/Help/guide/tutorial/Step2/MathFunctions/mysqrt.cxx
+++ b/Help/guide/tutorial/Step2/MathFunctions/mysqrt.cxx
@@ -1,5 +1,9 @@
+#include "mysqrt.h"
+
 #include <iostream>
 
+namespace mathfunctions {
+namespace detail {
 // a hack square root calculation using simple operations
 double mysqrt(double x)
 {
@@ -20,3 +24,5 @@
   }
   return result;
 }
+}
+}
diff --git a/Help/guide/tutorial/Step2/MathFunctions/mysqrt.h b/Help/guide/tutorial/Step2/MathFunctions/mysqrt.h
new file mode 100644
index 0000000..593d41e
--- /dev/null
+++ b/Help/guide/tutorial/Step2/MathFunctions/mysqrt.h
@@ -0,0 +1,7 @@
+#pragma once
+
+namespace mathfunctions {
+namespace detail {
+double mysqrt(double x);
+}
+}
diff --git a/Help/guide/tutorial/Step2/TutorialConfig.h.in b/Help/guide/tutorial/Step2/TutorialConfig.h.in
index 6c09e1a..7e4d7fa 100644
--- a/Help/guide/tutorial/Step2/TutorialConfig.h.in
+++ b/Help/guide/tutorial/Step2/TutorialConfig.h.in
@@ -1,5 +1,3 @@
 // the configured options and settings for Tutorial
 #define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
 #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
-
-// TODO 13: use cmakedefine to define USE_MYMATH
diff --git a/Help/guide/tutorial/Step2/tutorial.cxx b/Help/guide/tutorial/Step2/tutorial.cxx
index 87f5e0f..7a2a595 100644
--- a/Help/guide/tutorial/Step2/tutorial.cxx
+++ b/Help/guide/tutorial/Step2/tutorial.cxx
@@ -3,11 +3,8 @@
 #include <iostream>
 #include <string>
 
-#include "TutorialConfig.h"
-
-// TODO 11: Only include MathFunctions if USE_MYMATH is defined
-
 // TODO 5: Include MathFunctions.h
+#include "TutorialConfig.h"
 
 int main(int argc, char* argv[])
 {
@@ -22,9 +19,7 @@
   // convert input to double
   const double inputValue = std::stod(argv[1]);
 
-  // TODO 12: Use mysqrt if USE_MYMATH is defined and sqrt otherwise
-
-  // TODO 6: Replace sqrt with mysqrt
+  // TODO 6: Replace sqrt with mathfunctions::sqrt
 
   // calculate square root
   const double outputValue = sqrt(inputValue);
diff --git a/Help/guide/tutorial/Step3/CMakeLists.txt b/Help/guide/tutorial/Step3/CMakeLists.txt
index 007770a..ac3e9f1 100644
--- a/Help/guide/tutorial/Step3/CMakeLists.txt
+++ b/Help/guide/tutorial/Step3/CMakeLists.txt
@@ -3,13 +3,16 @@
 # set the project name and version
 project(Tutorial VERSION 1.0)
 
+# TODO 4: Replace the following code by:
+# * Creating an interface library called tutorial_compiler_flags
+#   Hint: use add_library() with the INTERFACE signature
+# * Add compiler feature cxx_std_11 to tutorial_compiler_flags
+#   Hint: Use target_compile_features()
+
 # specify the C++ standard
 set(CMAKE_CXX_STANDARD 11)
 set(CMAKE_CXX_STANDARD_REQUIRED True)
 
-# should we use our own math functions
-option(USE_MYMATH "Use tutorial provided math implementation" ON)
-
 # configure a header file to pass some of the CMake settings
 # to the source code
 configure_file(TutorialConfig.h.in TutorialConfig.h)
@@ -17,16 +20,15 @@
 # TODO 2: Remove EXTRA_INCLUDES list
 
 # add the MathFunctions library
-if(USE_MYMATH)
-  add_subdirectory(MathFunctions)
-  list(APPEND EXTRA_LIBS MathFunctions)
-  list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")
-endif()
+add_subdirectory(MathFunctions)
+list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")
 
 # add the executable
 add_executable(Tutorial tutorial.cxx)
 
-target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS})
+# TODO 5: Link Tutorial to tutorial_compiler_flags
+
+target_link_libraries(Tutorial PUBLIC MathFunctions)
 
 # TODO 3: Remove use of EXTRA_INCLUDES
 
diff --git a/Help/guide/tutorial/Step3/MathFunctions/CMakeLists.txt b/Help/guide/tutorial/Step3/MathFunctions/CMakeLists.txt
index 7bf05e0..0ffb9e1 100644
--- a/Help/guide/tutorial/Step3/MathFunctions/CMakeLists.txt
+++ b/Help/guide/tutorial/Step3/MathFunctions/CMakeLists.txt
@@ -1,5 +1,22 @@
-add_library(MathFunctions mysqrt.cxx)
+add_library(MathFunctions MathFunctions.cxx)
 
 # TODO 1: State that anybody linking to MathFunctions needs to include the
 # current source directory, while MathFunctions itself doesn't.
 # Hint: Use target_include_directories with the INTERFACE keyword
+
+# should we use our own math functions
+option(USE_MYMATH "Use tutorial provided math implementation" ON)
+if (USE_MYMATH)
+  target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
+
+  # library that just does sqrt
+  add_library(SqrtLibrary STATIC
+              mysqrt.cxx
+              )
+
+  # TODO 7: Link SqrtLibrary to tutorial_compiler_flags
+
+  target_link_libraries(MathFunctions PUBLIC SqrtLibrary)
+endif()
+
+# TODO 6: Link MathFunctions to tutorial_compiler_flags
diff --git a/Help/guide/tutorial/Step3/MathFunctions/MathFunctions.cxx b/Help/guide/tutorial/Step3/MathFunctions/MathFunctions.cxx
new file mode 100644
index 0000000..dc28b4b
--- /dev/null
+++ b/Help/guide/tutorial/Step3/MathFunctions/MathFunctions.cxx
@@ -0,0 +1,19 @@
+#include "MathFunctions.h"
+
+#include <cmath>
+
+#ifdef USE_MYMATH
+#  include "mysqrt.h"
+#endif
+
+namespace mathfunctions {
+double sqrt(double x)
+{
+// which square root function should we use?
+#ifdef USE_MYMATH
+  return detail::mysqrt(x);
+#else
+  return std::sqrt(x);
+#endif
+}
+}
diff --git a/Help/guide/tutorial/Step3/MathFunctions/MathFunctions.h b/Help/guide/tutorial/Step3/MathFunctions/MathFunctions.h
index cd36bcc..d5c2f22 100644
--- a/Help/guide/tutorial/Step3/MathFunctions/MathFunctions.h
+++ b/Help/guide/tutorial/Step3/MathFunctions/MathFunctions.h
@@ -1 +1,5 @@
-double mysqrt(double x);
+#pragma once
+
+namespace mathfunctions {
+double sqrt(double x);
+}
diff --git a/Help/guide/tutorial/Step3/MathFunctions/mysqrt.cxx b/Help/guide/tutorial/Step3/MathFunctions/mysqrt.cxx
index abe767d..ba0ac64 100644
--- a/Help/guide/tutorial/Step3/MathFunctions/mysqrt.cxx
+++ b/Help/guide/tutorial/Step3/MathFunctions/mysqrt.cxx
@@ -1,7 +1,9 @@
+#include "mysqrt.h"
+
 #include <iostream>
 
-#include "MathFunctions.h"
-
+namespace mathfunctions {
+namespace detail {
 // a hack square root calculation using simple operations
 double mysqrt(double x)
 {
@@ -22,3 +24,5 @@
   }
   return result;
 }
+}
+}
diff --git a/Help/guide/tutorial/Step3/MathFunctions/mysqrt.h b/Help/guide/tutorial/Step3/MathFunctions/mysqrt.h
new file mode 100644
index 0000000..593d41e
--- /dev/null
+++ b/Help/guide/tutorial/Step3/MathFunctions/mysqrt.h
@@ -0,0 +1,7 @@
+#pragma once
+
+namespace mathfunctions {
+namespace detail {
+double mysqrt(double x);
+}
+}
diff --git a/Help/guide/tutorial/Step3/TutorialConfig.h.in b/Help/guide/tutorial/Step3/TutorialConfig.h.in
index e23f521..7e4d7fa 100644
--- a/Help/guide/tutorial/Step3/TutorialConfig.h.in
+++ b/Help/guide/tutorial/Step3/TutorialConfig.h.in
@@ -1,4 +1,3 @@
 // the configured options and settings for Tutorial
 #define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
 #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
-#cmakedefine USE_MYMATH
diff --git a/Help/guide/tutorial/Step3/tutorial.cxx b/Help/guide/tutorial/Step3/tutorial.cxx
index b3c6a4f..a3a2bdc 100644
--- a/Help/guide/tutorial/Step3/tutorial.cxx
+++ b/Help/guide/tutorial/Step3/tutorial.cxx
@@ -3,13 +3,9 @@
 #include <iostream>
 #include <string>
 
+#include "MathFunctions.h"
 #include "TutorialConfig.h"
 
-// should we include the MathFunctions header?
-#ifdef USE_MYMATH
-#  include "MathFunctions.h"
-#endif
-
 int main(int argc, char* argv[])
 {
   if (argc < 2) {
@@ -23,12 +19,7 @@
   // convert input to double
   const double inputValue = std::stod(argv[1]);
 
-  // which square root function should we use?
-#ifdef USE_MYMATH
-  const double outputValue = mysqrt(inputValue);
-#else
-  const double outputValue = sqrt(inputValue);
-#endif
+  const double outputValue = mathfunctions::sqrt(inputValue);
 
   std::cout << "The square root of " << inputValue << " is " << outputValue
             << std::endl;
diff --git a/Help/guide/tutorial/Step4/CMakeLists.txt b/Help/guide/tutorial/Step4/CMakeLists.txt
index fa4aab2..fba9766 100644
--- a/Help/guide/tutorial/Step4/CMakeLists.txt
+++ b/Help/guide/tutorial/Step4/CMakeLists.txt
@@ -1,55 +1,41 @@
-# TODO 4: Update the minimum required version to 3.15
+# TODO 1: Update the minimum required version to 3.15
 
 cmake_minimum_required(VERSION 3.10)
 
 # set the project name and version
 project(Tutorial VERSION 1.0)
 
-# TODO 1: Replace the following code by:
-# * Creating an interface library called tutorial_compiler_flags
-#   Hint: use add_library() with the INTERFACE signature
-# * Add compiler feature cxx_std_11 to tutorial_compiler_flags
-#   Hint: Use target_compile_features()
-
 # specify the C++ standard
-set(CMAKE_CXX_STANDARD 11)
-set(CMAKE_CXX_STANDARD_REQUIRED True)
+add_library(tutorial_compiler_flags INTERFACE)
+target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
 
-# TODO 5: Create helper variables to determine which compiler we are using:
+# TODO 2: Create helper variables to determine which compiler we are using:
 # * Create a new variable gcc_like_cxx that is true if we are using CXX and
 #   any of the following compilers: ARMClang, AppleClang, Clang, GNU, LCC
 # * Create a new variable msvc_cxx that is true if we are using CXX and MSVC
 # Hint: Use set() and COMPILE_LANG_AND_ID
 
-# TODO 6: Add warning flag compile options to the interface library
+# TODO 3: Add warning flag compile options to the interface library
 # tutorial_compiler_flags.
 # * For gcc_like_cxx, add flags -Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused
 # * For msvc_cxx, add flags -W3
 # Hint: Use target_compile_options()
 
-# TODO 7: With nested generator expressions, only use the flags for the
+# TODO 4: With nested generator expressions, only use the flags for the
 # build-tree
 # Hint: Use BUILD_INTERFACE
 
-# should we use our own math functions
-option(USE_MYMATH "Use tutorial provided math implementation" ON)
-
 # configure a header file to pass some of the CMake settings
 # to the source code
 configure_file(TutorialConfig.h.in TutorialConfig.h)
 
 # add the MathFunctions library
-if(USE_MYMATH)
-  add_subdirectory(MathFunctions)
-  list(APPEND EXTRA_LIBS MathFunctions)
-endif()
+add_subdirectory(MathFunctions)
 
 # add the executable
 add_executable(Tutorial tutorial.cxx)
 
-# TODO 2: Link to tutorial_compiler_flags
-
-target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS})
+target_link_libraries(Tutorial PUBLIC MathFunctions tutorial_compiler_flags)
 
 # add the binary tree to the search path for include files
 # so that we will find TutorialConfig.h
diff --git a/Help/guide/tutorial/Step4/MathFunctions/CMakeLists.txt b/Help/guide/tutorial/Step4/MathFunctions/CMakeLists.txt
index 5f7369c..48561eb 100644
--- a/Help/guide/tutorial/Step4/MathFunctions/CMakeLists.txt
+++ b/Help/guide/tutorial/Step4/MathFunctions/CMakeLists.txt
@@ -1,9 +1,26 @@
-add_library(MathFunctions mysqrt.cxx)
+# create the MathFunctions library
+add_library(MathFunctions MathFunctions.cxx)
 
 # state that anybody linking to us needs to include the current source dir
 # to find MathFunctions.h, while we don't.
 target_include_directories(MathFunctions
-          INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
-          )
+                           INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
+                           )
 
-# TODO 3: Link to tutorial_compiler_flags
+# should we use our own math functions
+option(USE_MYMATH "Use tutorial provided math implementation" ON)
+if (USE_MYMATH)
+  target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
+
+  # library that just does sqrt
+  add_library(SqrtLibrary STATIC
+              mysqrt.cxx
+              )
+
+  # link our compiler flags interface library
+  target_link_libraries(SqrtLibrary PUBLIC tutorial_compiler_flags)
+  target_link_libraries(MathFunctions PUBLIC SqrtLibrary)
+endif()
+
+# link our compiler flags interface library
+target_link_libraries(MathFunctions PUBLIC tutorial_compiler_flags)
diff --git a/Help/guide/tutorial/Step4/MathFunctions/MathFunctions.cxx b/Help/guide/tutorial/Step4/MathFunctions/MathFunctions.cxx
new file mode 100644
index 0000000..dc28b4b
--- /dev/null
+++ b/Help/guide/tutorial/Step4/MathFunctions/MathFunctions.cxx
@@ -0,0 +1,19 @@
+#include "MathFunctions.h"
+
+#include <cmath>
+
+#ifdef USE_MYMATH
+#  include "mysqrt.h"
+#endif
+
+namespace mathfunctions {
+double sqrt(double x)
+{
+// which square root function should we use?
+#ifdef USE_MYMATH
+  return detail::mysqrt(x);
+#else
+  return std::sqrt(x);
+#endif
+}
+}
diff --git a/Help/guide/tutorial/Step4/MathFunctions/MathFunctions.h b/Help/guide/tutorial/Step4/MathFunctions/MathFunctions.h
index cd36bcc..d5c2f22 100644
--- a/Help/guide/tutorial/Step4/MathFunctions/MathFunctions.h
+++ b/Help/guide/tutorial/Step4/MathFunctions/MathFunctions.h
@@ -1 +1,5 @@
-double mysqrt(double x);
+#pragma once
+
+namespace mathfunctions {
+double sqrt(double x);
+}
diff --git a/Help/guide/tutorial/Step4/MathFunctions/mysqrt.cxx b/Help/guide/tutorial/Step4/MathFunctions/mysqrt.cxx
index abe767d..ba0ac64 100644
--- a/Help/guide/tutorial/Step4/MathFunctions/mysqrt.cxx
+++ b/Help/guide/tutorial/Step4/MathFunctions/mysqrt.cxx
@@ -1,7 +1,9 @@
+#include "mysqrt.h"
+
 #include <iostream>
 
-#include "MathFunctions.h"
-
+namespace mathfunctions {
+namespace detail {
 // a hack square root calculation using simple operations
 double mysqrt(double x)
 {
@@ -22,3 +24,5 @@
   }
   return result;
 }
+}
+}
diff --git a/Help/guide/tutorial/Step4/MathFunctions/mysqrt.h b/Help/guide/tutorial/Step4/MathFunctions/mysqrt.h
new file mode 100644
index 0000000..593d41e
--- /dev/null
+++ b/Help/guide/tutorial/Step4/MathFunctions/mysqrt.h
@@ -0,0 +1,7 @@
+#pragma once
+
+namespace mathfunctions {
+namespace detail {
+double mysqrt(double x);
+}
+}
diff --git a/Help/guide/tutorial/Step4/TutorialConfig.h.in b/Help/guide/tutorial/Step4/TutorialConfig.h.in
index e23f521..7e4d7fa 100644
--- a/Help/guide/tutorial/Step4/TutorialConfig.h.in
+++ b/Help/guide/tutorial/Step4/TutorialConfig.h.in
@@ -1,4 +1,3 @@
 // the configured options and settings for Tutorial
 #define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
 #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
-#cmakedefine USE_MYMATH
diff --git a/Help/guide/tutorial/Step4/tutorial.cxx b/Help/guide/tutorial/Step4/tutorial.cxx
index b3c6a4f..a3a2bdc 100644
--- a/Help/guide/tutorial/Step4/tutorial.cxx
+++ b/Help/guide/tutorial/Step4/tutorial.cxx
@@ -3,13 +3,9 @@
 #include <iostream>
 #include <string>
 
+#include "MathFunctions.h"
 #include "TutorialConfig.h"
 
-// should we include the MathFunctions header?
-#ifdef USE_MYMATH
-#  include "MathFunctions.h"
-#endif
-
 int main(int argc, char* argv[])
 {
   if (argc < 2) {
@@ -23,12 +19,7 @@
   // convert input to double
   const double inputValue = std::stod(argv[1]);
 
-  // which square root function should we use?
-#ifdef USE_MYMATH
-  const double outputValue = mysqrt(inputValue);
-#else
-  const double outputValue = sqrt(inputValue);
-#endif
+  const double outputValue = mathfunctions::sqrt(inputValue);
 
   std::cout << "The square root of " << inputValue << " is " << outputValue
             << std::endl;
diff --git a/Help/guide/tutorial/Step5/CMakeLists.txt b/Help/guide/tutorial/Step5/CMakeLists.txt
index 279ddf9..ad814f6 100644
--- a/Help/guide/tutorial/Step5/CMakeLists.txt
+++ b/Help/guide/tutorial/Step5/CMakeLists.txt
@@ -16,22 +16,17 @@
   "$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
 )
 
-# should we use our own math functions
-option(USE_MYMATH "Use tutorial provided math implementation" ON)
-
 # configure a header file to pass some of the CMake settings
 # to the source code
 configure_file(TutorialConfig.h.in TutorialConfig.h)
 
 # add the MathFunctions library
-if(USE_MYMATH)
-  add_subdirectory(MathFunctions)
-  list(APPEND EXTRA_LIBS MathFunctions)
-endif()
+add_subdirectory(MathFunctions)
 
 # add the executable
 add_executable(Tutorial tutorial.cxx)
-target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS} tutorial_compiler_flags)
+
+target_link_libraries(Tutorial PUBLIC MathFunctions tutorial_compiler_flags)
 
 # add the binary tree to the search path for include files
 # so that we will find TutorialConfig.h
diff --git a/Help/guide/tutorial/Step5/MathFunctions/CMakeLists.txt b/Help/guide/tutorial/Step5/MathFunctions/CMakeLists.txt
index 6cd88d7..0c688f2 100644
--- a/Help/guide/tutorial/Step5/MathFunctions/CMakeLists.txt
+++ b/Help/guide/tutorial/Step5/MathFunctions/CMakeLists.txt
@@ -1,13 +1,28 @@
-add_library(MathFunctions mysqrt.cxx)
+add_library(MathFunctions MathFunctions.cxx)
 
 # state that anybody linking to us needs to include the current source dir
 # to find MathFunctions.h, while we don't.
 target_include_directories(MathFunctions
-          INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
-          )
+                           INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
+                           )
+
+# should we use our own math functions
+option(USE_MYMATH "Use tutorial provided math implementation" ON)
+if (USE_MYMATH)
+  target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
+
+  # library that just does sqrt
+  add_library(SqrtLibrary STATIC
+              mysqrt.cxx
+              )
+
+  # link our compiler flags interface library
+  target_link_libraries(SqrtLibrary PUBLIC tutorial_compiler_flags)
+  target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
+endif()
 
 # link our compiler flags interface library
-target_link_libraries(MathFunctions tutorial_compiler_flags)
+target_link_libraries(MathFunctions PUBLIC tutorial_compiler_flags)
 
 # TODO 1: Create a variable called installable_libs that is a list of all
 # libraries we want to install (e.g. MathFunctions and tutorial_compiler_flags)
diff --git a/Help/guide/tutorial/Step5/MathFunctions/MathFunctions.cxx b/Help/guide/tutorial/Step5/MathFunctions/MathFunctions.cxx
new file mode 100644
index 0000000..dc28b4b
--- /dev/null
+++ b/Help/guide/tutorial/Step5/MathFunctions/MathFunctions.cxx
@@ -0,0 +1,19 @@
+#include "MathFunctions.h"
+
+#include <cmath>
+
+#ifdef USE_MYMATH
+#  include "mysqrt.h"
+#endif
+
+namespace mathfunctions {
+double sqrt(double x)
+{
+// which square root function should we use?
+#ifdef USE_MYMATH
+  return detail::mysqrt(x);
+#else
+  return std::sqrt(x);
+#endif
+}
+}
diff --git a/Help/guide/tutorial/Step5/MathFunctions/MathFunctions.h b/Help/guide/tutorial/Step5/MathFunctions/MathFunctions.h
index cd36bcc..d5c2f22 100644
--- a/Help/guide/tutorial/Step5/MathFunctions/MathFunctions.h
+++ b/Help/guide/tutorial/Step5/MathFunctions/MathFunctions.h
@@ -1 +1,5 @@
-double mysqrt(double x);
+#pragma once
+
+namespace mathfunctions {
+double sqrt(double x);
+}
diff --git a/Help/guide/tutorial/Step5/MathFunctions/mysqrt.cxx b/Help/guide/tutorial/Step5/MathFunctions/mysqrt.cxx
index abe767d..ba0ac64 100644
--- a/Help/guide/tutorial/Step5/MathFunctions/mysqrt.cxx
+++ b/Help/guide/tutorial/Step5/MathFunctions/mysqrt.cxx
@@ -1,7 +1,9 @@
+#include "mysqrt.h"
+
 #include <iostream>
 
-#include "MathFunctions.h"
-
+namespace mathfunctions {
+namespace detail {
 // a hack square root calculation using simple operations
 double mysqrt(double x)
 {
@@ -22,3 +24,5 @@
   }
   return result;
 }
+}
+}
diff --git a/Help/guide/tutorial/Step5/MathFunctions/mysqrt.h b/Help/guide/tutorial/Step5/MathFunctions/mysqrt.h
new file mode 100644
index 0000000..593d41e
--- /dev/null
+++ b/Help/guide/tutorial/Step5/MathFunctions/mysqrt.h
@@ -0,0 +1,7 @@
+#pragma once
+
+namespace mathfunctions {
+namespace detail {
+double mysqrt(double x);
+}
+}
diff --git a/Help/guide/tutorial/Step5/TutorialConfig.h.in b/Help/guide/tutorial/Step5/TutorialConfig.h.in
index e23f521..7e4d7fa 100644
--- a/Help/guide/tutorial/Step5/TutorialConfig.h.in
+++ b/Help/guide/tutorial/Step5/TutorialConfig.h.in
@@ -1,4 +1,3 @@
 // the configured options and settings for Tutorial
 #define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
 #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
-#cmakedefine USE_MYMATH
diff --git a/Help/guide/tutorial/Step5/tutorial.cxx b/Help/guide/tutorial/Step5/tutorial.cxx
index b3c6a4f..a3a2bdc 100644
--- a/Help/guide/tutorial/Step5/tutorial.cxx
+++ b/Help/guide/tutorial/Step5/tutorial.cxx
@@ -3,13 +3,9 @@
 #include <iostream>
 #include <string>
 
+#include "MathFunctions.h"
 #include "TutorialConfig.h"
 
-// should we include the MathFunctions header?
-#ifdef USE_MYMATH
-#  include "MathFunctions.h"
-#endif
-
 int main(int argc, char* argv[])
 {
   if (argc < 2) {
@@ -23,12 +19,7 @@
   // convert input to double
   const double inputValue = std::stod(argv[1]);
 
-  // which square root function should we use?
-#ifdef USE_MYMATH
-  const double outputValue = mysqrt(inputValue);
-#else
-  const double outputValue = sqrt(inputValue);
-#endif
+  const double outputValue = mathfunctions::sqrt(inputValue);
 
   std::cout << "The square root of " << inputValue << " is " << outputValue
             << std::endl;
diff --git a/Help/guide/tutorial/Step6/CMakeLists.txt b/Help/guide/tutorial/Step6/CMakeLists.txt
index c11e307..a86d60a 100644
--- a/Help/guide/tutorial/Step6/CMakeLists.txt
+++ b/Help/guide/tutorial/Step6/CMakeLists.txt
@@ -16,22 +16,17 @@
   "$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
 )
 
-# should we use our own math functions
-option(USE_MYMATH "Use tutorial provided math implementation" ON)
-
 # configure a header file to pass some of the CMake settings
 # to the source code
 configure_file(TutorialConfig.h.in TutorialConfig.h)
 
 # add the MathFunctions library
-if(USE_MYMATH)
-  add_subdirectory(MathFunctions)
-  list(APPEND EXTRA_LIBS MathFunctions)
-endif()
+add_subdirectory(MathFunctions)
 
 # add the executable
 add_executable(Tutorial tutorial.cxx)
-target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS} tutorial_compiler_flags)
+
+target_link_libraries(Tutorial PUBLIC MathFunctions tutorial_compiler_flags)
 
 # add the binary tree to the search path for include files
 # so that we will find TutorialConfig.h
diff --git a/Help/guide/tutorial/Step6/MathFunctions/CMakeLists.txt b/Help/guide/tutorial/Step6/MathFunctions/CMakeLists.txt
index b4724c4..b1b925e 100644
--- a/Help/guide/tutorial/Step6/MathFunctions/CMakeLists.txt
+++ b/Help/guide/tutorial/Step6/MathFunctions/CMakeLists.txt
@@ -1,16 +1,33 @@
-add_library(MathFunctions mysqrt.cxx)
+add_library(MathFunctions MathFunctions.cxx)
 
 # state that anybody linking to us needs to include the current source dir
 # to find MathFunctions.h, while we don't.
 target_include_directories(MathFunctions
-          INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
-          )
+                           INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
+                           )
+
+# should we use our own math functions
+option(USE_MYMATH "Use tutorial provided math implementation" ON)
+if (USE_MYMATH)
+  target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
+
+  # library that just does sqrt
+  add_library(SqrtLibrary STATIC
+              mysqrt.cxx
+              )
+
+  target_link_libraries(SqrtLibrary PUBLIC tutorial_compiler_flags)
+  target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
+endif()
 
 # link our compiler flags interface library
-target_link_libraries(MathFunctions tutorial_compiler_flags)
+target_link_libraries(MathFunctions PUBLIC tutorial_compiler_flags)
 
 # install libs
 set(installable_libs MathFunctions tutorial_compiler_flags)
+if(TARGET SqrtLibrary)
+  list(APPEND installable_libs SqrtLibrary)
+endif()
 install(TARGETS ${installable_libs} DESTINATION lib)
 # install include headers
 install(FILES MathFunctions.h DESTINATION include)
diff --git a/Help/guide/tutorial/Step6/MathFunctions/MathFunctions.cxx b/Help/guide/tutorial/Step6/MathFunctions/MathFunctions.cxx
new file mode 100644
index 0000000..dc28b4b
--- /dev/null
+++ b/Help/guide/tutorial/Step6/MathFunctions/MathFunctions.cxx
@@ -0,0 +1,19 @@
+#include "MathFunctions.h"
+
+#include <cmath>
+
+#ifdef USE_MYMATH
+#  include "mysqrt.h"
+#endif
+
+namespace mathfunctions {
+double sqrt(double x)
+{
+// which square root function should we use?
+#ifdef USE_MYMATH
+  return detail::mysqrt(x);
+#else
+  return std::sqrt(x);
+#endif
+}
+}
diff --git a/Help/guide/tutorial/Step6/MathFunctions/MathFunctions.h b/Help/guide/tutorial/Step6/MathFunctions/MathFunctions.h
index cd36bcc..d5c2f22 100644
--- a/Help/guide/tutorial/Step6/MathFunctions/MathFunctions.h
+++ b/Help/guide/tutorial/Step6/MathFunctions/MathFunctions.h
@@ -1 +1,5 @@
-double mysqrt(double x);
+#pragma once
+
+namespace mathfunctions {
+double sqrt(double x);
+}
diff --git a/Help/guide/tutorial/Step6/MathFunctions/mysqrt.cxx b/Help/guide/tutorial/Step6/MathFunctions/mysqrt.cxx
index abe767d..ba0ac64 100644
--- a/Help/guide/tutorial/Step6/MathFunctions/mysqrt.cxx
+++ b/Help/guide/tutorial/Step6/MathFunctions/mysqrt.cxx
@@ -1,7 +1,9 @@
+#include "mysqrt.h"
+
 #include <iostream>
 
-#include "MathFunctions.h"
-
+namespace mathfunctions {
+namespace detail {
 // a hack square root calculation using simple operations
 double mysqrt(double x)
 {
@@ -22,3 +24,5 @@
   }
   return result;
 }
+}
+}
diff --git a/Help/guide/tutorial/Step6/MathFunctions/mysqrt.h b/Help/guide/tutorial/Step6/MathFunctions/mysqrt.h
new file mode 100644
index 0000000..593d41e
--- /dev/null
+++ b/Help/guide/tutorial/Step6/MathFunctions/mysqrt.h
@@ -0,0 +1,7 @@
+#pragma once
+
+namespace mathfunctions {
+namespace detail {
+double mysqrt(double x);
+}
+}
diff --git a/Help/guide/tutorial/Step6/TutorialConfig.h.in b/Help/guide/tutorial/Step6/TutorialConfig.h.in
index e23f521..7e4d7fa 100644
--- a/Help/guide/tutorial/Step6/TutorialConfig.h.in
+++ b/Help/guide/tutorial/Step6/TutorialConfig.h.in
@@ -1,4 +1,3 @@
 // the configured options and settings for Tutorial
 #define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
 #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
-#cmakedefine USE_MYMATH
diff --git a/Help/guide/tutorial/Step6/tutorial.cxx b/Help/guide/tutorial/Step6/tutorial.cxx
index b3c6a4f..a3a2bdc 100644
--- a/Help/guide/tutorial/Step6/tutorial.cxx
+++ b/Help/guide/tutorial/Step6/tutorial.cxx
@@ -3,13 +3,9 @@
 #include <iostream>
 #include <string>
 
+#include "MathFunctions.h"
 #include "TutorialConfig.h"
 
-// should we include the MathFunctions header?
-#ifdef USE_MYMATH
-#  include "MathFunctions.h"
-#endif
-
 int main(int argc, char* argv[])
 {
   if (argc < 2) {
@@ -23,12 +19,7 @@
   // convert input to double
   const double inputValue = std::stod(argv[1]);
 
-  // which square root function should we use?
-#ifdef USE_MYMATH
-  const double outputValue = mysqrt(inputValue);
-#else
-  const double outputValue = sqrt(inputValue);
-#endif
+  const double outputValue = mathfunctions::sqrt(inputValue);
 
   std::cout << "The square root of " << inputValue << " is " << outputValue
             << std::endl;
diff --git a/Help/guide/tutorial/Step7/CMakeLists.txt b/Help/guide/tutorial/Step7/CMakeLists.txt
index d26a90c..97ec6aa 100644
--- a/Help/guide/tutorial/Step7/CMakeLists.txt
+++ b/Help/guide/tutorial/Step7/CMakeLists.txt
@@ -16,22 +16,17 @@
   "$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
 )
 
-# should we use our own math functions
-option(USE_MYMATH "Use tutorial provided math implementation" ON)
-
 # configure a header file to pass some of the CMake settings
 # to the source code
 configure_file(TutorialConfig.h.in TutorialConfig.h)
 
 # add the MathFunctions library
-if(USE_MYMATH)
-  add_subdirectory(MathFunctions)
-  list(APPEND EXTRA_LIBS MathFunctions)
-endif()
+add_subdirectory(MathFunctions)
 
 # add the executable
 add_executable(Tutorial tutorial.cxx)
-target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS} tutorial_compiler_flags)
+
+target_link_libraries(Tutorial PUBLIC MathFunctions tutorial_compiler_flags)
 
 # add the binary tree to the search path for include files
 # so that we will find TutorialConfig.h
diff --git a/Help/guide/tutorial/Step7/MathFunctions/CMakeLists.txt b/Help/guide/tutorial/Step7/MathFunctions/CMakeLists.txt
index e5bdc4d..897ec0e 100644
--- a/Help/guide/tutorial/Step7/MathFunctions/CMakeLists.txt
+++ b/Help/guide/tutorial/Step7/MathFunctions/CMakeLists.txt
@@ -1,36 +1,54 @@
-add_library(MathFunctions mysqrt.cxx)
+add_library(MathFunctions MathFunctions.cxx)
 
 # state that anybody linking to us needs to include the current source dir
 # to find MathFunctions.h, while we don't.
 target_include_directories(MathFunctions
-          INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
-          )
+                           INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
+                           )
+
+# should we use our own math functions
+option(USE_MYMATH "Use tutorial provided math implementation" ON)
+if (USE_MYMATH)
+  target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
+
+  # library that just does sqrt
+  add_library(SqrtLibrary STATIC
+              mysqrt.cxx
+              )
+
+  target_link_libraries(SqrtLibrary PUBLIC tutorial_compiler_flags)
+
+  # TODO 1: Include CheckCXXSourceCompiles
+
+  # TODO 2: Use check_cxx_source_compiles with simple C++ code to verify
+  # availability of:
+  # * std::log
+  # * std::exp
+  # Store the results in HAVE_LOG and HAVE_EXP respectively.
+
+  # Hint: Sample C++ code which uses log:
+  # #include <cmath>
+  # int main() {
+  #   std::log(1.0);
+  #   return 0;
+  # }
+
+  # TODO 3: Conditionally on HAVE_LOG and HAVE_EXP, add private compile
+  # definitions "HAVE_LOG" and "HAVE_EXP" to the SqrtLibrary target.
+
+  # Hint: Use target_compile_definitions()
+
+  target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
+endif()
 
 # link our compiler flags interface library
-target_link_libraries(MathFunctions tutorial_compiler_flags)
-
-# TODO 1: Include CheckCXXSourceCompiles
-
-# TODO 2: Use check_cxx_source_compiles with simple C++ code to verify
-# availability of:
-# * std::log
-# * std::exp
-# Store the results in HAVE_LOG and HAVE_EXP respectively.
-
-# Hint: Sample C++ code which uses log:
-# #include <cmath>
-# int main() {
-#   std::log(1.0);
-#   return 0;
-# }
-
-# TODO 3: Conditionally on HAVE_LOG and HAVE_EXP, add private compile
-# definitions "HAVE_LOG" and "HAVE_EXP" to the MathFunctions target.
-
-#Hint: Use target_compile_definitions()
+target_link_libraries(MathFunctions PUBLIC tutorial_compiler_flags)
 
 # install libs
 set(installable_libs MathFunctions tutorial_compiler_flags)
+if(TARGET SqrtLibrary)
+  list(APPEND installable_libs SqrtLibrary)
+endif()
 install(TARGETS ${installable_libs} DESTINATION lib)
 # install include headers
 install(FILES MathFunctions.h DESTINATION include)
diff --git a/Help/guide/tutorial/Step7/MathFunctions/MathFunctions.cxx b/Help/guide/tutorial/Step7/MathFunctions/MathFunctions.cxx
new file mode 100644
index 0000000..dc28b4b
--- /dev/null
+++ b/Help/guide/tutorial/Step7/MathFunctions/MathFunctions.cxx
@@ -0,0 +1,19 @@
+#include "MathFunctions.h"
+
+#include <cmath>
+
+#ifdef USE_MYMATH
+#  include "mysqrt.h"
+#endif
+
+namespace mathfunctions {
+double sqrt(double x)
+{
+// which square root function should we use?
+#ifdef USE_MYMATH
+  return detail::mysqrt(x);
+#else
+  return std::sqrt(x);
+#endif
+}
+}
diff --git a/Help/guide/tutorial/Step7/MathFunctions/MathFunctions.h b/Help/guide/tutorial/Step7/MathFunctions/MathFunctions.h
index cd36bcc..d5c2f22 100644
--- a/Help/guide/tutorial/Step7/MathFunctions/MathFunctions.h
+++ b/Help/guide/tutorial/Step7/MathFunctions/MathFunctions.h
@@ -1 +1,5 @@
-double mysqrt(double x);
+#pragma once
+
+namespace mathfunctions {
+double sqrt(double x);
+}
diff --git a/Help/guide/tutorial/Step7/MathFunctions/mysqrt.cxx b/Help/guide/tutorial/Step7/MathFunctions/mysqrt.cxx
index 3d2492a..9963cff 100644
--- a/Help/guide/tutorial/Step7/MathFunctions/mysqrt.cxx
+++ b/Help/guide/tutorial/Step7/MathFunctions/mysqrt.cxx
@@ -1,8 +1,9 @@
+#include "mysqrt.h"
+
 #include <iostream>
 
-// TODO 4: include cmath
-#include "MathFunctions.h"
-
+namespace mathfunctions {
+namespace detail {
 // a hack square root calculation using simple operations
 double mysqrt(double x)
 {
@@ -32,3 +33,5 @@
 
   return result;
 }
+}
+}
diff --git a/Help/guide/tutorial/Step7/MathFunctions/mysqrt.h b/Help/guide/tutorial/Step7/MathFunctions/mysqrt.h
new file mode 100644
index 0000000..593d41e
--- /dev/null
+++ b/Help/guide/tutorial/Step7/MathFunctions/mysqrt.h
@@ -0,0 +1,7 @@
+#pragma once
+
+namespace mathfunctions {
+namespace detail {
+double mysqrt(double x);
+}
+}
diff --git a/Help/guide/tutorial/Step7/TutorialConfig.h.in b/Help/guide/tutorial/Step7/TutorialConfig.h.in
index e23f521..7e4d7fa 100644
--- a/Help/guide/tutorial/Step7/TutorialConfig.h.in
+++ b/Help/guide/tutorial/Step7/TutorialConfig.h.in
@@ -1,4 +1,3 @@
 // the configured options and settings for Tutorial
 #define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
 #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
-#cmakedefine USE_MYMATH
diff --git a/Help/guide/tutorial/Step7/tutorial.cxx b/Help/guide/tutorial/Step7/tutorial.cxx
index b3c6a4f..a3a2bdc 100644
--- a/Help/guide/tutorial/Step7/tutorial.cxx
+++ b/Help/guide/tutorial/Step7/tutorial.cxx
@@ -3,13 +3,9 @@
 #include <iostream>
 #include <string>
 
+#include "MathFunctions.h"
 #include "TutorialConfig.h"
 
-// should we include the MathFunctions header?
-#ifdef USE_MYMATH
-#  include "MathFunctions.h"
-#endif
-
 int main(int argc, char* argv[])
 {
   if (argc < 2) {
@@ -23,12 +19,7 @@
   // convert input to double
   const double inputValue = std::stod(argv[1]);
 
-  // which square root function should we use?
-#ifdef USE_MYMATH
-  const double outputValue = mysqrt(inputValue);
-#else
-  const double outputValue = sqrt(inputValue);
-#endif
+  const double outputValue = mathfunctions::sqrt(inputValue);
 
   std::cout << "The square root of " << inputValue << " is " << outputValue
             << std::endl;
diff --git a/Help/guide/tutorial/Step8/CMakeLists.txt b/Help/guide/tutorial/Step8/CMakeLists.txt
index cb87281..97ec6aa 100644
--- a/Help/guide/tutorial/Step8/CMakeLists.txt
+++ b/Help/guide/tutorial/Step8/CMakeLists.txt
@@ -16,23 +16,17 @@
   "$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
 )
 
-
-# should we use our own math functions
-option(USE_MYMATH "Use tutorial provided math implementation" ON)
-
 # configure a header file to pass some of the CMake settings
 # to the source code
 configure_file(TutorialConfig.h.in TutorialConfig.h)
 
 # add the MathFunctions library
-if(USE_MYMATH)
-  add_subdirectory(MathFunctions)
-  list(APPEND EXTRA_LIBS MathFunctions)
-endif()
+add_subdirectory(MathFunctions)
 
 # add the executable
 add_executable(Tutorial tutorial.cxx)
-target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS} tutorial_compiler_flags)
+
+target_link_libraries(Tutorial PUBLIC MathFunctions tutorial_compiler_flags)
 
 # add the binary tree to the search path for include files
 # so that we will find TutorialConfig.h
diff --git a/Help/guide/tutorial/Step8/MathFunctions/CMakeLists.txt b/Help/guide/tutorial/Step8/MathFunctions/CMakeLists.txt
index f81b563..872a24a 100644
--- a/Help/guide/tutorial/Step8/MathFunctions/CMakeLists.txt
+++ b/Help/guide/tutorial/Step8/MathFunctions/CMakeLists.txt
@@ -1,39 +1,58 @@
-add_library(MathFunctions mysqrt.cxx)
+add_library(MathFunctions MathFunctions.cxx)
+
+# should we use our own math functions
+option(USE_MYMATH "Use tutorial provided math implementation" ON)
+if (USE_MYMATH)
+  target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
+
+  # library that just does sqrt
+  add_library(SqrtLibrary STATIC
+              mysqrt.cxx
+              )
+
+  target_link_libraries(SqrtLibrary PUBLIC tutorial_compiler_flags)
+
+  # does this system provide the log and exp functions?
+  include(CheckCXXSourceCompiles)
+  check_cxx_source_compiles("
+    #include <cmath>
+    int main() {
+      std::log(1.0);
+      return 0;
+    }
+  " HAVE_LOG)
+  check_cxx_source_compiles("
+    #include <cmath>
+    int main() {
+      std::exp(1.0);
+      return 0;
+    }
+  " HAVE_EXP)
+
+  # add compile definitions
+  if(HAVE_LOG AND HAVE_EXP)
+    target_compile_definitions(SqrtLibrary
+                               PRIVATE "HAVE_LOG" "HAVE_EXP"
+                               )
+  endif()
+
+  target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
+endif()
 
 # state that anybody linking to us needs to include the current source dir
 # to find MathFunctions.h, while we don't.
 target_include_directories(MathFunctions
-          INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
-          )
+                           INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
+                           )
 
 # link our compiler flags interface library
-target_link_libraries(MathFunctions tutorial_compiler_flags)
-
-# does this system provide the log and exp functions?
-include(CheckCXXSourceCompiles)
-check_cxx_source_compiles("
-  #include <cmath>
-  int main() {
-    std::log(1.0);
-    return 0;
-  }
-" HAVE_LOG)
-check_cxx_source_compiles("
-  #include <cmath>
-  int main() {
-    std::exp(1.0);
-    return 0;
-  }
-" HAVE_EXP)
-
-# add compile definitions
-if(HAVE_LOG AND HAVE_EXP)
-  target_compile_definitions(MathFunctions
-                             PRIVATE "HAVE_LOG" "HAVE_EXP")
-endif()
+target_link_libraries(MathFunctions PUBLIC tutorial_compiler_flags)
 
 # install libs
 set(installable_libs MathFunctions tutorial_compiler_flags)
+if(TARGET SqrtLibrary)
+  list(APPEND installable_libs SqrtLibrary)
+endif()
 install(TARGETS ${installable_libs} DESTINATION lib)
 # install include headers
 install(FILES MathFunctions.h DESTINATION include)
diff --git a/Help/guide/tutorial/Step8/MathFunctions/MathFunctions.cxx b/Help/guide/tutorial/Step8/MathFunctions/MathFunctions.cxx
new file mode 100644
index 0000000..dc28b4b
--- /dev/null
+++ b/Help/guide/tutorial/Step8/MathFunctions/MathFunctions.cxx
@@ -0,0 +1,19 @@
+#include "MathFunctions.h"
+
+#include <cmath>
+
+#ifdef USE_MYMATH
+#  include "mysqrt.h"
+#endif
+
+namespace mathfunctions {
+double sqrt(double x)
+{
+// which square root function should we use?
+#ifdef USE_MYMATH
+  return detail::mysqrt(x);
+#else
+  return std::sqrt(x);
+#endif
+}
+}
diff --git a/Help/guide/tutorial/Step8/MathFunctions/MathFunctions.h b/Help/guide/tutorial/Step8/MathFunctions/MathFunctions.h
index cd36bcc..d5c2f22 100644
--- a/Help/guide/tutorial/Step8/MathFunctions/MathFunctions.h
+++ b/Help/guide/tutorial/Step8/MathFunctions/MathFunctions.h
@@ -1 +1,5 @@
-double mysqrt(double x);
+#pragma once
+
+namespace mathfunctions {
+double sqrt(double x);
+}
diff --git a/Help/guide/tutorial/Step8/MathFunctions/mysqrt.cxx b/Help/guide/tutorial/Step8/MathFunctions/mysqrt.cxx
index 7eecd26..28ab042 100644
--- a/Help/guide/tutorial/Step8/MathFunctions/mysqrt.cxx
+++ b/Help/guide/tutorial/Step8/MathFunctions/mysqrt.cxx
@@ -1,8 +1,10 @@
+#include "mysqrt.h"
+
 #include <cmath>
 #include <iostream>
 
-#include "MathFunctions.h"
-
+namespace mathfunctions {
+namespace detail {
 // a hack square root calculation using simple operations
 double mysqrt(double x)
 {
@@ -30,3 +32,5 @@
 #endif
   return result;
 }
+}
+}
diff --git a/Help/guide/tutorial/Step8/MathFunctions/mysqrt.h b/Help/guide/tutorial/Step8/MathFunctions/mysqrt.h
new file mode 100644
index 0000000..593d41e
--- /dev/null
+++ b/Help/guide/tutorial/Step8/MathFunctions/mysqrt.h
@@ -0,0 +1,7 @@
+#pragma once
+
+namespace mathfunctions {
+namespace detail {
+double mysqrt(double x);
+}
+}
diff --git a/Help/guide/tutorial/Step8/TutorialConfig.h.in b/Help/guide/tutorial/Step8/TutorialConfig.h.in
index e23f521..7e4d7fa 100644
--- a/Help/guide/tutorial/Step8/TutorialConfig.h.in
+++ b/Help/guide/tutorial/Step8/TutorialConfig.h.in
@@ -1,4 +1,3 @@
 // the configured options and settings for Tutorial
 #define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
 #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
-#cmakedefine USE_MYMATH
diff --git a/Help/guide/tutorial/Step8/tutorial.cxx b/Help/guide/tutorial/Step8/tutorial.cxx
index b3c6a4f..a3a2bdc 100644
--- a/Help/guide/tutorial/Step8/tutorial.cxx
+++ b/Help/guide/tutorial/Step8/tutorial.cxx
@@ -3,13 +3,9 @@
 #include <iostream>
 #include <string>
 
+#include "MathFunctions.h"
 #include "TutorialConfig.h"
 
-// should we include the MathFunctions header?
-#ifdef USE_MYMATH
-#  include "MathFunctions.h"
-#endif
-
 int main(int argc, char* argv[])
 {
   if (argc < 2) {
@@ -23,12 +19,7 @@
   // convert input to double
   const double inputValue = std::stod(argv[1]);
 
-  // which square root function should we use?
-#ifdef USE_MYMATH
-  const double outputValue = mysqrt(inputValue);
-#else
-  const double outputValue = sqrt(inputValue);
-#endif
+  const double outputValue = mathfunctions::sqrt(inputValue);
 
   std::cout << "The square root of " << inputValue << " is " << outputValue
             << std::endl;
diff --git a/Help/guide/tutorial/Step9/CMakeLists.txt b/Help/guide/tutorial/Step9/CMakeLists.txt
index d26a90c..97ec6aa 100644
--- a/Help/guide/tutorial/Step9/CMakeLists.txt
+++ b/Help/guide/tutorial/Step9/CMakeLists.txt
@@ -16,22 +16,17 @@
   "$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
 )
 
-# should we use our own math functions
-option(USE_MYMATH "Use tutorial provided math implementation" ON)
-
 # configure a header file to pass some of the CMake settings
 # to the source code
 configure_file(TutorialConfig.h.in TutorialConfig.h)
 
 # add the MathFunctions library
-if(USE_MYMATH)
-  add_subdirectory(MathFunctions)
-  list(APPEND EXTRA_LIBS MathFunctions)
-endif()
+add_subdirectory(MathFunctions)
 
 # add the executable
 add_executable(Tutorial tutorial.cxx)
-target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS} tutorial_compiler_flags)
+
+target_link_libraries(Tutorial PUBLIC MathFunctions tutorial_compiler_flags)
 
 # add the binary tree to the search path for include files
 # so that we will find TutorialConfig.h
diff --git a/Help/guide/tutorial/Step9/MathFunctions/CMakeLists.txt b/Help/guide/tutorial/Step9/MathFunctions/CMakeLists.txt
index 8e04f97..54cecf8 100644
--- a/Help/guide/tutorial/Step9/MathFunctions/CMakeLists.txt
+++ b/Help/guide/tutorial/Step9/MathFunctions/CMakeLists.txt
@@ -1,34 +1,41 @@
-# first we add the executable that generates the table
-add_executable(MakeTable MakeTable.cxx)
-
-# add the command to generate the source code
-add_custom_command(
-  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
-  COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
-  DEPENDS MakeTable
-  )
-
-# add the main library
-add_library(MathFunctions
-            mysqrt.cxx
-            ${CMAKE_CURRENT_BINARY_DIR}/Table.h
-            )
+add_library(MathFunctions MathFunctions.cxx)
 
 # state that anybody linking to us needs to include the current source dir
 # to find MathFunctions.h, while we don't.
-# state that we depend on Tutorial_BINARY_DIR but consumers don't, as the
-# TutorialConfig.h include is an implementation detail
-# state that we depend on our binary dir to find Table.h
 target_include_directories(MathFunctions
-          INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
-          PRIVATE   ${CMAKE_CURRENT_BINARY_DIR}
-          )
+                           INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
+                           )
 
-# link our compiler flags interface library
-target_link_libraries(MathFunctions tutorial_compiler_flags)
+# should we use our own math functions
+option(USE_MYMATH "Use tutorial provided math implementation" ON)
+if (USE_MYMATH)
+  target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
+
+  # generate Table.h
+  include(MakeTable.cmake)
+
+  # library that just does sqrt
+  add_library(SqrtLibrary STATIC
+              mysqrt.cxx
+              ${CMAKE_CURRENT_BINARY_DIR}/Table.h
+              )
+
+  # state that we depend on our binary dir to find Table.h
+  target_include_directories(SqrtLibrary PRIVATE
+                             ${CMAKE_CURRENT_BINARY_DIR}
+                             )
+
+  target_link_libraries(SqrtLibrary PUBLIC tutorial_compiler_flags)
+  target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
+endif()
+
+target_link_libraries(MathFunctions PUBLIC tutorial_compiler_flags)
 
 # install libs
 set(installable_libs MathFunctions tutorial_compiler_flags)
+if(TARGET SqrtLibrary)
+  list(APPEND installable_libs SqrtLibrary)
+endif()
 install(TARGETS ${installable_libs} DESTINATION lib)
 # install include headers
 install(FILES MathFunctions.h DESTINATION include)
diff --git a/Help/guide/tutorial/Step9/MathFunctions/MakeTable.cmake b/Help/guide/tutorial/Step9/MathFunctions/MakeTable.cmake
new file mode 100644
index 0000000..12865a9
--- /dev/null
+++ b/Help/guide/tutorial/Step9/MathFunctions/MakeTable.cmake
@@ -0,0 +1,10 @@
+# first we add the executable that generates the table
+add_executable(MakeTable MakeTable.cxx)
+target_link_libraries(MakeTable PRIVATE tutorial_compiler_flags)
+
+# add the command to generate the source code
+add_custom_command(
+  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
+  COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
+  DEPENDS MakeTable
+  )
diff --git a/Help/guide/tutorial/Step9/MathFunctions/MathFunctions.cxx b/Help/guide/tutorial/Step9/MathFunctions/MathFunctions.cxx
new file mode 100644
index 0000000..dc28b4b
--- /dev/null
+++ b/Help/guide/tutorial/Step9/MathFunctions/MathFunctions.cxx
@@ -0,0 +1,19 @@
+#include "MathFunctions.h"
+
+#include <cmath>
+
+#ifdef USE_MYMATH
+#  include "mysqrt.h"
+#endif
+
+namespace mathfunctions {
+double sqrt(double x)
+{
+// which square root function should we use?
+#ifdef USE_MYMATH
+  return detail::mysqrt(x);
+#else
+  return std::sqrt(x);
+#endif
+}
+}
diff --git a/Help/guide/tutorial/Step9/MathFunctions/MathFunctions.h b/Help/guide/tutorial/Step9/MathFunctions/MathFunctions.h
index cd36bcc..d5c2f22 100644
--- a/Help/guide/tutorial/Step9/MathFunctions/MathFunctions.h
+++ b/Help/guide/tutorial/Step9/MathFunctions/MathFunctions.h
@@ -1 +1,5 @@
-double mysqrt(double x);
+#pragma once
+
+namespace mathfunctions {
+double sqrt(double x);
+}
diff --git a/Help/guide/tutorial/Step9/MathFunctions/mysqrt.cxx b/Help/guide/tutorial/Step9/MathFunctions/mysqrt.cxx
index 7d80ee9..477d715 100644
--- a/Help/guide/tutorial/Step9/MathFunctions/mysqrt.cxx
+++ b/Help/guide/tutorial/Step9/MathFunctions/mysqrt.cxx
@@ -1,10 +1,12 @@
-#include <iostream>
+#include "mysqrt.h"
 
-#include "MathFunctions.h"
+#include <iostream>
 
 // include the generated table
 #include "Table.h"
 
+namespace mathfunctions {
+namespace detail {
 // a hack square root calculation using simple operations
 double mysqrt(double x)
 {
@@ -31,3 +33,5 @@
 
   return result;
 }
+}
+}
diff --git a/Help/guide/tutorial/Step9/MathFunctions/mysqrt.h b/Help/guide/tutorial/Step9/MathFunctions/mysqrt.h
new file mode 100644
index 0000000..593d41e
--- /dev/null
+++ b/Help/guide/tutorial/Step9/MathFunctions/mysqrt.h
@@ -0,0 +1,7 @@
+#pragma once
+
+namespace mathfunctions {
+namespace detail {
+double mysqrt(double x);
+}
+}
diff --git a/Help/guide/tutorial/Step9/TutorialConfig.h.in b/Help/guide/tutorial/Step9/TutorialConfig.h.in
index e23f521..7e4d7fa 100644
--- a/Help/guide/tutorial/Step9/TutorialConfig.h.in
+++ b/Help/guide/tutorial/Step9/TutorialConfig.h.in
@@ -1,4 +1,3 @@
 // the configured options and settings for Tutorial
 #define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
 #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
-#cmakedefine USE_MYMATH
diff --git a/Help/guide/tutorial/Step9/tutorial.cxx b/Help/guide/tutorial/Step9/tutorial.cxx
index b3c6a4f..a3a2bdc 100644
--- a/Help/guide/tutorial/Step9/tutorial.cxx
+++ b/Help/guide/tutorial/Step9/tutorial.cxx
@@ -3,13 +3,9 @@
 #include <iostream>
 #include <string>
 
+#include "MathFunctions.h"
 #include "TutorialConfig.h"
 
-// should we include the MathFunctions header?
-#ifdef USE_MYMATH
-#  include "MathFunctions.h"
-#endif
-
 int main(int argc, char* argv[])
 {
   if (argc < 2) {
@@ -23,12 +19,7 @@
   // convert input to double
   const double inputValue = std::stod(argv[1]);
 
-  // which square root function should we use?
-#ifdef USE_MYMATH
-  const double outputValue = mysqrt(inputValue);
-#else
-  const double outputValue = sqrt(inputValue);
-#endif
+  const double outputValue = mathfunctions::sqrt(inputValue);
 
   std::cout << "The square root of " << inputValue << " is " << outputValue
             << std::endl;
diff --git a/Help/manual/ccmake.1.rst b/Help/manual/ccmake.1.rst
index a09857b..5b118d1 100644
--- a/Help/manual/ccmake.1.rst
+++ b/Help/manual/ccmake.1.rst
@@ -8,6 +8,7 @@
 
 .. parsed-literal::
 
+ ccmake [<options>] -B <path-to-build> [-S <path-to-source>]
  ccmake [<options>] <path-to-source | path-to-existing-build>
 
 Description
diff --git a/Help/manual/cmake-buildsystem.7.rst b/Help/manual/cmake-buildsystem.7.rst
index b9d621b..b88b864 100644
--- a/Help/manual/cmake-buildsystem.7.rst
+++ b/Help/manual/cmake-buildsystem.7.rst
@@ -37,6 +37,8 @@
 When linking the ``zipapp`` executable, the ``archive`` static library is
 linked in.
 
+.. _`Binary Executables`:
+
 Binary Executables
 ------------------
 
@@ -797,6 +799,10 @@
   created by the :command:`add_executable` command when its
   :prop_tgt:`ENABLE_EXPORTS` target property is set.
 
+* On macOS: the linker import file (e.g. ``.tbd``) of a shared library target
+  created by the :command:`add_library` command with the ``SHARED`` option and
+  when its :prop_tgt:`ENABLE_EXPORTS` target property is set.
+
 The :prop_tgt:`ARCHIVE_OUTPUT_DIRECTORY` and :prop_tgt:`ARCHIVE_OUTPUT_NAME`
 target properties may be used to control archive output artifact locations
 and names in the build tree.
diff --git a/Help/manual/cmake-env-variables.7.rst b/Help/manual/cmake-env-variables.7.rst
index 4c29b80..197e56e 100644
--- a/Help/manual/cmake-env-variables.7.rst
+++ b/Help/manual/cmake-env-variables.7.rst
@@ -20,7 +20,13 @@
 .. toctree::
    :maxdepth: 1
 
+   /envvar/CMAKE_APPBUNDLE_PATH
+   /envvar/CMAKE_FRAMEWORK_PATH
+   /envvar/CMAKE_INCLUDE_PATH
+   /envvar/CMAKE_LIBRARY_PATH
+   /envvar/CMAKE_MAXIMUM_RECURSION_DEPTH
    /envvar/CMAKE_PREFIX_PATH
+   /envvar/CMAKE_PROGRAM_PATH
    /envvar/SSL_CERT_DIR
    /envvar/SSL_CERT_FILE
 
@@ -44,6 +50,7 @@
    /envvar/CMAKE_GENERATOR_TOOLSET
    /envvar/CMAKE_INSTALL_MODE
    /envvar/CMAKE_LANG_COMPILER_LAUNCHER
+   /envvar/CMAKE_LANG_IMPLICIT_LINK_DIRECTORIES_EXCLUDE
    /envvar/CMAKE_LANG_LINKER_LAUNCHER
    /envvar/CMAKE_MSVCIDE_RUN_PATH
    /envvar/CMAKE_NO_VERBOSE
diff --git a/Help/manual/cmake-file-api.7.rst b/Help/manual/cmake-file-api.7.rst
index 7ff9728..0bdb419 100644
--- a/Help/manual/cmake-file-api.7.rst
+++ b/Help/manual/cmake-file-api.7.rst
@@ -425,7 +425,7 @@
 
   {
     "kind": "codemodel",
-    "version": { "major": 2, "minor": 5 },
+    "version": { "major": 2, "minor": 6 },
     "paths": {
       "source": "/path/to/top-level-source-dir",
       "build": "/path/to/top-level-build-dir"
@@ -1211,6 +1211,28 @@
       an unsigned integer 0-based index into the ``backtraceGraph``
       member's ``nodes`` array.
 
+  ``frameworks``
+    Optional member that is present when, on Apple platforms, there are
+    frameworks. The value is a JSON array with an entry for each directory.
+    Each entry is a JSON object with members:
+
+    ``path``
+      A string specifying the path to the framework directory,
+      represented with forward slashes.
+
+    ``isSystem``
+      Optional member that is present with boolean value ``true`` if
+      the framework is marked as a system one.
+
+    ``backtrace``
+      Optional member that is present when a CMake language backtrace to
+      the :command:`target_link_libraries` or other command invocation
+      that added this framework is available.  The value is
+      an unsigned integer 0-based index into the ``backtraceGraph``
+      member's ``nodes`` array.
+
+    This field was added in codemodel version 2.6.
+
   ``precompileHeaders``
     Optional member that is present when :command:`target_precompile_headers`
     or other command invocations set :prop_tgt:`PRECOMPILE_HEADERS` on the
diff --git a/Help/manual/cmake-generator-expressions.7.rst b/Help/manual/cmake-generator-expressions.7.rst
index c3e87d7..473e8d7 100644
--- a/Help/manual/cmake-generator-expressions.7.rst
+++ b/Help/manual/cmake-generator-expressions.7.rst
@@ -104,6 +104,17 @@
     VERBATIM
   )
 
+Finally, the above example can be expressed in a more simple and robust way
+using an alternate generator expression:
+
+.. code-block:: cmake
+
+  add_custom_target(run_some_tool
+    COMMAND some_tool "$<LIST:TRANSFORM,$<TARGET_PROPERTY:tgt,INCLUDE_DIRECTORIES>,PREPEND,-I>"
+    COMMAND_EXPAND_LISTS
+    VERBATIM
+  )
+
 A common mistake is to try to split a generator expression across multiple
 lines with indenting:
 
@@ -318,6 +329,15 @@
 List Expressions
 ----------------
 
+Most of the expressions in this section are closely associated with the
+:command:`list` command, providing the same capabilities, but in
+the form of a generator expression.
+
+.. _GenEx List Comparisons:
+
+List Comparisons
+^^^^^^^^^^^^^^^^
+
 .. genex:: $<IN_LIST:string,list>
 
   .. versionadded:: 3.12
@@ -325,9 +345,186 @@
   ``1`` if ``string`` is an item in the semicolon-separated ``list``, else ``0``.
   It uses case-sensitive comparisons.
 
-.. genex:: $<JOIN:list,string>
+.. _GenEx List Queries:
 
-  Joins the list with the content of ``string`` inserted between each item.
+List Queries
+^^^^^^^^^^^^
+
+.. genex:: $<LIST:LENGTH,list>
+
+  .. versionadded:: 3.27
+
+  Returns the list's length.
+
+.. genex:: $<LIST:GET,list,index,...>
+
+  .. versionadded:: 3.27
+
+  Returns the list of elements specified by indices from the list.
+
+.. genex:: $<LIST:SUBLIST,list,begin,length>
+
+  .. versionadded:: 3.27
+
+  Returns a sublist of the given list. If <length> is 0, an empty list will be
+  returned. If <length> is -1 or the list is smaller than <begin>+<length> then
+  the remaining elements of the list starting at <begin> will be returned.
+
+.. genex:: $<LIST:FIND,list,value>
+
+  .. versionadded:: 3.27
+
+  Returns the index of the element specified in the list or -1 if it wasn't
+  found.
+
+.. _GenEx List Transformations:
+
+List Transformations
+^^^^^^^^^^^^^^^^^^^^
+
+.. genex:: $<LIST:JOIN,list,glue>
+
+  .. versionadded:: 3.27
+
+  Returns a string which joins the list with the content of the ``glue`` string
+  inserted between each item.
+
+.. genex:: $<LIST:APPEND,list,element,...>
+
+  .. versionadded:: 3.27
+
+  Returns a list with the elements appended.
+
+.. genex:: $<LIST:PREPEND,list,element,...>
+
+  .. versionadded:: 3.27
+
+  Returns a list with the elements inserted at the beginning of the list.
+
+.. genex:: $<LIST:INSERT,list,index,element,...>
+
+  .. versionadded:: 3.27
+
+  Returns a list with the elements inserted at the specified index. It is an
+  error to specify an out-of-range index. Valid indexes are 0 to N where N is
+  the length of the list, inclusive. An empty list has length 0.
+
+.. genex:: $<LIST:POP_BACK,list>
+
+  .. versionadded:: 3.27
+
+  Returns a list with the last element was removed.
+
+.. genex:: $<LIST:POP_FRONT,list>
+
+  .. versionadded:: 3.27
+
+  Returns a list with the first element was removed.
+
+.. genex:: $<LIST:REMOVE_ITEM,list,value,...>
+
+  .. versionadded:: 3.27
+
+  Returns a list with all instances of the given values were removed.
+
+.. genex:: $<LIST:REMOVE_AT,list,index,...>
+
+  .. versionadded:: 3.27
+
+  Returns a list with all values at given indices were removed.
+
+.. genex:: $<LIST:REMOVE_DUPLICATES,list>
+
+  .. versionadded:: 3.27
+
+  Returns a list where duplicated items were removed. The relative order of
+  items is preserved, but if duplicates are encountered, only the first
+  instance is preserved.
+
+.. genex:: $<LIST:FILTER,list,INCLUDE|EXCLUDE,regex>
+
+  .. versionadded:: 3.27
+
+  Returns a list with the items that match the regular expression ``regex``
+  were included or removed.
+
+.. genex:: $<LIST:TRANSFORM,list,ACTION[,SELECTOR]>
+
+  .. versionadded:: 3.27
+
+  Returns the list transformed by applying an ``ACTION`` to all or, by
+  specifying a ``SELECTOR``, to the selected elements of the list.
+
+  .. note::
+
+    The ``TRANSFORM`` sub-command does not change the number of elements in the
+    list. If a ``SELECTOR`` is specified, only some elements will be changed,
+    the other ones will remain the same as before the transformation.
+
+  ``ACTION`` specifies the action to apply to the elements of the list.
+  The actions have exactly the same semantics as of the
+  :command:`list(TRANSFORM)` command.  ``ACTION`` must be one of the following:
+
+    :command:`APPEND <list(TRANSFORM_APPEND)>`, :command:`PREPEND <list(TRANSFORM_APPEND)>`
+      Append, prepend specified value to each element of the list.
+
+      .. code-block:: cmake
+
+        $<LIST:TRANSFORM,list,(APPEND|PREPEND),value[,SELECTOR]>
+
+    :command:`TOLOWER <list(TRANSFORM_TOLOWER)>`, :command:`TOUPPER <list(TRANSFORM_TOLOWER)>`
+      Convert each element of the list to lower, upper characters.
+
+      .. code-block:: cmake
+
+        $<LIST:TRANSFORM,list,(TOLOWER|TOUPPER)[,SELECTOR]>
+
+    :command:`STRIP <list(TRANSFORM_STRIP)>`
+      Remove leading and trailing spaces from each element of the list.
+
+      .. code-block:: cmake
+
+        $<LIST:TRANSFORM,list,STRIP[,SELECTOR]>
+
+    :command:`REPLACE <list(TRANSFORM_REPLACE)>`:
+      Match the regular expression as many times as possible and substitute
+      the replacement expression for the match for each element of the list.
+
+      .. code-block:: cmake
+
+        $<LIST:TRANSFORM,list,REPLACE,regular_expression,replace_expression[,SELECTOR]>
+
+  ``SELECTOR`` determines which elements of the list will be transformed.
+  Only one type of selector can be specified at a time. When given,
+  ``SELECTOR`` must be one of the following:
+
+    ``AT``
+      Specify a list of indexes.
+
+      .. code-block:: cmake
+
+        $<LIST:TRANSFORM,list,ACTION,AT,index[,index...]>
+
+    ``FOR``
+      Specify a range with, optionally, an increment used to iterate over the
+      range.
+
+      .. code-block:: cmake
+
+        $<LIST:TRANSFORM,list,ACTION,FOR,start,stop[,step]>
+
+    ``REGEX``
+      Specify a regular expression.
+      Only elements matching the regular expression will be transformed.
+
+      .. code-block:: cmake
+
+        $<LIST:TRANSFORM,list,ACTION,REGEX,regular_expression>
+
+.. genex:: $<JOIN:list,glue>
+
+  Joins the list with the content of the ``glue`` string inserted between each
+  item.
 
 .. genex:: $<REMOVE_DUPLICATES:list>
 
@@ -344,6 +541,69 @@
   Includes or removes items from ``list`` that match the regular expression
   ``regex``.
 
+.. _GenEx List Ordering:
+
+List Ordering
+^^^^^^^^^^^^^
+
+.. genex:: $<LIST:REVERSE,list>
+
+  .. versionadded:: 3.27
+
+  Returns the list with the elements in reverse order.
+
+.. genex:: $<LIST:SORT,list[,(COMPARE:option|CASE:option|ORDER:option)]...>
+
+  .. versionadded:: 3.27
+
+  Returns the list sorted according the specified options.
+
+  Use one of the ``COMPARE`` options to select the comparison method
+  for sorting:
+
+    ``STRING``
+      Sorts a list of strings alphabetically.
+      This is the default behavior if the ``COMPARE`` option is not given.
+
+    ``FILE_BASENAME``
+      Sorts a list of pathnames of files by their basenames.
+
+    ``NATURAL``
+      Sorts a list of strings using natural order
+      (see ``strverscmp(3)`` manual), i.e. such that contiguous digits
+      are compared as whole numbers.
+      For example: the following list `10.0 1.1 2.1 8.0 2.0 3.1`
+      will be sorted as `1.1 2.0 2.1 3.1 8.0 10.0` if the ``NATURAL``
+      comparison is selected where it will be sorted as
+      `1.1 10.0 2.0 2.1 3.1 8.0` with the ``STRING`` comparison.
+
+  Use one of the ``CASE`` options to select a case sensitive or case
+  insensitive sort mode:
+
+    ``SENSITIVE``
+      List items are sorted in a case-sensitive manner.
+      This is the default behavior if the ``CASE`` option is not given.
+
+    ``INSENSITIVE``
+      List items are sorted case insensitively.  The order of
+      items which differ only by upper/lowercase is not specified.
+
+  To control the sort order, one of the ``ORDER`` options can be given:
+
+    ``ASCENDING``
+      Sorts the list in ascending order.
+      This is the default behavior when the ``ORDER`` option is not given.
+
+    ``DESCENDING``
+      Sorts the list in descending order.
+
+  This is an error to specify multiple times the same option. Various options
+  can be specified in any order:
+
+  .. code-block:: cmake
+
+    $<LIST:SORT,list,CASE:SENSITIVE,COMPARE:STRING,ORDER:DESCENDING>
+
 Path Expressions
 ----------------
 
@@ -446,16 +706,20 @@
   components from a path. See :ref:`Path Structure And Terminology` for the
   meaning of each path component.
 
+  .. versionchanged:: 3.27
+    All operations now accept a list of paths as argument. When a list of paths
+    is specified, the operation will be applied to each path.
+
   ::
 
-    $<PATH:GET_ROOT_NAME,path>
-    $<PATH:GET_ROOT_DIRECTORY,path>
-    $<PATH:GET_ROOT_PATH,path>
-    $<PATH:GET_FILENAME,path>
-    $<PATH:GET_EXTENSION[,LAST_ONLY],path>
-    $<PATH:GET_STEM[,LAST_ONLY],path>
-    $<PATH:GET_RELATIVE_PART,path>
-    $<PATH:GET_PARENT_PATH,path>
+    $<PATH:GET_ROOT_NAME,path...>
+    $<PATH:GET_ROOT_DIRECTORY,path...>
+    $<PATH:GET_ROOT_PATH,path...>
+    $<PATH:GET_FILENAME,path...>
+    $<PATH:GET_EXTENSION[,LAST_ONLY],path...>
+    $<PATH:GET_STEM[,LAST_ONLY],path...>
+    $<PATH:GET_RELATIVE_PART,path...>
+    $<PATH:GET_PARENT_PATH,path...>
 
   If a requested component is not present in the path, an empty string is
   returned.
@@ -470,9 +734,14 @@
 options of the :command:`cmake_path` command.  All paths are expected to be
 in cmake-style format.
 
+.. versionchanged:: 3.27
+  All operations now accept a list of paths as argument. When a list of paths
+  is specified, the operation will be applied to each path.
+
+
 .. _GenEx PATH-CMAKE_PATH:
 
-.. genex:: $<PATH:CMAKE_PATH[,NORMALIZE],path>
+.. genex:: $<PATH:CMAKE_PATH[,NORMALIZE],path...>
 
   .. versionadded:: 3.24
 
@@ -483,7 +752,7 @@
   When the ``NORMALIZE`` option is specified, the path is :ref:`normalized
   <Normalization>` after the conversion.
 
-.. genex:: $<PATH:APPEND,path,input,...>
+.. genex:: $<PATH:APPEND,path...,input,...>
 
   .. versionadded:: 3.24
 
@@ -493,7 +762,7 @@
 
   See :ref:`cmake_path(APPEND) <APPEND>` for more details.
 
-.. genex:: $<PATH:REMOVE_FILENAME,path>
+.. genex:: $<PATH:REMOVE_FILENAME,path...>
 
   .. versionadded:: 3.24
 
@@ -503,7 +772,7 @@
 
   See :ref:`cmake_path(REMOVE_FILENAME) <REMOVE_FILENAME>` for more details.
 
-.. genex:: $<PATH:REPLACE_FILENAME,path,input>
+.. genex:: $<PATH:REPLACE_FILENAME,path...,input>
 
   .. versionadded:: 3.24
 
@@ -513,7 +782,7 @@
 
   See :ref:`cmake_path(REPLACE_FILENAME) <REPLACE_FILENAME>` for more details.
 
-.. genex:: $<PATH:REMOVE_EXTENSION[,LAST_ONLY],path>
+.. genex:: $<PATH:REMOVE_EXTENSION[,LAST_ONLY],path...>
 
   .. versionadded:: 3.24
 
@@ -521,7 +790,7 @@
 
   See :ref:`cmake_path(REMOVE_EXTENSION) <REMOVE_EXTENSION>` for more details.
 
-.. genex:: $<PATH:REPLACE_EXTENSION[,LAST_ONLY],path,input>
+.. genex:: $<PATH:REPLACE_EXTENSION[,LAST_ONLY],path...,input>
 
   .. versionadded:: 3.24
 
@@ -530,14 +799,14 @@
 
   See :ref:`cmake_path(REPLACE_EXTENSION) <REPLACE_EXTENSION>` for more details.
 
-.. genex:: $<PATH:NORMAL_PATH,path>
+.. genex:: $<PATH:NORMAL_PATH,path...>
 
   .. versionadded:: 3.24
 
   Returns ``path`` normalized according to the steps described in
   :ref:`Normalization`.
 
-.. genex:: $<PATH:RELATIVE_PATH,path,base_directory>
+.. genex:: $<PATH:RELATIVE_PATH,path...,base_directory>
 
   .. versionadded:: 3.24
 
@@ -547,7 +816,7 @@
   See :ref:`cmake_path(RELATIVE_PATH) <cmake_path-RELATIVE_PATH>` for more
   details.
 
-.. genex:: $<PATH:ABSOLUTE_PATH[,NORMALIZE],path,base_directory>
+.. genex:: $<PATH:ABSOLUTE_PATH[,NORMALIZE],path...,base_directory>
 
   .. versionadded:: 3.24
 
@@ -950,6 +1219,25 @@
   :manual:`cmake-compile-features(7)` manual for information on
   compile features and a list of supported compilers.
 
+Compile Context
+^^^^^^^^^^^^^^^
+
+.. genex:: $<COMPILE_ONLY:...>
+
+  .. versionadded:: 3.27
+
+  Content of ``...``, when collecting :ref:`Target Usage Requirements`,
+  otherwise it is the empty string.  This is intended for use in an
+  :prop_tgt:`INTERFACE_LINK_LIBRARIES` and :prop_tgt:`LINK_LIBRARIES` target
+  properties, typically populated via the :command:`target_link_libraries` command.
+  Provides compilation usage requirements without any linking requirements.
+
+  Use cases include header-only usage where all usages are known to not have
+  linking requirements (e.g., all-``inline`` or C++ template libraries).
+
+  Note that for proper evaluation of this expression requires policy :policy:`CMP0099`
+  to be set to `NEW`.
+
 Linker Language And ID
 ^^^^^^^^^^^^^^^^^^^^^^
 
@@ -1330,7 +1618,8 @@
   in which case it is the empty string.  This is intended for use in an
   :prop_tgt:`INTERFACE_LINK_LIBRARIES` target property, typically populated
   via the :command:`target_link_libraries` command, to specify private link
-  dependencies without other usage requirements.
+  dependencies without other usage requirements such as include directories or
+  compile options.
 
   .. versionadded:: 3.24
     ``LINK_ONLY`` may also be used in a :prop_tgt:`LINK_LIBRARIES` target
@@ -1409,6 +1698,7 @@
     expression is being evaluated.
 
 .. genex:: $<TARGET_PROPERTY:prop>
+  :target: TARGET_PROPERTY:prop
 
   Value of the property ``prop`` on the target for which the expression
   is being evaluated. Note that for generator expressions in
@@ -1494,6 +1784,76 @@
   Note that ``tgt`` is not added as a dependency of the target this
   expression is evaluated on (see policy :policy:`CMP0112`).
 
+.. genex:: $<TARGET_IMPORT_FILE:tgt>
+
+  .. versionadded:: 3.27
+
+  Full path to the linker import file. On DLL platforms, it would be the
+  ``.lib`` file. On AIX, for the executables, and on macOS, for the shared
+  libraries, it could be, respectively, the ``.imp`` or ``.tbd`` import file,
+  depending of the value of :prop_tgt:`ENABLE_EXPORTS` property.
+
+  An empty string is returned when there is no import file associated with the
+  target.
+
+.. genex:: $<TARGET_IMPORT_FILE_BASE_NAME:tgt>
+
+  .. versionadded:: 3.27
+
+  Base name of file linker import file of the target ``tgt`` without prefix and
+  suffix. For example, if target file name is ``libbase.tbd``, the base name is
+  ``base``.
+
+  See also the :prop_tgt:`OUTPUT_NAME` and :prop_tgt:`ARCHIVE_OUTPUT_NAME`
+  target properties and their configuration specific variants
+  :prop_tgt:`OUTPUT_NAME_<CONFIG>` and :prop_tgt:`ARCHIVE_OUTPUT_NAME_<CONFIG>`.
+
+  The :prop_tgt:`<CONFIG>_POSTFIX` and :prop_tgt:`DEBUG_POSTFIX` target
+  properties can also be considered.
+
+  Note that ``tgt`` is not added as a dependency of the target this
+  expression is evaluated on.
+
+.. genex:: $<TARGET_IMPORT_FILE_PREFIX:tgt>
+
+  .. versionadded:: 3.27
+
+  Prefix of the import file of the target ``tgt``.
+
+  See also the :prop_tgt:`IMPORT_PREFIX` target property.
+
+  Note that ``tgt`` is not added as a dependency of the target this
+  expression is evaluated on.
+
+.. genex:: $<TARGET_IMPORT_FILE_SUFFIX:tgt>
+
+  .. versionadded:: 3.27
+
+  Suffix of the import file of the target ``tgt``.
+
+  The suffix corresponds to the file extension (such as ".lib" or ".tbd").
+
+  See also the :prop_tgt:`IMPORT_SUFFIX` target property.
+
+  Note that ``tgt`` is not added as a dependency of the target this
+  expression is evaluated on.
+
+.. genex:: $<TARGET_IMPORT_FILE_NAME:tgt>
+
+  .. versionadded:: 3.27
+
+  Name of the import file of the target target ``tgt``.
+
+  Note that ``tgt`` is not added as a dependency of the target this
+  expression is evaluated on.
+
+.. genex:: $<TARGET_IMPORT_FILE_DIR:tgt>
+
+  Directory of the import file of the target ``tgt``.
+
+  Note that ``tgt`` is not added as a dependency of the target this
+  expression is evaluated on.
+
 .. genex:: $<TARGET_LINKER_FILE:tgt>
 
   File used when linking to the ``tgt`` target.  This will usually
@@ -1501,13 +1861,22 @@
   but for a shared library on DLL platforms, it would be the ``.lib``
   import library associated with the DLL.
 
+  .. versionadded:: 3.27
+    On macOS, it could be the ``.tbd`` import file associated with the shared
+    library, depending of the value of :prop_tgt:`ENABLE_EXPORTS` property.
+
+  This generator expression is equivalent to
+  :genex:`$<TARGET_LINKER_LIBRARY_FILE>` or
+  :genex:`$<TARGET_LINKER_IMPORT_FILE>` generator expressions, depending of the
+  characteristics of the target and the platform.
+
 .. genex:: $<TARGET_LINKER_FILE_BASE_NAME:tgt>
 
   .. versionadded:: 3.15
 
   Base name of file used to link the target ``tgt``, i.e.
-  ``$<TARGET_LINKER_FILE_NAME:tgt>`` without prefix and suffix. For example,
-  if target file name is ``libbase.a``, the base name is ``base``.
+  :genex:`$<TARGET_LINKER_FILE_NAME:tgt>` without prefix and suffix. For
+  example, if target file name is ``libbase.a``, the base name is ``base``.
 
   See also the :prop_tgt:`OUTPUT_NAME`, :prop_tgt:`ARCHIVE_OUTPUT_NAME`,
   and :prop_tgt:`LIBRARY_OUTPUT_NAME` target properties and their configuration
@@ -1561,9 +1930,151 @@
   Note that ``tgt`` is not added as a dependency of the target this
   expression is evaluated on (see policy :policy:`CMP0112`).
 
+.. genex:: $<TARGET_LINKER_LIBRARY_FILE:tgt>
+
+  .. versionadded:: 3.27
+
+  File used when linking o the ``tgt`` target is done using directly the
+  library, and not an import file. This will usually be the library that
+  ``tgt`` represents (``.a``, ``.so``, ``.dylib``). So, on DLL platforms, it
+  will be an empty string.
+
+.. genex:: $<TARGET_LINKER_LIBRARY_FILE_BASE_NAME:tgt>
+
+  .. versionadded:: 3.27
+
+  Base name of library file used to link the target ``tgt``, i.e.
+  :genex:`$<TARGET_LINKER_LIBRARY_FILE_NAME:tgt>` without prefix and suffix.
+  For example, if target file name is ``libbase.a``, the base name is ``base``.
+
+  See also the :prop_tgt:`OUTPUT_NAME`, :prop_tgt:`ARCHIVE_OUTPUT_NAME`,
+  and :prop_tgt:`LIBRARY_OUTPUT_NAME` target properties and their configuration
+  specific variants :prop_tgt:`OUTPUT_NAME_<CONFIG>`,
+  :prop_tgt:`ARCHIVE_OUTPUT_NAME_<CONFIG>` and
+  :prop_tgt:`LIBRARY_OUTPUT_NAME_<CONFIG>`.
+
+  The :prop_tgt:`<CONFIG>_POSTFIX` and :prop_tgt:`DEBUG_POSTFIX` target
+  properties can also be considered.
+
+  Note that ``tgt`` is not added as a dependency of the target this
+  expression is evaluated on.
+
+.. genex:: $<TARGET_LINKER_LIBRARY_FILE_PREFIX:tgt>
+
+  .. versionadded:: 3.27
+
+  Prefix of the library file used to link target ``tgt``.
+
+  See also the :prop_tgt:`PREFIX` target property.
+
+  Note that ``tgt`` is not added as a dependency of the target this
+  expression is evaluated on.
+
+.. genex:: $<TARGET_LINKER_LIBRARY_FILE_SUFFIX:tgt>
+
+  .. versionadded:: 3.27
+
+  Suffix of the library file used to link target ``tgt``.
+
+  The suffix corresponds to the file extension (such as ".a" or ".dylib").
+
+  See also the :prop_tgt:`SUFFIX` target property.
+
+  Note that ``tgt`` is not added as a dependency of the target this
+  expression is evaluated on.
+
+.. genex:: $<TARGET_LINKER_LIBRARY_FILE_NAME:tgt>
+
+  .. versionadded:: 3.27
+
+  Name of the library file used to link target ``tgt``.
+
+  Note that ``tgt`` is not added as a dependency of the target this
+  expression is evaluated on.
+
+.. genex:: $<TARGET_LINKER_LIBRARY_FILE_DIR:tgt>
+
+  .. versionadded:: 3.27
+
+  Directory of the library file used to link target ``tgt``.
+
+  Note that ``tgt`` is not added as a dependency of the target this
+  expression is evaluated on.
+
+.. genex:: $<TARGET_LINKER_IMPORT_FILE:tgt>
+
+  .. versionadded:: 3.27
+
+  File used when linking to the ``tgt`` target is done using an import
+  file.  This will usually be the import file that ``tgt`` represents
+  (``.lib``, ``.tbd``). So, when no import file is involved in the link step,
+  an empty string is returned.
+
+.. genex:: $<TARGET_LINKER_IMPORT_FILE_BASE_NAME:tgt>
+
+  .. versionadded:: 3.27
+
+  Base name of the import file used to link the target ``tgt``, i.e.
+  :genex:`$<TARGET_LINKER_IMPORT_FILE_NAME:tgt>` without prefix and suffix.
+  For example, if target file name is ``libbase.tbd``, the base name is ``base``.
+
+  See also the :prop_tgt:`OUTPUT_NAME` and :prop_tgt:`ARCHIVE_OUTPUT_NAME`,
+  target properties and their configuration
+  specific variants :prop_tgt:`OUTPUT_NAME_<CONFIG>` and
+  :prop_tgt:`ARCHIVE_OUTPUT_NAME_<CONFIG>`.
+
+  The :prop_tgt:`<CONFIG>_POSTFIX` and :prop_tgt:`DEBUG_POSTFIX` target
+  properties can also be considered.
+
+  Note that ``tgt`` is not added as a dependency of the target this
+  expression is evaluated on.
+
+.. genex:: $<TARGET_LINKER_IMPORT_FILE_PREFIX:tgt>
+
+  .. versionadded:: 3.27
+
+  Prefix of the import file used to link target ``tgt``.
+
+  See also the :prop_tgt:`IMPORT_PREFIX` target property.
+
+  Note that ``tgt`` is not added as a dependency of the target this
+  expression is evaluated on.
+
+.. genex:: $<TARGET_LINKER_IMPORT_FILE_SUFFIX:tgt>
+
+  .. versionadded:: 3.27
+
+  Suffix of the import file used to link target ``tgt``.
+
+  The suffix corresponds to the file extension (such as ".lib" or ".tbd").
+
+  See also the :prop_tgt:`IMPORT_SUFFIX` target property.
+
+  Note that ``tgt`` is not added as a dependency of the target this
+  expression is evaluated on.
+
+.. genex:: $<TARGET_LINKER_IMPORT_FILE_NAME:tgt>
+
+  .. versionadded:: 3.27
+
+  Name of the import file used to link target ``tgt``.
+
+  Note that ``tgt`` is not added as a dependency of the target this
+  expression is evaluated on.
+
+.. genex:: $<TARGET_LINKER_IMPORT_FILE_DIR:tgt>
+
+  .. versionadded:: 3.27
+
+  Directory of the import file used to link target ``tgt``.
+
+  Note that ``tgt`` is not added as a dependency of the target this
+  expression is evaluated on.
+
 .. genex:: $<TARGET_SONAME_FILE:tgt>
 
   File with soname (``.so.3``) where ``tgt`` is the name of a target.
+
 .. genex:: $<TARGET_SONAME_FILE_NAME:tgt>
 
   Name of file with soname (``.so.3``).
@@ -1573,11 +2084,35 @@
 
 .. genex:: $<TARGET_SONAME_FILE_DIR:tgt>
 
-  Directory of with soname (``.so.3``).
+  Directory of file with soname (``.so.3``).
 
   Note that ``tgt`` is not added as a dependency of the target this
   expression is evaluated on (see policy :policy:`CMP0112`).
 
+.. genex:: $<TARGET_SONAME_IMPORT_FILE:tgt>
+
+  .. versionadded:: 3.27
+
+  Import file with soname (``.3.tbd``) where ``tgt`` is the name of a target.
+
+.. genex:: $<TARGET_SONAME_IMPORT_FILE_NAME:tgt>
+
+  .. versionadded:: 3.27
+
+  Name of the import file with soname (``.3.tbd``).
+
+  Note that ``tgt`` is not added as a dependency of the target this
+  expression is evaluated on.
+
+.. genex:: $<TARGET_SONAME_IMPORT_FILE_DIR:tgt>
+
+  .. versionadded:: 3.27
+
+  Directory of the import file with soname (``.3.tbd``).
+
+  Note that ``tgt`` is not added as a dependency of the target this
+  expression is evaluated on.
+
 .. genex:: $<TARGET_PDB_FILE:tgt>
 
   .. versionadded:: 3.1
@@ -1668,7 +2203,9 @@
 
   List of DLLs that the target depends on at runtime. This is determined by
   the locations of all the ``SHARED`` targets in the target's transitive
-  dependencies. Using this generator expression on targets other than
+  dependencies. If only the directories of the DLLs are needed, see the
+  :genex:`TARGET_RUNTIME_DLL_DIRS` generator expression.
+  Using this generator expression on targets other than
   executables, ``SHARED`` libraries, and ``MODULE`` libraries is an error.
   **On non-DLL platforms, this expression always evaluates to an empty string**.
 
@@ -1700,6 +2237,20 @@
 :prop_tgt:`INSTALL_RPATH` target property.
 On Apple platforms, refer to the :prop_tgt:`INSTALL_NAME_DIR` target property.
 
+.. genex:: $<TARGET_RUNTIME_DLL_DIRS:tgt>
+
+  .. versionadded:: 3.27
+
+  List of the directories which contain the DLLs that the target depends on at
+  runtime (see :genex:`TARGET_RUNTIME_DLLS`). This is determined by
+  the locations of all the ``SHARED`` targets in the target's transitive
+  dependencies. Using this generator expression on targets other than
+  executables, ``SHARED`` libraries, and ``MODULE`` libraries is an error.
+  **On non-DLL platforms, this expression always evaluates to an empty string**.
+
+  This generator expression can e.g. be used to create a batch file using
+  :command:`file(GENERATE)` which sets the PATH environment variable accordingly.
+
 Export And Install Expressions
 ------------------------------
 
@@ -1725,8 +2276,10 @@
 
   Content of the install prefix when the target is exported via
   :command:`install(EXPORT)`, or when evaluated in the
-  :prop_tgt:`INSTALL_NAME_DIR` property or the ``INSTALL_NAME_DIR`` argument of
-  :command:`install(RUNTIME_DEPENDENCY_SET)`, and empty otherwise.
+  :prop_tgt:`INSTALL_NAME_DIR` property, the ``INSTALL_NAME_DIR`` argument of
+  :command:`install(RUNTIME_DEPENDENCY_SET)`, the code argument of
+  :command:`install(CODE)`, or the file argument of :command:`install(SCRIPT)`,
+  and empty otherwise.
 
 Multi-level Expression Evaluation
 ---------------------------------
diff --git a/Help/manual/cmake-generators.7.rst b/Help/manual/cmake-generators.7.rst
index ed5bbbf..9647f0d 100644
--- a/Help/manual/cmake-generators.7.rst
+++ b/Help/manual/cmake-generators.7.rst
@@ -107,6 +107,12 @@
 Extra Generators
 ================
 
+.. deprecated:: 3.27
+
+  Support for "Extra Generators" is deprecated and will be removed from
+  a future version of CMake.  IDEs may use the :manual:`cmake-file-api(7)`
+  to view CMake-generated project build trees.
+
 Some of the `CMake Generators`_ listed in the :manual:`cmake(1)`
 command-line tool :option:`--help <cmake --help>` output may have
 variants that specify an extra generator for an auxiliary IDE tool.
diff --git a/Help/manual/cmake-gui.1.rst b/Help/manual/cmake-gui.1.rst
index cdb860f..26083ca 100644
--- a/Help/manual/cmake-gui.1.rst
+++ b/Help/manual/cmake-gui.1.rst
@@ -9,9 +9,9 @@
 .. parsed-literal::
 
  cmake-gui [<options>]
+ cmake-gui [<options>] -B <path-to-build> [-S <path-to-source>]
  cmake-gui [<options>] <path-to-source | path-to-existing-build>
- cmake-gui [<options>] -S <path-to-source> -B <path-to-build>
- cmake-gui [<options>] --browse-manual
+ cmake-gui [<options>] --browse-manual [<filename>]
 
 Description
 ===========
@@ -46,9 +46,11 @@
  Name of the preset to use from the project's
  :manual:`presets <cmake-presets(7)>` files, if it has them.
 
-.. option:: --browse-manual
+.. option:: --browse-manual [<filename>]
 
- Open the CMake reference manual in a browser and immediately exit.
+ Open the CMake reference manual in a browser and immediately exit. If
+ ``<filename>`` is specified, open that file within the reference manual
+ instead of ``index.html``.
 
 .. include:: OPTIONS_HELP.txt
 
diff --git a/Help/manual/cmake-policies.7.rst b/Help/manual/cmake-policies.7.rst
index 22e9eb2..7c48806 100644
--- a/Help/manual/cmake-policies.7.rst
+++ b/Help/manual/cmake-policies.7.rst
@@ -51,6 +51,21 @@
 to determine whether to report an error on use of deprecated macros or
 functions.
 
+Policies Introduced by CMake 3.27
+=================================
+
+.. toctree::
+   :maxdepth: 1
+
+   CMP0151: AUTOMOC include directory is a system include directory by default. </policy/CMP0151>
+   CMP0150: ExternalProject_Add and FetchContent_Declare treat relative git repository paths as being relative to parent project's remote. </policy/CMP0150>
+   CMP0149: Visual Studio generators select latest Windows SDK by default. </policy/CMP0149>
+   CMP0148: The FindPythonInterp and FindPythonLibs modules are removed. </policy/CMP0148>
+   CMP0147: Visual Studio generators build custom commands in parallel. </policy/CMP0147>
+   CMP0146: The FindCUDA module is removed. </policy/CMP0146>
+   CMP0145: The Dart and FindDart modules are removed. </policy/CMP0145>
+   CMP0144: find_package uses upper-case PACKAGENAME_ROOT variables. </policy/CMP0144>
+
 Policies Introduced by CMake 3.26
 =================================
 
diff --git a/Help/manual/cmake-presets.7.rst b/Help/manual/cmake-presets.7.rst
index da699d8..e2366da 100644
--- a/Help/manual/cmake-presets.7.rst
+++ b/Help/manual/cmake-presets.7.rst
@@ -63,6 +63,9 @@
   ``6``
     .. versionadded:: 3.25
 
+  ``7``
+    .. versionadded:: 3.27
+
 ``cmakeMinimumRequired``
   An optional object representing the minimum version of CMake needed to
   build this project. This object consists of the following fields:
@@ -130,6 +133,9 @@
 guaranteed to be provided by the project. ``CMakeUserPresets.json`` may
 include files from anywhere.
 
+Starting from version ``7``, the ``include`` field supports
+`macro expansion`_, but only ``$penv{}`` macro expansion.
+
 Configure Preset
 ^^^^^^^^^^^^^^^^
 
@@ -359,6 +365,52 @@
     An optional boolean. Setting this to ``true`` is equivalent to passing
     :option:`--debug-find <cmake --debug-find>` on the command line.
 
+``trace``
+  An optional object specifying trace options. This is allowed in preset
+  files specifying version ``7``. The object may contain the following fields:
+
+  ``mode``
+    An optional string that specifies the trace mode. Valid values are:
+
+    ``on``
+      Causes a trace of all calls made and from where to be printed.
+      Equivalent to passing :option:`--trace <cmake --trace>` on the command
+      line.
+
+    ``off``
+      A trace of all calls will not be printed.
+
+    ``expand``
+      Causes a trace with variables expanded of all calls made and from where
+      to be printed. Equivalent to passing :option:`--trace-expand <cmake --trace-expand>`
+      on the command line.
+
+  ``format``
+    An optional string that specifies the format output of the trace.
+    Valid values are:
+
+    ``human``
+      Prints each trace line in a human-readable format.
+      This is the default format.  Equivalent to passing
+      :option:`--trace-format=human <cmake --trace-format>`
+      on the command line.
+
+    ``json-v1``
+      Prints each line as a separate JSON document.  Equivalent to passing
+      :option:`--trace-format=json-v1 <cmake --trace-format>`
+      on the command line.
+
+  ``source``
+    An optional array of strings representing the paths of source files to
+    be traced.  This field can also be a string, which is equivalent to an
+    array containing one string.  Equivalent to passing
+    :option:`--trace-source <cmake --trace-source>` on the command line.
+
+  ``redirect``
+    An optional string specifying a path to a trace output file.  Equivalent
+    to passing :option:`--trace-redirect <cmake --trace-redirect>`
+    on the command line.
+
 Build Preset
 ^^^^^^^^^^^^
 
@@ -1008,6 +1060,12 @@
   a workflow preset may have the same name as a configure, build, test, or
   package preset.
 
+``vendor``
+  An optional map containing vendor-specific information. CMake does not
+  interpret the contents of this field except to verify that it is a map
+  if it does exist. However, it should follow the same conventions as the
+  root-level ``vendor`` field.
+
 ``displayName``
   An optional string with a human-friendly name of the preset.
 
diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst
index fb84f5a..b2a27a0 100644
--- a/Help/manual/cmake-properties.7.rst
+++ b/Help/manual/cmake-properties.7.rst
@@ -133,6 +133,7 @@
    /prop_tgt/AUTOGEN_ORIGIN_DEPENDS
    /prop_tgt/AUTOGEN_PARALLEL
    /prop_tgt/AUTOGEN_TARGET_DEPENDS
+   /prop_tgt/AUTOGEN_USE_SYSTEM_INCLUDE
    /prop_tgt/AUTOMOC
    /prop_tgt/AUTOMOC_COMPILER_PREDEFINES
    /prop_tgt/AUTOMOC_DEPEND_FILTERS
@@ -175,7 +176,10 @@
    /prop_tgt/CONFIG_POSTFIX
    /prop_tgt/CROSSCOMPILING_EMULATOR
    /prop_tgt/CUDA_ARCHITECTURES
+   /prop_tgt/CUDA_CUBIN_COMPILATION
    /prop_tgt/CUDA_EXTENSIONS
+   /prop_tgt/CUDA_FATBIN_COMPILATION
+   /prop_tgt/CUDA_OPTIX_COMPILATION
    /prop_tgt/CUDA_PTX_COMPILATION
    /prop_tgt/CUDA_RESOLVE_DEVICE_SYMBOLS
    /prop_tgt/CUDA_RUNTIME_LIBRARY
@@ -202,6 +206,7 @@
    /prop_tgt/DEPLOYMENT_REMOTE_DIRECTORY
    /prop_tgt/DEPRECATION
    /prop_tgt/DISABLE_PRECOMPILE_HEADERS
+   /prop_tgt/DLL_NAME_WITH_SOVERSION
    /prop_tgt/DOTNET_SDK
    /prop_tgt/DOTNET_TARGET_FRAMEWORK
    /prop_tgt/DOTNET_TARGET_FRAMEWORK_VERSION
@@ -269,6 +274,7 @@
    /prop_tgt/INSTALL_REMOVE_ENVIRONMENT_RPATH
    /prop_tgt/INSTALL_RPATH
    /prop_tgt/INSTALL_RPATH_USE_LINK_PATH
+   /prop_tgt/INTERFACE_AUTOMOC_MACRO_NAMES
    /prop_tgt/INTERFACE_AUTOUIC_OPTIONS
    /prop_tgt/INTERFACE_COMPILE_DEFINITIONS
    /prop_tgt/INTERFACE_COMPILE_FEATURES
@@ -551,6 +557,7 @@
    /prop_sf/SKIP_AUTOMOC
    /prop_sf/SKIP_AUTORCC
    /prop_sf/SKIP_AUTOUIC
+   /prop_sf/SKIP_LINTING
    /prop_sf/SKIP_PRECOMPILE_HEADERS
    /prop_sf/SKIP_UNITY_BUILD_INCLUSION
    /prop_sf/Swift_DEPENDENCIES_FILE
diff --git a/Help/manual/cmake-toolchains.7.rst b/Help/manual/cmake-toolchains.7.rst
index 8a83807..a831fa6 100644
--- a/Help/manual/cmake-toolchains.7.rst
+++ b/Help/manual/cmake-toolchains.7.rst
@@ -84,7 +84,7 @@
 ==================
 
 CMake provides the :command:`try_compile` command and wrapper macros such as
-:module:`CheckCXXSourceCompiles`, :module:`CheckCXXSymbolExists` and
+:module:`CheckSourceCompiles`, :module:`CheckCXXSymbolExists` and
 :module:`CheckIncludeFile` to test capability and availability of various
 toolchain features. These APIs test the toolchain in some way and cache the
 result so that the test does not have to be performed again the next time
@@ -273,7 +273,7 @@
 Cross Compiling for Windows 10 Universal Applications
 -----------------------------------------------------
 
-A toolchain file to configure a Visual Studio generator for a
+A toolchain file to configure :ref:`Visual Studio Generators` for a
 Windows 10 Universal Application may look like this:
 
 .. code-block:: cmake
@@ -283,9 +283,10 @@
 
 A Windows 10 Universal Application targets both Windows Store and
 Windows Phone.  Specify the :variable:`CMAKE_SYSTEM_VERSION` variable
-to be ``10.0`` to build with the latest available Windows 10 SDK.
-Specify a more specific version (e.g. ``10.0.10240.0`` for RTM)
-to build with the corresponding SDK.
+to be ``10.0`` or higher.
+
+CMake selects a Windows SDK as described by documentation of the
+:variable:`CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION` variable.
 
 Cross Compiling for Windows Phone
 ---------------------------------
diff --git a/Help/manual/cmake-variables.7.rst b/Help/manual/cmake-variables.7.rst
index 23d8256..fa7a90f 100644
--- a/Help/manual/cmake-variables.7.rst
+++ b/Help/manual/cmake-variables.7.rst
@@ -132,6 +132,7 @@
    /variable/CMAKE_VS_TARGET_FRAMEWORK_TARGETS_VERSION
    /variable/CMAKE_VS_TARGET_FRAMEWORK_VERSION
    /variable/CMAKE_VS_VERSION_BUILD_NUMBER
+   /variable/CMAKE_VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION
    /variable/CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION
    /variable/CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION_MAXIMUM
    /variable/CMAKE_XCODE_BUILD_SYSTEM
@@ -166,6 +167,7 @@
 
    /variable/BUILD_SHARED_LIBS
    /variable/CMAKE_ABSOLUTE_DESTINATION_FILES
+   /variable/CMAKE_ADD_CUSTOM_COMMAND_DEPENDS_EXPLICIT_ONLY
    /variable/CMAKE_APPBUNDLE_PATH
    /variable/CMAKE_AUTOMOC_RELAXED_MODE
    /variable/CMAKE_BACKWARDS_COMPATIBILITY
@@ -226,6 +228,8 @@
    /variable/CMAKE_INSTALL_MESSAGE
    /variable/CMAKE_INSTALL_PREFIX
    /variable/CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT
+   /variable/CMAKE_KATE_FILES_MODE
+   /variable/CMAKE_KATE_MAKE_ARGUMENTS
    /variable/CMAKE_LIBRARY_PATH
    /variable/CMAKE_LINK_DIRECTORIES_BEFORE
    /variable/CMAKE_LINK_LIBRARIES_ONLY_TARGETS
@@ -393,6 +397,7 @@
    /variable/CMAKE_ARCHIVE_OUTPUT_DIRECTORY_CONFIG
    /variable/CMAKE_AUTOGEN_ORIGIN_DEPENDS
    /variable/CMAKE_AUTOGEN_PARALLEL
+   /variable/CMAKE_AUTOGEN_USE_SYSTEM_INCLUDE
    /variable/CMAKE_AUTOGEN_VERBOSE
    /variable/CMAKE_AUTOMOC
    /variable/CMAKE_AUTOMOC_COMPILER_PREDEFINES
@@ -400,11 +405,14 @@
    /variable/CMAKE_AUTOMOC_MACRO_NAMES
    /variable/CMAKE_AUTOMOC_MOC_OPTIONS
    /variable/CMAKE_AUTOMOC_PATH_PREFIX
+   /variable/CMAKE_AUTOMOC_EXECUTABLE
    /variable/CMAKE_AUTORCC
    /variable/CMAKE_AUTORCC_OPTIONS
+   /variable/CMAKE_AUTORCC_EXECUTABLE
    /variable/CMAKE_AUTOUIC
    /variable/CMAKE_AUTOUIC_OPTIONS
    /variable/CMAKE_AUTOUIC_SEARCH_PATHS
+   /variable/CMAKE_AUTOUIC_EXECUTABLE
    /variable/CMAKE_BUILD_RPATH
    /variable/CMAKE_BUILD_RPATH_USE_ORIGIN
    /variable/CMAKE_BUILD_WITH_INSTALL_NAME_DIR
@@ -424,7 +432,9 @@
    /variable/CMAKE_DEFAULT_CONFIGS
    /variable/CMAKE_DEPENDS_USE_COMPILER
    /variable/CMAKE_DISABLE_PRECOMPILE_HEADERS
+   /variable/CMAKE_DLL_NAME_WITH_SOVERSION
    /variable/CMAKE_ENABLE_EXPORTS
+   /variable/CMAKE_EXECUTABLE_ENABLE_EXPORTS
    /variable/CMAKE_EXE_LINKER_FLAGS
    /variable/CMAKE_EXE_LINKER_FLAGS_CONFIG
    /variable/CMAKE_EXE_LINKER_FLAGS_CONFIG_INIT
@@ -470,6 +480,7 @@
    /variable/CMAKE_LIBRARY_PATH_FLAG
    /variable/CMAKE_LINK_DEF_FILE_FLAG
    /variable/CMAKE_LINK_DEPENDS_NO_SHARED
+   /variable/CMAKE_LINK_DEPENDS_USE_LINKER
    /variable/CMAKE_LINK_GROUP_USING_FEATURE
    /variable/CMAKE_LINK_GROUP_USING_FEATURE_SUPPORTED
    /variable/CMAKE_LINK_INTERFACE_LIBRARIES
@@ -504,6 +515,7 @@
    /variable/CMAKE_POSITION_INDEPENDENT_CODE
    /variable/CMAKE_RUNTIME_OUTPUT_DIRECTORY
    /variable/CMAKE_RUNTIME_OUTPUT_DIRECTORY_CONFIG
+   /variable/CMAKE_SHARED_LIBRARY_ENABLE_EXPORTS
    /variable/CMAKE_SHARED_LINKER_FLAGS
    /variable/CMAKE_SHARED_LINKER_FLAGS_CONFIG
    /variable/CMAKE_SHARED_LINKER_FLAGS_CONFIG_INIT
@@ -525,6 +537,10 @@
    /variable/CMAKE_USE_RELATIVE_PATHS
    /variable/CMAKE_VERIFY_INTERFACE_HEADER_SETS
    /variable/CMAKE_VISIBILITY_INLINES_HIDDEN
+   /variable/CMAKE_VS_DEBUGGER_COMMAND
+   /variable/CMAKE_VS_DEBUGGER_COMMAND_ARGUMENTS
+   /variable/CMAKE_VS_DEBUGGER_ENVIRONMENT
+   /variable/CMAKE_VS_DEBUGGER_WORKING_DIRECTORY
    /variable/CMAKE_VS_GLOBALS
    /variable/CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD
    /variable/CMAKE_VS_INCLUDE_PACKAGE_TO_DEFAULT_BUILD
diff --git a/Help/manual/cmake.1.rst b/Help/manual/cmake.1.rst
index e48ecd9..b5848f7 100644
--- a/Help/manual/cmake.1.rst
+++ b/Help/manual/cmake.1.rst
@@ -9,8 +9,8 @@
 .. parsed-literal::
 
  `Generate a Project Buildsystem`_
+  cmake [<options>] -B <path-to-build> [-S <path-to-source>]
   cmake [<options>] <path-to-source | path-to-existing-build>
-  cmake [<options>] -S <path-to-source> -B <path-to-build>
 
  `Build a Project`_
   cmake --build <dir> [<options>] [-- <build-tool-options>]
@@ -116,6 +116,20 @@
 Run CMake with one of the following command signatures to specify the
 source and build trees and generate a buildsystem:
 
+``cmake [<options>] -B <path-to-build> [-S <path-to-source>]``
+
+  .. versionadded:: 3.13
+
+  Uses ``<path-to-build>`` as the build tree and ``<path-to-source>``
+  as the source tree.  The specified paths may be absolute or relative
+  to the current working directory.  The source tree must contain a
+  ``CMakeLists.txt`` file.  The build tree will be created automatically
+  if it does not already exist.  For example:
+
+  .. code-block:: console
+
+    $ cmake -S src -B build
+
 ``cmake [<options>] <path-to-source>``
   Uses the current working directory as the build tree, and
   ``<path-to-source>`` as the source tree.  The specified path may
@@ -141,20 +155,6 @@
     $ cd build
     $ cmake .
 
-``cmake [<options>] -S <path-to-source> -B <path-to-build>``
-
-  .. versionadded:: 3.13
-
-  Uses ``<path-to-build>`` as the build tree and ``<path-to-source>``
-  as the source tree.  The specified paths may be absolute or relative
-  to the current working directory.  The source tree must contain a
-  ``CMakeLists.txt`` file.  The build tree will be created automatically
-  if it does not already exist.  For example:
-
-  .. code-block:: console
-
-    $ cmake -S src -B build
-
 In all cases the ``<options>`` may be zero or more of the `Options`_ below.
 
 The above styles for specifying the source and build trees may be mixed.
@@ -167,14 +167,14 @@
 ============================== ============ ===========
  Command Line                   Source Dir   Build Dir
 ============================== ============ ===========
+ ``cmake -B build``             `cwd`        ``build``
+ ``cmake -B build src``         ``src``      ``build``
+ ``cmake -B build -S src``      ``src``      ``build``
  ``cmake src``                  ``src``      `cwd`
  ``cmake build`` (existing)     `loaded`     ``build``
  ``cmake -S src``               ``src``      `cwd`
  ``cmake -S src build``         ``src``      ``build``
  ``cmake -S src -B build``      ``src``      ``build``
- ``cmake -B build``             `cwd`        ``build``
- ``cmake -B build src``         ``src``      ``build``
- ``cmake -B build -S src``      ``src``      ``build``
 ============================== ============ ===========
 
 .. versionchanged:: 3.23
@@ -517,6 +517,53 @@
  If ``<type>`` is omitted, ``configure`` is assumed.  The current working
  directory must contain CMake preset files.
 
+.. option:: --debugger
+
+  Enables interactive debugging of the CMake language. CMake exposes a debugging
+  interface on the pipe named by :option:`--debugger-pipe <cmake --debugger-pipe>`
+  that conforms to the `Debug Adapter Protocol`_ specification with the following
+  modifications.
+
+  The ``initialize`` response includes an additional field named ``cmakeVersion``
+  which specifies the version of CMake being debugged.
+
+  .. code-block:: json
+    :caption: Debugger initialize response
+
+    {
+      "cmakeVersion": {
+        "major": 3,
+        "minor": 27,
+        "patch": 0,
+        "full": "3.27.0"
+      }
+    }
+
+  The members are:
+
+  ``major``
+    An integer specifying the major version number.
+
+  ``minor``
+    An integer specifying the minor version number.
+
+  ``patch``
+    An integer specifying the patch version number.
+
+  ``full``
+    A string specifying the full CMake version.
+
+.. _`Debug Adapter Protocol`: https://microsoft.github.io/debug-adapter-protocol/
+
+.. option:: --debugger-pipe <pipe name>, --debugger-pipe=<pipe name>
+
+  Name of the pipe (on Windows) or domain socket (on Unix) to use for
+  debugger communication.
+
+.. option:: --debugger-dap-log <log path>, --debugger-dap-log=<log path>
+
+  Logs all debugger communication to the specified file.
+
 .. _`Build Tool Mode`:
 
 Build a Project
@@ -781,7 +828,7 @@
       (:option:`-A ... <cmake -A>`).  The value is a list of platforms known to
       be supported.
     ``extraGenerators``
-      A list of strings with all the extra generators compatible with
+      A list of strings with all the :ref:`Extra Generators` compatible with
       the generator.
 
   ``fileApi``
@@ -809,6 +856,12 @@
 
     ``true`` if TLS support is enabled and ``false`` otherwise.
 
+  ``debugger``
+    .. versionadded:: 3.27
+
+    ``true`` if the :option:`--debugger <cmake --debugger>` mode
+    is supported and ``false`` otherwise.
+
 .. option:: cat [--] <files>...
 
   .. versionadded:: 3.18
@@ -1080,11 +1133,18 @@
   situations instead. Use ``--`` to stop interpreting options and treat all
   remaining arguments as paths, even if they start with ``-``.
 
-.. option:: sleep <number>...
+.. option:: sleep <number>
 
   .. versionadded:: 3.0
 
-  Sleep for given number of seconds.
+  Sleep for ``<number>`` seconds. ``<number>`` may be a floating point number.
+  A practical minimum is about 0.1 seconds due to overhead in starting/stopping
+  CMake executable. This can be useful in a CMake script to insert a delay:
+
+  .. code-block:: cmake
+
+    # Sleep for about 0.5 seconds
+    execute_process(COMMAND ${CMAKE_COMMAND} -E sleep 0.5)
 
 .. option:: tar [cxt][vf][zjJ] file.tar [<options>] [--] [<pathname>...]
 
@@ -1189,7 +1249,7 @@
 
 .. option:: time <command> [<args>...]
 
-  Run command and display elapsed time.
+  Run ``<command>`` and display elapsed time (including overhead of CMake frontend).
 
   .. versionadded:: 3.5
     The command now properly passes arguments with spaces or special characters
diff --git a/Help/manual/cpack-generators.7.rst b/Help/manual/cpack-generators.7.rst
index ade9149..abb291b 100644
--- a/Help/manual/cpack-generators.7.rst
+++ b/Help/manual/cpack-generators.7.rst
@@ -20,6 +20,7 @@
    /cpack_gen/dmg
    /cpack_gen/external
    /cpack_gen/freebsd
+   /cpack_gen/innosetup
    /cpack_gen/ifw
    /cpack_gen/nsis
    /cpack_gen/nuget
diff --git a/Help/manual/ctest.1.rst b/Help/manual/ctest.1.rst
index 5e82faa..994ae47 100644
--- a/Help/manual/ctest.1.rst
+++ b/Help/manual/ctest.1.rst
@@ -11,7 +11,7 @@
 .. parsed-literal::
 
  `Run Tests`_
-  ctest [<options>]
+  ctest [<options>] [--test-dir <path-to-build>]
 
  `Build and Test Mode`_
   ctest --build-and-test <path-to-source> <path-to-build>
@@ -354,7 +354,8 @@
 
 .. option:: --test-dir <dir>
 
- Specify the directory in which to look for tests.
+ Specify the directory in which to look for tests, typically a CMake project
+ build directory. If not specified, the current directory is used.
 
 .. option:: --test-output-size-passed <size>
 
diff --git a/Help/manual/presets/schema.json b/Help/manual/presets/schema.json
index 348116b..adfb1cb 100644
--- a/Help/manual/presets/schema.json
+++ b/Help/manual/presets/schema.json
@@ -89,6 +89,23 @@
         "include": { "$ref": "#/definitions/include"}
       },
       "additionalProperties": false
+    },
+    {
+      "properties": {
+        "version": {
+          "const": 7,
+          "description": "A required integer representing the version of the JSON schema."
+        },
+        "cmakeMinimumRequired": { "$ref": "#/definitions/cmakeMinimumRequired"},
+        "vendor": { "$ref": "#/definitions/vendor" },
+        "configurePresets": { "$ref": "#/definitions/configurePresetsV7"},
+        "buildPresets": { "$ref": "#/definitions/buildPresetsV4"},
+        "testPresets": { "$ref": "#/definitions/testPresetsV6"},
+        "packagePresets": { "$ref": "#/definitions/packagePresetsV6"},
+        "workflowPresets": { "$ref": "#/definitions/workflowPresetsV6" },
+        "include": { "$ref": "#/definitions/include"}
+      },
+      "additionalProperties": false
     }
   ],
   "required": [
@@ -119,6 +136,59 @@
       "description": "An optional map containing vendor-specific information. CMake does not interpret the contents of this field except to verify that it is a map if it does exist. However, the keys should be a vendor-specific domain name followed by a /-separated path. For example, the Example IDE 1.0 could use example.com/ExampleIDE/1.0. The value of each field can be anything desired by the vendor, though will typically be a map.",
       "properties": {}
     },
+    "configurePresetsItemsV7": {
+      "type": "array",
+      "description": "A configure preset object.",
+      "items": {
+        "type": "object",
+        "description": "A configure preset object.",
+        "properties": {
+          "trace": {
+            "type": "object",
+            "description": "An optional object specifying trace options.",
+            "properties": {
+              "mode": {
+                "type": "string",
+                "description": "An optional string that specifies the trace mode.",
+                "enum": [
+                  "on", "off", "expand"
+                ]
+              },
+              "format": {
+                "type": "string",
+                "description": "An optional string that specifies the trace output format.",
+                "enum": [
+                  "human", "json-v1"
+                ]
+              },
+              "source": {
+                "anyOf": [
+                  {
+                    "type": "string",
+                    "description": "An optional string representing the path to one source file to be traced.",
+                    "minLength": 1
+                  },
+                  {
+                    "type": "array",
+                    "description": "An optional array of strings representing the paths to source files to be traced.",
+                    "items": {
+                      "type": "string",
+                      "description": "A string representing the path to one source file to be traced.",
+                      "minLength": 1
+                    }
+                  }
+                ]
+              },
+              "redirect": {
+                "type": "string",
+                "description": "An optional string specifying a path to a trace output file."
+              }
+            },
+            "additionalProperties": false
+          }
+        }
+      }
+    },
     "configurePresetsItemsV3": {
       "type": "array",
       "description": "A configure preset object.",
@@ -393,6 +463,43 @@
         }
       }
     },
+    "configurePresetsV7": {
+      "type": "array",
+      "description": "An optional array of configure preset objects.",
+      "allOf": [
+        { "$ref": "#/definitions/configurePresetsItemsV1" },
+        { "$ref": "#/definitions/configurePresetsItemsV3" },
+        { "$ref": "#/definitions/configurePresetsItemsV7" }
+      ],
+      "items": {
+        "properties": {
+          "name": {},
+          "hidden": {},
+          "inherits": {},
+          "vendor": {},
+          "displayName": {},
+          "description": {},
+          "generator": {},
+          "architecture": {},
+          "toolset": {},
+          "toolchainFile": {},
+          "binaryDir": {},
+          "installDir": {},
+          "cmakeExecutable": {},
+          "cacheVariables": {},
+          "environment": {},
+          "warnings": {},
+          "errors": {},
+          "debug": {},
+          "condition": {},
+          "trace": {}
+        },
+        "required": [
+          "name"
+        ],
+        "additionalProperties": false
+      }
+    },
     "configurePresetsV3": {
       "type": "array",
       "description": "An optional array of configure preset objects.",
diff --git a/Help/module/CMAKE_REQUIRED_DEFINITIONS.txt b/Help/module/CMAKE_REQUIRED_DEFINITIONS.txt
new file mode 100644
index 0000000..17289c3
--- /dev/null
+++ b/Help/module/CMAKE_REQUIRED_DEFINITIONS.txt
@@ -0,0 +1,4 @@
+  ``CMAKE_REQUIRED_DEFINITIONS``
+    A :ref:`;-list <CMake Language Lists>` of compiler definitions of the form
+    ``-DFOO`` or ``-DFOO=bar``. A definition for the name specified by
+    ``<resultVar>`` will also be added automatically.
diff --git a/Help/module/CMAKE_REQUIRED_FLAGS.txt b/Help/module/CMAKE_REQUIRED_FLAGS.txt
new file mode 100644
index 0000000..80ae239
--- /dev/null
+++ b/Help/module/CMAKE_REQUIRED_FLAGS.txt
@@ -0,0 +1,6 @@
+  ``CMAKE_REQUIRED_FLAGS``
+    String of additional flags to pass to the compiler. The string must be
+    space-delimited--a :ref:`;-list <CMake Language Lists>` will not work.
+    The contents of :variable:`CMAKE_<LANG>_FLAGS <CMAKE_<LANG>_FLAGS>` and
+    its associated configuration-specific variable are automatically added
+    to the compiler command before the contents of ``CMAKE_REQUIRED_FLAGS``.
diff --git a/Help/module/CMAKE_REQUIRED_INCLUDES.txt b/Help/module/CMAKE_REQUIRED_INCLUDES.txt
new file mode 100644
index 0000000..c8993bb
--- /dev/null
+++ b/Help/module/CMAKE_REQUIRED_INCLUDES.txt
@@ -0,0 +1,4 @@
+  ``CMAKE_REQUIRED_INCLUDES``
+    A :ref:`;-list <CMake Language Lists>` of header search paths to pass to
+    the compiler. These will be the only header search paths used--the contents
+    of the :prop_dir:`INCLUDE_DIRECTORIES` directory property will be ignored.
diff --git a/Help/module/CMAKE_REQUIRED_LIBRARIES.txt b/Help/module/CMAKE_REQUIRED_LIBRARIES.txt
new file mode 100644
index 0000000..8611b9e
--- /dev/null
+++ b/Help/module/CMAKE_REQUIRED_LIBRARIES.txt
@@ -0,0 +1,5 @@
+  ``CMAKE_REQUIRED_LIBRARIES``
+    A :ref:`;-list <CMake Language Lists>` of libraries to add to the link
+    command. These can be the name of system libraries or they can be
+    :ref:`Imported Targets <Imported Targets>` (see :command:`try_compile` for
+    further details).
diff --git a/Help/module/CMAKE_REQUIRED_LINK_OPTIONS.txt b/Help/module/CMAKE_REQUIRED_LINK_OPTIONS.txt
new file mode 100644
index 0000000..f2a2474
--- /dev/null
+++ b/Help/module/CMAKE_REQUIRED_LINK_OPTIONS.txt
@@ -0,0 +1,5 @@
+  ``CMAKE_REQUIRED_LINK_OPTIONS``
+    .. versionadded:: 3.14
+
+    A :ref:`;-list <CMake Language Lists>` of options to add to the link
+    command (see :command:`try_compile` for further details).
diff --git a/Help/module/CMAKE_REQUIRED_QUIET.txt b/Help/module/CMAKE_REQUIRED_QUIET.txt
new file mode 100644
index 0000000..aae8059
--- /dev/null
+++ b/Help/module/CMAKE_REQUIRED_QUIET.txt
@@ -0,0 +1,5 @@
+  ``CMAKE_REQUIRED_QUIET``
+    .. versionadded:: 3.1
+
+    If this variable evaluates to a boolean true value, all status messages
+    associated with the check will be suppressed.
diff --git a/Help/policy/CMP0144.rst b/Help/policy/CMP0144.rst
new file mode 100644
index 0000000..3959d96
--- /dev/null
+++ b/Help/policy/CMP0144.rst
@@ -0,0 +1,26 @@
+CMP0144
+-------
+
+.. versionadded:: 3.27
+
+:command:`find_package` uses upper-case ``<PACKAGENAME>_ROOT`` variables.
+
+In CMake 3.27 and above the :command:`find_package(<PackageName>)` command now
+searches prefixes specified by the upper-case :variable:`<PACKAGENAME>_ROOT`
+CMake variable and the :envvar:`<PACKAGENAME>_ROOT` environment variable
+in addition to the case-preserved :variable:`<PackageName>_ROOT` and
+:envvar:`<PackageName>_ROOT` variables used since policy :policy:`CMP0074`.
+This policy provides compatibility with projects that have not been
+updated to avoid using ``<PACKAGENAME>_ROOT`` variables for other purposes.
+
+The ``OLD`` behavior for this policy is to ignore ``<PACKAGENAME>_ROOT``
+variables if the original ``<PackageName>`` has lower-case characters.
+The ``NEW`` behavior for this policy is to use ``<PACKAGENAME>_ROOT``
+variables.
+
+This policy was introduced in CMake version 3.27.  CMake version
+|release| warns when the policy is not set and uses ``OLD`` behavior.
+Use the :command:`cmake_policy` command to set it to ``OLD`` or ``NEW``
+explicitly.
+
+.. include:: DEPRECATED.txt
diff --git a/Help/policy/CMP0145.rst b/Help/policy/CMP0145.rst
new file mode 100644
index 0000000..bb1c02e
--- /dev/null
+++ b/Help/policy/CMP0145.rst
@@ -0,0 +1,30 @@
+CMP0145
+-------
+
+.. versionadded:: 3.27
+
+The :module:`Dart` and :module:`FindDart` modules are removed.
+
+These modules were added very early in CMake's development to support
+driving tests with a "DART" tool, but DART has not been distributed or
+maintained for many years.  Projects would ``include(Dart)`` to use it,
+and the ``Dart`` module would run ``find_package(Dart)`` internally.
+Since :manual:`ctest(1)` was created, the ``Dart`` module has just been
+a compatibility shim that finds ``Dart`` to support some legacy
+functionality and then forwards to the :module:`CTest` module.
+
+CMake 3.27 and above prefer to not provide the :module:`Dart` or
+:module:`FindDart` modules.  This policy provides compatibility for
+projects that have not been ported away from them.  Projects using the
+``Dart`` module should be updated to use the :module:`CTest` module directly.
+
+The ``OLD`` behavior of this policy is for ``include(Dart)`` and
+``find_package(Dart)`` to load the deprecated modules.  The ``NEW``
+behavior is for uses of the modules to fail as if they do not exist.
+
+This policy was introduced in CMake version 3.27.  CMake version
+|release| warns when the policy is not set and uses ``OLD`` behavior.
+Use the :command:`cmake_policy` command to set it to ``OLD`` or ``NEW``
+explicitly.
+
+.. include:: DEPRECATED.txt
diff --git a/Help/policy/CMP0146.rst b/Help/policy/CMP0146.rst
new file mode 100644
index 0000000..c7cac22
--- /dev/null
+++ b/Help/policy/CMP0146.rst
@@ -0,0 +1,29 @@
+CMP0146
+-------
+
+.. versionadded:: 3.27
+
+The :module:`FindCUDA` module is removed.
+
+The :module:`FindCUDA` module has been deprecated since CMake 3.10.
+CMake 3.27 and above prefer to not provide the module.
+This policy provides compatibility for projects that have not been
+ported away from it.
+
+Projects using the :module:`FindCUDA` module should be updated to use
+CMake's first-class ``CUDA`` language support.  List ``CUDA`` among the
+languages named in the top-level call to the :command:`project` command,
+or call the :command:`enable_language` command with ``CUDA``.
+Then one can add CUDA (``.cu``) sources directly to targets,
+similar to other languages.
+
+The ``OLD`` behavior of this policy is for ``find_package(CUDA)`` to
+load the deprecated module.  The ``NEW`` behavior is for uses of the
+module to fail as if it does not exist.
+
+This policy was introduced in CMake version 3.27.  CMake version
+|release| warns when the policy is not set and uses ``OLD`` behavior.
+Use the :command:`cmake_policy` command to set it to ``OLD`` or ``NEW``
+explicitly.
+
+.. include:: DEPRECATED.txt
diff --git a/Help/policy/CMP0147.rst b/Help/policy/CMP0147.rst
new file mode 100644
index 0000000..0f25096
--- /dev/null
+++ b/Help/policy/CMP0147.rst
@@ -0,0 +1,24 @@
+CMP0147
+-------
+
+.. versionadded:: 3.27
+
+:ref:`Visual Studio Generators` build custom commands in parallel.
+
+Visual Studio 15.8 (2017) and newer support building custom commands in
+parallel.  CMake 3.27 and above prefer to enable this behavior by adding
+a ``BuildInParallel`` setting to custom commands in ``.vcxproj`` files.
+This policy provides compatibility for projects that have not been updated
+to expect this, e.g., because their custom commands were accidentally
+relying on serial execution by MSBuild.
+
+The ``OLD`` behavior for this policy is to not add ``BuildInParallel``.
+The ``NEW`` behavior for this policy is to add ``BuildInParallel`` for
+VS 15.8 and newer.
+
+This policy was introduced in CMake version 3.27.  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/policy/CMP0148.rst b/Help/policy/CMP0148.rst
new file mode 100644
index 0000000..2f5c43d
--- /dev/null
+++ b/Help/policy/CMP0148.rst
@@ -0,0 +1,29 @@
+CMP0148
+-------
+
+.. versionadded:: 3.27
+
+The :module:`FindPythonInterp` and :module:`FindPythonLibs` modules are removed.
+
+These modules have been deprecated since CMake 3.12.
+CMake 3.27 and above prefer to not provide the modules.
+This policy provides compatibility for projects that have not been
+ported away from them.
+
+Projects using the :module:`FindPythonInterp` and/or :module:`FindPythonLibs`
+modules should be updated to use one of their replacements:
+
+* :module:`FindPython3`
+* :module:`FindPython2`
+* :module:`FindPython`
+
+The ``OLD`` behavior of this policy is for ``find_package(PythonInterp)``
+and ``find_package(PythonLibs)`` to load the deprecated modules.  The ``NEW``
+behavior is for uses of the modules to fail as if they do not exist.
+
+This policy was introduced in CMake version 3.27.  CMake version
+|release| warns when the policy is not set and uses ``OLD`` behavior.
+Use the :command:`cmake_policy` command to set it to ``OLD`` or ``NEW``
+explicitly.
+
+.. include:: DEPRECATED.txt
diff --git a/Help/policy/CMP0149.rst b/Help/policy/CMP0149.rst
new file mode 100644
index 0000000..714eeaf
--- /dev/null
+++ b/Help/policy/CMP0149.rst
@@ -0,0 +1,47 @@
+CMP0149
+-------
+
+.. versionadded:: 3.27
+
+:ref:`Visual Studio Generators` select latest Windows SDK by default.
+
+Visual Studio Generators select a Windows SDK version to put in the
+``WindowsTargetPlatformVersion`` setting in ``.vcxproj`` files.
+CMake sets the :variable:`CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION`
+variable to the selected SDK version.
+
+Prior to CMake 3.27, the SDK version was always selected by the value of
+the :variable:`CMAKE_SYSTEM_VERSION` variable.  Users or toolchain files
+could set that variable to one of the exact Windows SDK versions available
+on the host system.  Since :variable:`CMAKE_SYSTEM_VERSION` defaults to
+:variable:`CMAKE_HOST_SYSTEM_VERSION`, and it is not guaranteed that a
+matching Windows SDK version is available, CMake had to fall back to
+using the latest Windows SDK version if no exact match was available.
+This approach was problematic:
+
+* The latest Windows SDK might or might not be selected based on whether
+  the host version of Windows happens to match an available SDK version.
+
+* An old Windows SDK version might be selected that has not been updated
+  for newer language standards such as C11.
+
+CMake 3.27 and higher prefer to ignore the exact value of
+:variable:`CMAKE_SYSTEM_VERSION` and by default select the latest SDK
+version available.  An exact SDK version may be specified explicitly
+using a ``version=`` field in the :variable:`CMAKE_GENERATOR_PLATFORM`
+variable.  See :ref:`Visual Studio Platform Selection`.
+
+This policy provides compatibility for projects, toolchain files, and
+build scripts that have not been ported away from using
+:variable:`CMAKE_SYSTEM_VERSION` to specify an exact SDK version.
+
+The ``OLD`` behavior for this policy is to use the exact value of
+:variable:`CMAKE_SYSTEM_VERSION` if possible.  The ``NEW`` behavior
+for this policy is to ignore it.
+
+This policy was introduced in CMake version 3.27.  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/policy/CMP0150.rst b/Help/policy/CMP0150.rst
new file mode 100644
index 0000000..fe646d9
--- /dev/null
+++ b/Help/policy/CMP0150.rst
@@ -0,0 +1,39 @@
+CMP0150
+-------
+
+.. versionadded:: 3.27
+
+:command:`ExternalProject_Add` and :command:`FetchContent_Declare` commands
+treat relative ``GIT_REPOSITORY`` paths as being relative to the parent
+project's remote.
+
+Earlier versions of these commands always treated relative paths in
+``GIT_REPOSITORY`` as local paths, but the base directory it was treated
+as relative to was both undocumented and unintuitive.  The ``OLD`` behavior
+for this policy is to interpret relative paths used for ``GIT_REPOSITORY``
+as local paths relative to the following:
+
+* The parent directory of ``SOURCE_DIR`` for :command:`ExternalProject_Add`.
+* ``FETCHCONTENT_BASE_DIR`` for :command:`FetchContent_Declare`.
+
+The ``NEW`` behavior is to determine the remote from the parent project and
+interpret the path relative to that remote.  The value of
+:variable:`CMAKE_CURRENT_SOURCE_DIR` when :command:`ExternalProject_Add` or
+:command:`FetchContent_Declare` is called determines the parent project.
+The remote is selected according to the following (the first match is used):
+
+* If the parent project is checked out on a branch with an upstream remote
+  defined, use that remote.
+* If only one remote is defined, use that remote.
+* If multiple remotes are defined and one of them is named ``origin``, use
+  ``origin``'s remote but also issue a warning.
+
+If an appropriate remote cannot be determined from the above, a fatal error
+will be raised.
+
+This policy was introduced in CMake version 3.27.  CMake version |release|
+warns when a relative path is encountered and the policy is not set,
+falling back to using ``OLD`` behavior.  Use the :command:`cmake_policy`
+command to set it to ``OLD`` or ``NEW`` explicitly.
+
+.. include:: DEPRECATED.txt
diff --git a/Help/policy/CMP0151.rst b/Help/policy/CMP0151.rst
new file mode 100644
index 0000000..c12f595
--- /dev/null
+++ b/Help/policy/CMP0151.rst
@@ -0,0 +1,28 @@
+CMP0151
+-------
+
+.. versionadded:: 3.27
+
+AUTOMOC include directory is a system include directory by default.
+
+Headers generated for :ref:`Qt AUTOMOC` are placed in target-specific include
+directories.  CMake 3.26 and older added these as normal include directories.
+CMake 3.27 and newer prefer to add them as system include directories.
+This policy provides compatibility for projects that have not been updated
+to expect this.
+
+If the :prop_tgt:`AUTOGEN_USE_SYSTEM_INCLUDE` target property is set,
+perhaps via the :variable:`CMAKE_AUTOGEN_USE_SYSTEM_INCLUDE` variable,
+then its value is used regardless of the setting of this policy.
+
+The ``OLD`` behavior for this policy is to add autogen include directory to
+the target's include directories.
+The ``NEW`` behavior for this policy is to add autogen include directory to
+the target's system include directories.
+
+This policy was introduced in CMake version 3.27.  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_sf/SKIP_LINTING.rst b/Help/prop_sf/SKIP_LINTING.rst
new file mode 100644
index 0000000..19592a8
--- /dev/null
+++ b/Help/prop_sf/SKIP_LINTING.rst
@@ -0,0 +1,41 @@
+SKIP_LINTING
+------------
+
+.. versionadded:: 3.27
+
+This property allows you to exclude a specific source file
+from the linting process. The linting process involves running
+tools such as :prop_tgt:`<LANG>_CPPLINT`, :prop_tgt:`<LANG>_CLANG_TIDY`,
+:prop_tgt:`<LANG>_CPPCHECK`, and :prop_tgt:`<LANG>_INCLUDE_WHAT_YOU_USE`
+on the source files. By setting ``SKIP_LINTING`` on a source file,
+the mentioned linting tools will not be executed for that
+particular file.
+
+Example
+^^^^^^^
+
+Consider a C++ project that includes multiple source files,
+such as ``main.cpp``, ``things.cpp``, and ``generatedBindings.cpp``.
+In this example, you want to exclude the ``generatedBindings.cpp``
+file from the linting process. To achieve this, you can utilize
+the ``SKIP_LINTING`` property with the :command:`set_source_files_properties`
+command as shown below:
+
+.. code-block:: cmake
+
+  add_executable(MyApp main.cpp things.cpp generatedBindings.cpp)
+
+  set_source_files_properties(generatedBindings.cpp PROPERTIES
+      SKIP_LINTING ON
+  )
+
+In the provided code snippet, the ``SKIP_LINTING`` property is set to true
+for the ``generatedBindings.cpp`` source file. As a result, when the linting
+tools specified by :prop_tgt:`<LANG>_CPPLINT`, :prop_tgt:`<LANG>_CLANG_TIDY`,
+:prop_tgt:`<LANG>_CPPCHECK`, or :prop_tgt:`<LANG>_INCLUDE_WHAT_YOU_USE`
+are executed, they will skip analyzing the ``generatedBindings.cpp`` file.
+
+By using the ``SKIP_LINTING`` property, you can selectively exclude specific
+source files from the linting process. This allows you to focus the
+linting tools on the relevant parts of your project, enhancing the efficiency
+and effectiveness of the linting workflow.
diff --git a/Help/prop_test/TIMEOUT.rst b/Help/prop_test/TIMEOUT.rst
index d1cb90d..385539b 100644
--- a/Help/prop_test/TIMEOUT.rst
+++ b/Help/prop_test/TIMEOUT.rst
@@ -7,3 +7,6 @@
 specified number of seconds to run.  If it exceeds that the test
 process will be killed and ctest will move to the next test.  This
 setting takes precedence over :variable:`CTEST_TEST_TIMEOUT`.
+
+An explicit ``0`` value means the test has no timeout, except as
+necessary to honor :option:`ctest --stop-time`.
diff --git a/Help/prop_test/WILL_FAIL.rst b/Help/prop_test/WILL_FAIL.rst
index f1f94a4..4926f40 100644
--- a/Help/prop_test/WILL_FAIL.rst
+++ b/Help/prop_test/WILL_FAIL.rst
@@ -3,5 +3,6 @@
 
 If set to true, this will invert the pass/fail flag of the test.
 
-This property can be used for tests that are expected to fail and
-return a non zero return code.
+This property can be used for tests that are expected to fail and return a
+non-zero return code. Note that system-level test failures such as segmentation
+faults or heap errors will still fail the test even if ``WILL_FALL`` is true.
diff --git a/Help/prop_tgt/ARCHIVE_OUTPUT_DIRECTORY.rst b/Help/prop_tgt/ARCHIVE_OUTPUT_DIRECTORY.rst
index 677e06d..abb627c 100644
--- a/Help/prop_tgt/ARCHIVE_OUTPUT_DIRECTORY.rst
+++ b/Help/prop_tgt/ARCHIVE_OUTPUT_DIRECTORY.rst
@@ -6,4 +6,7 @@
 .. |CMAKE_XXX_OUTPUT_DIRECTORY| replace:: :variable:`CMAKE_ARCHIVE_OUTPUT_DIRECTORY`
 .. include:: XXX_OUTPUT_DIRECTORY.txt
 
+.. |IDEM| replace:: in the same directory
+.. include:: MACOS_IMPORT_FILES.txt
+
 See also the :prop_tgt:`ARCHIVE_OUTPUT_DIRECTORY_<CONFIG>` target property.
diff --git a/Help/prop_tgt/ARCHIVE_OUTPUT_NAME.rst b/Help/prop_tgt/ARCHIVE_OUTPUT_NAME.rst
index 6150193..1f1c467 100644
--- a/Help/prop_tgt/ARCHIVE_OUTPUT_NAME.rst
+++ b/Help/prop_tgt/ARCHIVE_OUTPUT_NAME.rst
@@ -5,4 +5,7 @@
 .. |xxx| replace:: archive
 .. include:: XXX_OUTPUT_NAME.txt
 
+.. |IDEM| replace:: with the same name
+.. include:: MACOS_IMPORT_FILES.txt
+
 See also the :prop_tgt:`ARCHIVE_OUTPUT_NAME_<CONFIG>` target property.
diff --git a/Help/prop_tgt/AUTOGEN_USE_SYSTEM_INCLUDE.rst b/Help/prop_tgt/AUTOGEN_USE_SYSTEM_INCLUDE.rst
new file mode 100644
index 0000000..84212c8
--- /dev/null
+++ b/Help/prop_tgt/AUTOGEN_USE_SYSTEM_INCLUDE.rst
@@ -0,0 +1,17 @@
+AUTOGEN_USE_SYSTEM_INCLUDE
+--------------------------
+
+``AUTOGEN_USE_SYSTEM_INCLUDE`` is a boolean property that can be set
+on a target to indicate that the autogen target include directory should
+be added as a system include directory or normal include directory to the
+target.
+
+If this property is not set, the autogen target include directory is added
+as a system include directory by default.  See policy :policy:`CMP0151`.
+
+See the :manual:`cmake-qt(7)` manual for more information on using CMake
+with Qt.
+
+This property is initialized by the
+:variable:`CMAKE_AUTOGEN_USE_SYSTEM_INCLUDE` variable if it is set when
+a target is created.
diff --git a/Help/prop_tgt/AUTOMOC_MACRO_NAMES.rst b/Help/prop_tgt/AUTOMOC_MACRO_NAMES.rst
index 072e7f7..a4a9ba2 100644
--- a/Help/prop_tgt/AUTOMOC_MACRO_NAMES.rst
+++ b/Help/prop_tgt/AUTOMOC_MACRO_NAMES.rst
@@ -3,7 +3,7 @@
 
 .. versionadded:: 3.10
 
-A :ref:`semicolon-separated list <CMake Language Lists>` list of macro names used by
+A :ref:`semicolon-separated list <CMake Language Lists>` of macro names used by
 :prop_tgt:`AUTOMOC` to determine if a C++ file needs to be processed by ``moc``.
 
 This property is only used if the :prop_tgt:`AUTOMOC` property is ``ON``
@@ -21,6 +21,8 @@
 By default ``AUTOMOC_MACRO_NAMES`` is initialized from
 :variable:`CMAKE_AUTOMOC_MACRO_NAMES`.
 
+See also the :prop_tgt:`INTERFACE_AUTOMOC_MACRO_NAMES` target property.
+
 See the :manual:`cmake-qt(7)` manual for more information on using CMake
 with Qt.
 
@@ -29,6 +31,8 @@
 
 In this case the ``Q_OBJECT`` macro is hidden inside another macro
 called ``CUSTOM_MACRO``.  To let CMake know that source files that contain
-``CUSTOM_MACRO`` need to be ``moc`` processed, we call::
+``CUSTOM_MACRO`` need to be ``moc`` processed, we call:
+
+.. code-block:: cmake
 
   set_property(TARGET tgt APPEND PROPERTY AUTOMOC_MACRO_NAMES "CUSTOM_MACRO")
diff --git a/Help/prop_tgt/COMPILE_OPTIONS.rst b/Help/prop_tgt/COMPILE_OPTIONS.rst
index 2b93db1..8b032ad 100644
--- a/Help/prop_tgt/COMPILE_OPTIONS.rst
+++ b/Help/prop_tgt/COMPILE_OPTIONS.rst
@@ -11,6 +11,10 @@
 variables, but before those propagated from dependencies by the
 :prop_tgt:`INTERFACE_COMPILE_OPTIONS` property.
 
+This property adds compile options for all languages in a target.
+Use the :genex:`COMPILE_LANGUAGE` generator expression to specify
+per-language compile options.
+
 This property is initialized by the :prop_dir:`COMPILE_OPTIONS` directory
 property when a target is created, and is used by the generators to set
 the options for the compiler.
diff --git a/Help/prop_tgt/CUDA_CUBIN_COMPILATION.rst b/Help/prop_tgt/CUDA_CUBIN_COMPILATION.rst
new file mode 100644
index 0000000..f8860ae
--- /dev/null
+++ b/Help/prop_tgt/CUDA_CUBIN_COMPILATION.rst
@@ -0,0 +1,14 @@
+CUDA_CUBIN_COMPILATION
+----------------------
+
+.. versionadded:: 3.27
+
+Compile CUDA sources to ``.cubin`` files instead of ``.obj`` files
+within :ref:`Object Libraries`.
+
+For example:
+
+.. code-block:: cmake
+
+  add_library(mycubin OBJECT a.cu b.cu)
+  set_property(TARGET mycubin PROPERTY CUDA_CUBIN_COMPILATION ON)
diff --git a/Help/prop_tgt/CUDA_FATBIN_COMPILATION.rst b/Help/prop_tgt/CUDA_FATBIN_COMPILATION.rst
new file mode 100644
index 0000000..3d3c715
--- /dev/null
+++ b/Help/prop_tgt/CUDA_FATBIN_COMPILATION.rst
@@ -0,0 +1,14 @@
+CUDA_FATBIN_COMPILATION
+-----------------------
+
+.. versionadded:: 3.27
+
+Compile CUDA sources to ``.fatbin`` files instead of ``.obj`` files
+within :ref:`Object Libraries`.
+
+For example:
+
+.. code-block:: cmake
+
+  add_library(myfbins OBJECT a.cu b.cu)
+  set_property(TARGET myfbins PROPERTY CUDA_FATBIN_COMPILATION ON)
diff --git a/Help/prop_tgt/CUDA_OPTIX_COMPILATION.rst b/Help/prop_tgt/CUDA_OPTIX_COMPILATION.rst
new file mode 100644
index 0000000..c2a06a8
--- /dev/null
+++ b/Help/prop_tgt/CUDA_OPTIX_COMPILATION.rst
@@ -0,0 +1,14 @@
+CUDA_OPTIX_COMPILATION
+----------------------
+
+.. versionadded:: 3.27
+
+Compile CUDA sources to ``.optixir`` files instead of ``.obj`` files
+within :ref:`Object Libraries`.
+
+For example:
+
+.. code-block:: cmake
+
+  add_library(myoptix OBJECT a.cu b.cu)
+  set_property(TARGET myoptix PROPERTY CUDA_OPTIX_COMPILATION ON)
diff --git a/Help/prop_tgt/DLL_NAME_WITH_SOVERSION.rst b/Help/prop_tgt/DLL_NAME_WITH_SOVERSION.rst
new file mode 100644
index 0000000..59ef00f
--- /dev/null
+++ b/Help/prop_tgt/DLL_NAME_WITH_SOVERSION.rst
@@ -0,0 +1,17 @@
+DLL_NAME_WITH_SOVERSION
+-----------------------
+
+.. versionadded:: 3.27
+
+This property control whether the :prop_tgt:`SOVERSION` target
+property are added to the filename of generated DLL filenames
+for the Windows platform, which is selected when the
+:variable:`WIN32` variable is set.
+
+The value of the listed property is appended to the
+basename of the runtime component of the shared library
+target as ``-<SOVERSION>``.
+
+Please note that setting this property has no effect
+if versioned filenames are globally disabled with the
+:variable:`CMAKE_PLATFORM_NO_VERSIONED_SONAME` variable.
diff --git a/Help/prop_tgt/ENABLE_EXPORTS.rst b/Help/prop_tgt/ENABLE_EXPORTS.rst
index 0b1064a..3e9b285 100644
--- a/Help/prop_tgt/ENABLE_EXPORTS.rst
+++ b/Help/prop_tgt/ENABLE_EXPORTS.rst
@@ -1,7 +1,7 @@
 ENABLE_EXPORTS
 --------------
 
-Specify whether an executable exports symbols for loadable modules.
+Specify whether an executable or a shared library exports symbols.
 
 Normally an executable does not export any symbols because it is the
 final program.  It is possible for an executable to export symbols to
@@ -28,4 +28,29 @@
   automatically bind symbols when the module is loaded.
 
 This property is initialized by the value of the variable
-:variable:`CMAKE_ENABLE_EXPORTS` if it is set when a target is created.
+:variable:`CMAKE_EXECUTABLE_ENABLE_EXPORTS` if it is set when an executable
+target is created.
+
+.. versionadded:: 3.27
+  On macOS, to link with a shared library (standard one as well as framework),
+  a linker import file (e.g. a text-based stubs file, with ``.tbd`` extension)
+  can be used instead of the shared library itself.
+
+The generation of these linker import files, as well as the consumption, is
+controlled by this property. When this property is set to true, CMake will
+generate a ``.tbd`` file for each shared library created by
+:command:`add_library` command. This allow other targets to use this ``.tbd``
+file to link to the library with the :command:`target_link_libraries`
+command.
+
+.. note::
+
+  For compatibility purpose, this property will be ignored if
+  :prop_tgt:`XCODE_ATTRIBUTE_GENERATE_TEXT_BASED_STUBS <XCODE_ATTRIBUTE_<an-attribute>>`
+  target property or the
+  :variable:`CMAKE_XCODE_ATTRIBUTE_GENERATE_TEXT_BASED_STUBS <CMAKE_XCODE_ATTRIBUTE_<an-attribute>>`
+  variable is set to ``NO``.
+
+This property is initialized by the value of the variable
+:variable:`CMAKE_SHARED_LIBRARY_ENABLE_EXPORTS` if it is set when a shared
+library target is created.
diff --git a/Help/prop_tgt/IMPORTED_CONFIGURATIONS.rst b/Help/prop_tgt/IMPORTED_CONFIGURATIONS.rst
index 6de1baa..a4746d3 100644
--- a/Help/prop_tgt/IMPORTED_CONFIGURATIONS.rst
+++ b/Help/prop_tgt/IMPORTED_CONFIGURATIONS.rst
@@ -1,11 +1,22 @@
 IMPORTED_CONFIGURATIONS
 -----------------------
 
-Configurations provided for an IMPORTED target.
+Configurations provided for an :ref:`imported target <Imported targets>`.
 
-Set this to the list of configuration names available for an IMPORTED
-target.  The names correspond to configurations defined in the project
-from which the target is imported.  If the importing project uses a
-different set of configurations the names may be mapped using the
-MAP_IMPORTED_CONFIG_<CONFIG> property.  Ignored for non-imported
-targets.
+Set this to the list of configuration names available for an imported
+target.  For each configuration named, the imported target's artifacts
+must be specified in other target properties:
+
+* :prop_tgt:`IMPORTED_LOCATION_<CONFIG>`, or
+* :prop_tgt:`IMPORTED_IMPLIB_<CONFIG>` (on DLL platforms, on AIX for
+  :ref:`Executables <Binary Executables>` or on Apple for
+  :ref:`Shared Libraries <Normal Libraries>`), or
+* :prop_tgt:`IMPORTED_OBJECTS_<CONFIG>` (for :ref:`Object Libraries`), or
+* :prop_tgt:`IMPORTED_LIBNAME_<CONFIG>` (for :ref:`Interface Libraries`).
+
+The configuration names correspond to those defined in the project from
+which the target is imported.  If the importing project uses a different
+set of configurations, the names may be mapped using the
+:prop_tgt:`MAP_IMPORTED_CONFIG_<CONFIG>` target property.
+
+The ``IMPORTED_CONFIGURATIONS`` property is ignored for non-imported targets.
diff --git a/Help/prop_tgt/IMPORTED_IMPLIB.rst b/Help/prop_tgt/IMPORTED_IMPLIB.rst
index c8b6fde..e67acba 100644
--- a/Help/prop_tgt/IMPORTED_IMPLIB.rst
+++ b/Help/prop_tgt/IMPORTED_IMPLIB.rst
@@ -3,7 +3,22 @@
 
 Full path to the import library for an ``IMPORTED`` target.
 
-Set this to the location of the ``.lib`` part of a Windows DLL, or on
-AIX set it to an import file created for executables that export symbols
-(see the :prop_tgt:`ENABLE_EXPORTS` target property).
-Ignored for non-imported targets.
+This property may be set:
+
+* On DLL platforms, to the location of the ``.lib`` part of the DLL.
+* On AIX, to an import file (e.g. ``.imp``) created for executables that export
+  symbols (see the :prop_tgt:`ENABLE_EXPORTS` target property).
+* On macOS, to an import file (e.g. ``.tbd``) created for shared libraries (see
+  the :prop_tgt:`ENABLE_EXPORTS` target property). For frameworks this is the
+  location of the ``.tbd`` file symlink just inside the framework folder.
+
+The ``IMPORTED_IMPLIB`` target property may be overridden for a
+given configuration ``<CONFIG>`` by the configuration-specific
+:prop_tgt:`IMPORTED_IMPLIB_<CONFIG>` target property.  Furthermore,
+the :prop_tgt:`MAP_IMPORTED_CONFIG_<CONFIG>` target property may be
+used to map between a project's configurations and those of an imported
+target.  If none of these is set then the name of any other configuration
+listed in the :prop_tgt:`IMPORTED_CONFIGURATIONS` target property may be
+selected and its :prop_tgt:`IMPORTED_IMPLIB_<CONFIG>` value used.
+
+This property is ignored for non-imported targets.
diff --git a/Help/prop_tgt/IMPORTED_LOCATION.rst b/Help/prop_tgt/IMPORTED_LOCATION.rst
index ddd910a..a7207d8 100644
--- a/Help/prop_tgt/IMPORTED_LOCATION.rst
+++ b/Help/prop_tgt/IMPORTED_LOCATION.rst
@@ -27,5 +27,5 @@
 To get the location of an imported target read one of the :prop_tgt:`LOCATION`
 or ``LOCATION_<CONFIG>`` properties.
 
-For platforms with import libraries (e.g. Windows) see also
+For platforms with import libraries (e.g. Windows, AIX or macOS) see also
 :prop_tgt:`IMPORTED_IMPLIB`.
diff --git a/Help/prop_tgt/IMPORTED_NO_SYSTEM.rst b/Help/prop_tgt/IMPORTED_NO_SYSTEM.rst
index c8ec8b5..e1fea37 100644
--- a/Help/prop_tgt/IMPORTED_NO_SYSTEM.rst
+++ b/Help/prop_tgt/IMPORTED_NO_SYSTEM.rst
@@ -25,6 +25,8 @@
   would be by default.   Entries of
   :prop_tgt:`INTERFACE_SYSTEM_INCLUDE_DIRECTORIES` are not affected,
   and will always be treated as system include directories.
+* On Apple platforms, when the target is a framework, it will not be treated as
+  system.
 
 This property can also be enabled on a non-imported target.  Doing so does
 not affect the build system, but does tell the :command:`install(EXPORT)` and
diff --git a/Help/prop_tgt/INTERFACE_AUTOMOC_MACRO_NAMES.rst b/Help/prop_tgt/INTERFACE_AUTOMOC_MACRO_NAMES.rst
new file mode 100644
index 0000000..502775c
--- /dev/null
+++ b/Help/prop_tgt/INTERFACE_AUTOMOC_MACRO_NAMES.rst
@@ -0,0 +1,89 @@
+INTERFACE_AUTOMOC_MACRO_NAMES
+-----------------------------
+
+.. versionadded:: 3.27
+
+A :ref:`semicolon-separated list <CMake Language Lists>` of macro names for
+:prop_tgt:`AUTOMOC` to be propagated to consumers.
+
+When a target with :prop_tgt:`AUTOMOC` enabled links to a library that sets
+``INTERFACE_AUTOMOC_MACRO_NAMES``, the target inherits the listed macro names
+and merges them with those specified in its own :prop_tgt:`AUTOMOC_MACRO_NAMES`
+property.  The target will then automatically generate MOC files for source
+files that contain the inherited macro names too, not just the macro names
+specified in its own :prop_tgt:`AUTOMOC_MACRO_NAMES` property.
+
+By default ``INTERFACE_AUTOMOC_MACRO_NAMES`` is empty.
+
+See the :manual:`cmake-qt(7)` manual for more information on using CMake
+with Qt.
+
+Example 1
+^^^^^^^^^
+
+In this example, ``myapp`` inherits the macro names ``STATIC_LIB_1`` and
+``STATIC_LIB_2`` from ``static_lib``.  The ``moc`` tool will then automatically
+be run on any of the ``myapp`` sources which contain ``STATIC_LIB_1`` or
+``STATIC_LIB_2``.
+
+.. code-block:: cmake
+
+  set(AUTOMOC ON)
+  add_executable(myapp main.cpp)
+  target_link_libraries(myapp PRIVATE static_lib)
+
+  add_library(static_lib STATIC static.cpp)
+  set_property(TARGET static_lib PROPERTY
+    INTERFACE_AUTOMOC_MACRO_NAMES "STATIC_LIB_1;STATIC_LIB_2"
+  )
+
+Example 2
+^^^^^^^^^
+
+In this example, the ``INTERFACE_AUTOMOC_MACRO_NAMES`` target property of the
+various ``*_deep_lib`` libraries will propagate to ``shared_lib``,
+``static_lib`` and ``interface_lib``.  Because the linking relationships are
+specified as ``PUBLIC`` and ``INTERFACE``, those macro names will also further
+propagate transitively up to ``app``.
+
+.. code-block:: cmake
+
+  set(AUTOMOC ON)
+
+  add_library(shared_deep_lib SHARED deep_lib.cpp)
+  add_library(static_deep_lib STATIC deep_lib.cpp)
+  add_library(interface_deep_lib INTERFACE)
+
+  set_property(TARGET shared_deep_lib PROPERTY
+    INTERFACE_AUTOMOC_MACRO_NAMES "SHARED_LINK_LIB"
+  )
+  set_property(TARGET static_deep_lib PROPERTY
+    INTERFACE_AUTOMOC_MACRO_NAMES "STATIC_LINK_LIB"
+  )
+  set_property(TARGET interface_deep_lib PROPERTY
+    INTERFACE_AUTOMOC_MACRO_NAMES "INTERFACE_LINK_LIB"
+  )
+
+  add_library(shared_lib SHARED lib.cpp)
+  add_library(static_lib STATIC lib.cpp)
+  add_library(interface_lib INTERFACE)
+
+  # PUBLIC and INTERFACE here ensure the macro names propagate to any
+  # consumers of shared_lib, static_lib or interface_lib too
+  target_link_libraries(shared_lib PUBLIC shared_deep_lib)
+  target_link_libraries(static_lib PUBLIC static_deep_lib)
+  target_link_libraries(interface_lib INTERFACE interface_deep_lib)
+
+  # This consumer will receive all three of the above custom macro names as
+  # transitive usage requirements
+  add_executable(app main.cpp)
+  target_link_libraries(app PRIVATE shared_lib static_lib interface_lib)
+
+In the above:
+
+* ``shared_lib`` sources will be processed by ``moc`` if they contain
+  ``SHARED_LINK_LIB``.
+* ``static_lib`` sources will be processed by ``moc`` if they contain
+  ``STATIC_LINK_LIB``.
+* ``app`` sources will be processed by ``moc`` if they contain
+  ``SHARED_LINK_LIB``, ``STATIC_LINK_LIB`` or ``INTERFACE_LINK_LIB``.
diff --git a/Help/prop_tgt/LANG_CLANG_TIDY.rst b/Help/prop_tgt/LANG_CLANG_TIDY.rst
index 31f1876..9ecdc57 100644
--- a/Help/prop_tgt/LANG_CLANG_TIDY.rst
+++ b/Help/prop_tgt/LANG_CLANG_TIDY.rst
@@ -25,3 +25,17 @@
 This property is initialized by the value of
 the :variable:`CMAKE_<LANG>_CLANG_TIDY` variable if it is set
 when a target is created.
+
+.. versionadded:: 3.27
+
+  This property supports
+  :manual:`generator expressions <cmake-generator-expressions(7)>`.
+
+.. versionadded:: 3.27
+
+  :prop_sf:`SKIP_LINTING` can be set on individual source files to exclude
+  them from the linting tools defined by :prop_tgt:`<LANG>_CPPLINT`,
+  ``<LANG>_CLANG_TIDY``, :prop_tgt:`<LANG>_CPPCHECK`, and
+  :prop_tgt:`<LANG>_INCLUDE_WHAT_YOU_USE`.  When :prop_sf:`SKIP_LINTING` is
+  set to true on a source file, those tools will not be run on that specific
+  file.
diff --git a/Help/prop_tgt/LANG_CPPCHECK.rst b/Help/prop_tgt/LANG_CPPCHECK.rst
index 80acbc0..0628061 100644
--- a/Help/prop_tgt/LANG_CPPCHECK.rst
+++ b/Help/prop_tgt/LANG_CPPCHECK.rst
@@ -15,3 +15,17 @@
 This property is initialized by the value of the
 :variable:`CMAKE_<LANG>_CPPCHECK` variable if it is set when a target is
 created.
+
+.. versionadded:: 3.27
+
+  This property supports
+  :manual:`generator expressions <cmake-generator-expressions(7)>`.
+
+.. versionadded:: 3.27
+
+  :prop_sf:`SKIP_LINTING` can be set on individual source files to exclude
+  them from the linting tools defined by :prop_tgt:`<LANG>_CPPLINT`,
+  :prop_tgt:`<LANG>_CLANG_TIDY`, ``<LANG>_CPPCHECK``, and
+  :prop_tgt:`<LANG>_INCLUDE_WHAT_YOU_USE`.  When :prop_sf:`SKIP_LINTING` is
+  set to true on a source file, those tools will not be run on that specific
+  file.
diff --git a/Help/prop_tgt/LANG_CPPLINT.rst b/Help/prop_tgt/LANG_CPPLINT.rst
index be6db46..02610da 100644
--- a/Help/prop_tgt/LANG_CPPLINT.rst
+++ b/Help/prop_tgt/LANG_CPPLINT.rst
@@ -13,3 +13,17 @@
 This property is initialized by the value of the
 :variable:`CMAKE_<LANG>_CPPLINT` variable if it is set when a target is
 created.
+
+.. versionadded:: 3.27
+
+  This property supports
+  :manual:`generator expressions <cmake-generator-expressions(7)>`.
+
+.. versionadded:: 3.27
+
+  :prop_sf:`SKIP_LINTING` can be set on individual source files to exclude
+  them from the linting tools defined by ``<LANG>_CPPLINT``,
+  :prop_tgt:`<LANG>_CLANG_TIDY`, :prop_tgt:`<LANG>_CPPCHECK`, and
+  :prop_tgt:`<LANG>_INCLUDE_WHAT_YOU_USE`.  When :prop_sf:`SKIP_LINTING` is
+  set to true on a source file, those tools will not be run on that specific
+  file.
diff --git a/Help/prop_tgt/LANG_INCLUDE_WHAT_YOU_USE.rst b/Help/prop_tgt/LANG_INCLUDE_WHAT_YOU_USE.rst
index eebef56..2f51c8f 100644
--- a/Help/prop_tgt/LANG_INCLUDE_WHAT_YOU_USE.rst
+++ b/Help/prop_tgt/LANG_INCLUDE_WHAT_YOU_USE.rst
@@ -13,3 +13,17 @@
 This property is initialized by the value of
 the :variable:`CMAKE_<LANG>_INCLUDE_WHAT_YOU_USE` variable if it is set
 when a target is created.
+
+.. versionadded:: 3.27
+
+  This property supports
+  :manual:`generator expressions <cmake-generator-expressions(7)>`.
+
+.. versionadded:: 3.27
+
+  :prop_sf:`SKIP_LINTING` can be set on individual source files to exclude
+  them from the linting tools defined by :prop_tgt:`<LANG>_CPPLINT`,
+  :prop_tgt:`<LANG>_CLANG_TIDY`, :prop_tgt:`<LANG>_CPPCHECK`, and
+  ``<LANG>_INCLUDE_WHAT_YOU_USE``.  When :prop_sf:`SKIP_LINTING` is
+  set to true on a source file, those tools will not be run on that specific
+  file.
diff --git a/Help/prop_tgt/LANG_LINKER_LAUNCHER.rst b/Help/prop_tgt/LANG_LINKER_LAUNCHER.rst
index f6ca5ad..d39ec20 100644
--- a/Help/prop_tgt/LANG_LINKER_LAUNCHER.rst
+++ b/Help/prop_tgt/LANG_LINKER_LAUNCHER.rst
@@ -14,3 +14,8 @@
 This property is initialized by the value of the
 :variable:`CMAKE_<LANG>_LINKER_LAUNCHER` variable if it is set when a target is
 created.
+
+.. versionadded:: 3.27
+
+  The property value may use
+  :manual:`generator expressions <cmake-generator-expressions(7)>`.
diff --git a/Help/prop_tgt/MACOS_IMPORT_FILES.txt b/Help/prop_tgt/MACOS_IMPORT_FILES.txt
new file mode 100644
index 0000000..3c98fc8
--- /dev/null
+++ b/Help/prop_tgt/MACOS_IMPORT_FILES.txt
@@ -0,0 +1,12 @@
+.. note::
+
+  On macOS, this property will be ignored for the linker import files (e.g.
+  ``.tbd`` files, see :prop_tgt:`ENABLE_EXPORTS` property for details) when:
+
+  * The :prop_tgt:`FRAMEWORK` is set, because the framework layout cannot be
+    changed.
+  * The :generator:`Xcode` generator is used, due to the limitations and
+    constraints of the ``Xcode`` tool.
+
+  In both cases, the linker import files will be generated |IDEM| as the shared
+  library.
diff --git a/Help/prop_tgt/NO_SYSTEM_FROM_IMPORTED.rst b/Help/prop_tgt/NO_SYSTEM_FROM_IMPORTED.rst
index a4c9b9f..458618b 100644
--- a/Help/prop_tgt/NO_SYSTEM_FROM_IMPORTED.rst
+++ b/Help/prop_tgt/NO_SYSTEM_FROM_IMPORTED.rst
@@ -4,13 +4,14 @@
 Do not treat include directories from the interfaces of consumed
 :ref:`imported targets` as system directories.
 
-The contents of the :prop_tgt:`INTERFACE_INCLUDE_DIRECTORIES` target property
-are treated as system includes when the consumed target's :prop_tgt:`SYSTEM`
-property is set to true.  By default, :prop_tgt:`SYSTEM` is true for imported
-targets and false for other target types.  If the ``NO_SYSTEM_FROM_IMPORTED``
-property is set to true on a *consuming* target, compilation of sources in that
-consuming target will not treat the contents of the
-:prop_tgt:`INTERFACE_INCLUDE_DIRECTORIES` of consumed imported targets as
+When the consumed target's :prop_tgt:`SYSTEM` property is set to true, the
+contents of the :prop_tgt:`INTERFACE_INCLUDE_DIRECTORIES` target property are
+treated as system includes or, on Apple platforms, when the target is a
+framework, it will be treated as system.  By default, :prop_tgt:`SYSTEM` is
+true for imported targets and false for other target types.  If the
+``NO_SYSTEM_FROM_IMPORTED`` property is set to true on a *consuming* target,
+compilation of sources in that consuming target will not treat the contents of
+the :prop_tgt:`INTERFACE_INCLUDE_DIRECTORIES` of consumed imported targets as
 system includes, even if that imported target's :prop_tgt:`SYSTEM` property
 is false.
 
diff --git a/Help/prop_tgt/SYSTEM.rst b/Help/prop_tgt/SYSTEM.rst
index c7ae27e..f5c11bc 100644
--- a/Help/prop_tgt/SYSTEM.rst
+++ b/Help/prop_tgt/SYSTEM.rst
@@ -10,13 +10,17 @@
   system include directories when compiling consumers.
   Entries of :prop_tgt:`INTERFACE_SYSTEM_INCLUDE_DIRECTORIES` are not
   affected, and will always be treated as system include directories.
+* On Apple platforms, If the :prop_tgt:`FRAMEWORK` target property is true,
+  the frameworks directory is treated as system.
 
 For imported targets, this property defaults to true, which means
-that their :prop_tgt:`INTERFACE_INCLUDE_DIRECTORIES` are treated
-as system directories by default.  If their ``SYSTEM`` property is false,
-then their :prop_tgt:`INTERFACE_INCLUDE_DIRECTORIES` will not be
-treated as system.  Use the :prop_tgt:`EXPORT_NO_SYSTEM` property to change
-how a target's ``SYSTEM`` property is set when it is installed.
+that their :prop_tgt:`INTERFACE_INCLUDE_DIRECTORIES` and, if the
+:prop_tgt:`FRAMEWORK` target property is true, frameworks directory are
+treated as system directories by default.  If their ``SYSTEM`` property is
+false, then their :prop_tgt:`INTERFACE_INCLUDE_DIRECTORIES` as well as
+frameworks will not be treated as system.  Use the :prop_tgt:`EXPORT_NO_SYSTEM`
+property to change how a target's ``SYSTEM`` property is set when it is
+installed.
 
 For non-imported targets, this target property is initialized from
 the :prop_dir:`SYSTEM` directory property when the target is created.
diff --git a/Help/prop_tgt/VS_DEBUGGER_COMMAND.rst b/Help/prop_tgt/VS_DEBUGGER_COMMAND.rst
index 1e84c00..5bf47a3 100644
--- a/Help/prop_tgt/VS_DEBUGGER_COMMAND.rst
+++ b/Help/prop_tgt/VS_DEBUGGER_COMMAND.rst
@@ -7,7 +7,9 @@
 The property value may use
 :manual:`generator expressions <cmake-generator-expressions(7)>`.
 This is defined in ``<LocalDebuggerCommand>`` in the Visual Studio
-project file.
+project file.  This property is initialized by the value of the variable
+:variable:`CMAKE_VS_DEBUGGER_COMMAND` if it is set when a target is
+created.
 
 This property only works for Visual Studio 11 2012 and above;
 it is ignored on other generators.
diff --git a/Help/prop_tgt/VS_DEBUGGER_COMMAND_ARGUMENTS.rst b/Help/prop_tgt/VS_DEBUGGER_COMMAND_ARGUMENTS.rst
index e54e140..4b9dff7 100644
--- a/Help/prop_tgt/VS_DEBUGGER_COMMAND_ARGUMENTS.rst
+++ b/Help/prop_tgt/VS_DEBUGGER_COMMAND_ARGUMENTS.rst
@@ -7,7 +7,9 @@
 The property value may use
 :manual:`generator expressions <cmake-generator-expressions(7)>`.
 This is defined in ``<LocalDebuggerCommandArguments>`` in the Visual Studio
-project file.
+project file.  This property is initialized by the value of the variable
+:variable:`CMAKE_VS_DEBUGGER_COMMAND_ARGUMENTS` if it is set when a target is
+created.
 
 This property only works for Visual Studio 11 2012 and above;
 it is ignored on other generators.
diff --git a/Help/prop_tgt/VS_DEBUGGER_ENVIRONMENT.rst b/Help/prop_tgt/VS_DEBUGGER_ENVIRONMENT.rst
index 60bc2f0..8373dbb 100644
--- a/Help/prop_tgt/VS_DEBUGGER_ENVIRONMENT.rst
+++ b/Help/prop_tgt/VS_DEBUGGER_ENVIRONMENT.rst
@@ -7,7 +7,9 @@
 The property value may use
 :manual:`generator expressions <cmake-generator-expressions(7)>`.
 This is defined in ``<LocalDebuggerEnvironment>`` in the Visual Studio
-project file.
+project file.  This property is initialized by the value of the variable
+:variable:`CMAKE_VS_DEBUGGER_ENVIRONMENT` if it is set when a target is
+created.
 
 This property only works for Visual Studio 11 2012 and above;
 it is ignored on other generators.
diff --git a/Help/prop_tgt/VS_DEBUGGER_WORKING_DIRECTORY.rst b/Help/prop_tgt/VS_DEBUGGER_WORKING_DIRECTORY.rst
index f9ce7aa..3942047 100644
--- a/Help/prop_tgt/VS_DEBUGGER_WORKING_DIRECTORY.rst
+++ b/Help/prop_tgt/VS_DEBUGGER_WORKING_DIRECTORY.rst
@@ -7,7 +7,9 @@
 The property value may use
 :manual:`generator expressions <cmake-generator-expressions(7)>`.
 This is defined in ``<LocalDebuggerWorkingDirectory>`` in the Visual Studio
-project file.
+project file.  This property is initialized by the value of the variable
+:variable:`CMAKE_VS_DEBUGGER_WORKING_DIRECTORY` if it is set when a target is
+created.
 
 This property only works for Visual Studio 11 2012 and above;
 it is ignored on other generators.
diff --git a/Help/prop_tgt/VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION.rst b/Help/prop_tgt/VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION.rst
index 50cf203..ef3ceb0 100644
--- a/Help/prop_tgt/VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION.rst
+++ b/Help/prop_tgt/VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION.rst
@@ -7,6 +7,11 @@
 
 For Windows 10. Specifies the minimum version of the OS that is being
 targeted. For example ``10.0.10240.0``. If the value is not specified, the
-value of :variable:`CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION` will be used on
-WindowsStore projects otherwise the target platform minimum version will not
-be specified for the project.
+value of the :variable:`CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION` variable
+will be used on WindowsStore projects.  Otherwise the target platform
+minimum version will not be specified for the project.
+
+.. versionadded:: 3.27
+  This property is initialized by the value of the
+  :variable:`CMAKE_VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION` variable
+  if it is set when a target is created.
diff --git a/Help/release/3.11.rst b/Help/release/3.11.rst
index 957dd4f..6e1520a 100644
--- a/Help/release/3.11.rst
+++ b/Help/release/3.11.rst
@@ -174,7 +174,7 @@
   to removal of the ``javah`` tool by `JEP 313`_.
 
 .. _`FLAME`: https://github.com/flame
-.. _`JEP 313`: https://openjdk.java.net/jeps/313
+.. _`JEP 313`: https://openjdk.org/jeps/313
 
 Autogen
 -------
diff --git a/Help/release/dev/0-sample-topic.rst b/Help/release/dev/0-sample-topic.rst
new file mode 100644
index 0000000..e4cc01e
--- /dev/null
+++ b/Help/release/dev/0-sample-topic.rst
@@ -0,0 +1,7 @@
+0-sample-topic
+--------------
+
+* This is a sample release note for the change in a topic.
+  Developers should add similar notes for each topic branch
+  making a noteworthy change.  Each document should be named
+  and titled to match the topic name to avoid merge conflicts.
diff --git a/Help/release/dev/Apple-tbd-files-management.rst b/Help/release/dev/Apple-tbd-files-management.rst
new file mode 100644
index 0000000..edcfe55
--- /dev/null
+++ b/Help/release/dev/Apple-tbd-files-management.rst
@@ -0,0 +1,6 @@
+Apple-tbd-files-management
+--------------------------
+
+* Support for text-based stubs (i.e. ``.tbd`` files) was added on macOS
+  platform. This capability is managed through the :prop_tgt:`ENABLE_EXPORTS`
+  property.
diff --git a/Help/release/dev/ExternalProject-FetchContent-relative-git-remotes.rst b/Help/release/dev/ExternalProject-FetchContent-relative-git-remotes.rst
new file mode 100644
index 0000000..d467620
--- /dev/null
+++ b/Help/release/dev/ExternalProject-FetchContent-relative-git-remotes.rst
@@ -0,0 +1,7 @@
+ExternalProject-FetchContent-Relative-git-remotes
+-------------------------------------------------
+
+* The :module:`ExternalProject` and :module:`FetchContent` modules
+  now resolve relative `GIT_REPOSITORY` paths as relative to the
+  parent project's remote, not as a relative local file system path.
+  See :policy:`CMP0150`.
diff --git a/Help/release/dev/FileAPI-Frameworks.rst b/Help/release/dev/FileAPI-Frameworks.rst
new file mode 100644
index 0000000..65cf043
--- /dev/null
+++ b/Help/release/dev/FileAPI-Frameworks.rst
@@ -0,0 +1,7 @@
+FileAPI-Frameworks
+------------------
+
+* The :manual:`cmake-file-api(7)` "codemodel" version 2 ``version`` field has
+  been updated to 2.6.
+* The :manual:`cmake-file-api(7)` "codemodel" version 2 "target" object gained
+  a new "frameworks" field in the "compileGroups" objects.
diff --git a/Help/release/dev/FindCUDA-remove.rst b/Help/release/dev/FindCUDA-remove.rst
new file mode 100644
index 0000000..e8b09d4
--- /dev/null
+++ b/Help/release/dev/FindCUDA-remove.rst
@@ -0,0 +1,6 @@
+FindCUDA-remove
+---------------
+
+* The :module:`FindCUDA` module has been fully deprecated via policy
+  :policy:`CMP0146`.  Port projects to CMake's first-class ``CUDA``
+  language support.
diff --git a/Help/release/dev/FindCUDAToolkit-target-for-cudla.rst b/Help/release/dev/FindCUDAToolkit-target-for-cudla.rst
new file mode 100644
index 0000000..9de456e
--- /dev/null
+++ b/Help/release/dev/FindCUDAToolkit-target-for-cudla.rst
@@ -0,0 +1,4 @@
+FindCUDAToolkit-target-for-cudla
+--------------------------------
+
+* The :module:`FindCUDAToolkit` module now provides an imported target for ``cudla``, if found.
diff --git a/Help/release/dev/FindDoxygen-custom-config-file.rst b/Help/release/dev/FindDoxygen-custom-config-file.rst
new file mode 100644
index 0000000..badc26e
--- /dev/null
+++ b/Help/release/dev/FindDoxygen-custom-config-file.rst
@@ -0,0 +1,5 @@
+FindDoxygen-custom-config-file
+------------------------------
+
+* The :module:`FindDoxygen` module's ``doxygen_add_docs`` command gained
+  a ``CONFIG_FILE`` option to specify a custom doxygen configuration file.
diff --git a/Help/release/dev/FindOpenGL-gles.rst b/Help/release/dev/FindOpenGL-gles.rst
new file mode 100644
index 0000000..fcbc516
--- /dev/null
+++ b/Help/release/dev/FindOpenGL-gles.rst
@@ -0,0 +1,5 @@
+FindOpenGL-gles
+---------------
+
+* The :module:`FindOpenGL` module gained support for components
+  ``GLES2`` and ``GLES3``.
diff --git a/Help/release/dev/FindPython-Windows-ARM.rst b/Help/release/dev/FindPython-Windows-ARM.rst
new file mode 100644
index 0000000..d88a65a
--- /dev/null
+++ b/Help/release/dev/FindPython-Windows-ARM.rst
@@ -0,0 +1,5 @@
+FindPython-Windows-ARM
+----------------------
+
+* :module:`FindPython`, :module:`FindPython2` and :module:`FindPython3` modules
+  learn to manage ``Windows/ARM`` platform.
diff --git a/Help/release/dev/FindwxWidgets-imported-target.rst b/Help/release/dev/FindwxWidgets-imported-target.rst
new file mode 100644
index 0000000..c04e0ac
--- /dev/null
+++ b/Help/release/dev/FindwxWidgets-imported-target.rst
@@ -0,0 +1,4 @@
+FindwxWidgets-imported-target
+-----------------------------
+
+* The :module:`FindwxWidgets` module now provides an imported target.
diff --git a/Help/release/dev/GenEx-LIST.rst b/Help/release/dev/GenEx-LIST.rst
new file mode 100644
index 0000000..f65a092
--- /dev/null
+++ b/Help/release/dev/GenEx-LIST.rst
@@ -0,0 +1,4 @@
+GenEx-LIST
+----------
+
+* The :genex:`LIST` generator expression was added to manage lists.
diff --git a/Help/release/dev/PATH-genex-support-list.rst b/Help/release/dev/PATH-genex-support-list.rst
new file mode 100644
index 0000000..ce87fdd
--- /dev/null
+++ b/Help/release/dev/PATH-genex-support-list.rst
@@ -0,0 +1,5 @@
+PATH-genex-supports-list
+------------------------
+
+* The :genex:`$<PATH>` generator expression learned to process list of paths
+  for decomposition and transformation operations.
diff --git a/Help/release/dev/System-Framework.rst b/Help/release/dev/System-Framework.rst
new file mode 100644
index 0000000..106cf23
--- /dev/null
+++ b/Help/release/dev/System-Framework.rst
@@ -0,0 +1,4 @@
+System-Framework
+----------------
+
+* The :prop_tgt:`SYSTEM` target property is now honored for Apple Frameworks.
diff --git a/Help/release/dev/autogen-exe-vars.rst b/Help/release/dev/autogen-exe-vars.rst
new file mode 100644
index 0000000..a386b4f
--- /dev/null
+++ b/Help/release/dev/autogen-exe-vars.rst
@@ -0,0 +1,7 @@
+autogen-exe-vars
+----------------
+
+* The :variable:`CMAKE_AUTOMOC_EXECUTABLE`,
+  :variable:`CMAKE_AUTORCC_EXECUTABLE`, and
+  :variable:`CMAKE_AUTOUIC_EXECUTABLE` variables were added to initialize the
+  corresponding target properties as targets are created.
diff --git a/Help/release/dev/autogen-system-include.rst b/Help/release/dev/autogen-system-include.rst
new file mode 100644
index 0000000..aea81be
--- /dev/null
+++ b/Help/release/dev/autogen-system-include.rst
@@ -0,0 +1,7 @@
+autogen-system-include
+----------------------
+
+* The :prop_tgt:`AUTOGEN_USE_SYSTEM_INCLUDE` target property and
+  corresponding :variable:`CMAKE_AUTOGEN_USE_SYSTEM_INCLUDE` were
+  added to explicitly control whether autogen headers are
+  considered system headers.
diff --git a/Help/release/dev/automoc-macro-names.rst b/Help/release/dev/automoc-macro-names.rst
new file mode 100644
index 0000000..9c037b3
--- /dev/null
+++ b/Help/release/dev/automoc-macro-names.rst
@@ -0,0 +1,5 @@
+automoc-macro-names
+-------------------
+
+* The :prop_tgt:`INTERFACE_AUTOMOC_MACRO_NAMES` target property was added to
+  specify macro names for ``moc`` as a transitive usage requirement.
diff --git a/Help/release/dev/cmake-debugger.rst b/Help/release/dev/cmake-debugger.rst
new file mode 100644
index 0000000..bfc4f6c
--- /dev/null
+++ b/Help/release/dev/cmake-debugger.rst
@@ -0,0 +1,5 @@
+cmake-debugger
+--------------
+
+* :manual:`cmake(1)` now supports interactive debugging of the CMake language.
+  See the :option:`--debugger <cmake --debugger>` option.
diff --git a/Help/release/dev/cmake-verbose-print-build-tool-command.rst b/Help/release/dev/cmake-verbose-print-build-tool-command.rst
new file mode 100644
index 0000000..4f13231
--- /dev/null
+++ b/Help/release/dev/cmake-verbose-print-build-tool-command.rst
@@ -0,0 +1,5 @@
+cmake-verbose-print-build-tool-command
+--------------------------------------
+
+* ``cmake --build $dir --verbose`` will now print the working directory and
+  command line used to perform the build.
diff --git a/Help/release/dev/cpack-innosetup.rst b/Help/release/dev/cpack-innosetup.rst
new file mode 100644
index 0000000..a9f8e8e
--- /dev/null
+++ b/Help/release/dev/cpack-innosetup.rst
@@ -0,0 +1,12 @@
+cpack-innosetup
+---------------
+
+* The :cpack_gen:`CPack Inno Setup Generator` was added to package using
+  Inno Setup.
+
+  The new generator adds:
+
+  * A lot of options to customize the Inno Setup installer (e.g. custom
+    installation rules)
+  * Start menu and desktop shortcuts
+  * Components (and also downloaded components)
diff --git a/Help/release/dev/cuda-support-new-compile-modes.rst b/Help/release/dev/cuda-support-new-compile-modes.rst
new file mode 100644
index 0000000..2d24c16
--- /dev/null
+++ b/Help/release/dev/cuda-support-new-compile-modes.rst
@@ -0,0 +1,14 @@
+cuda-support-new-compile-modes
+------------------------------
+
+* A :prop_tgt:`CUDA_CUBIN_COMPILATION` target property was added to
+  :ref:`Object Libraries` to support compiling to ``.cubin`` files
+  instead of host object files. Currently only supported with NVIDIA.
+
+* A :prop_tgt:`CUDA_FATBIN_COMPILATION` target property was added to
+  :ref:`Object Libraries` to support compiling to ``.fatbin`` files
+  instead of host object files. Currently only supported with NVIDIA.
+
+* A :prop_tgt:`CUDA_OPTIX_COMPILATION` target property was added to
+  :ref:`Object Libraries` to support compiling to ``.optixir`` files
+  instead of host object files. Currently only supported with NVIDIA.
diff --git a/Help/release/dev/cxx-module-extensions.rst b/Help/release/dev/cxx-module-extensions.rst
new file mode 100644
index 0000000..92df86a
--- /dev/null
+++ b/Help/release/dev/cxx-module-extensions.rst
@@ -0,0 +1,5 @@
+cxx-module-extensions
+---------------------
+
+* Source file extensions ``.ccm``, ``.cxxm``, or ``.c++m`` are now
+  treated as C++.
diff --git a/Help/release/dev/deprecate-extra-generators.rst b/Help/release/dev/deprecate-extra-generators.rst
new file mode 100644
index 0000000..ceb2f4e
--- /dev/null
+++ b/Help/release/dev/deprecate-extra-generators.rst
@@ -0,0 +1,5 @@
+deprecate-extra-generators
+--------------------------
+
+* The :ref:`Extra Generators` have been deprecated.  IDEs may use the
+  :manual:`cmake-file-api(7)` to view CMake-generated project build trees.
diff --git a/Help/release/dev/deprecate-policy-old.rst b/Help/release/dev/deprecate-policy-old.rst
new file mode 100644
index 0000000..9c38866
--- /dev/null
+++ b/Help/release/dev/deprecate-policy-old.rst
@@ -0,0 +1,7 @@
+deprecate-policy-old
+--------------------
+
+* Compatibility with versions of CMake older than 3.5 is now deprecated
+  and will be removed from a future version.  Calls to
+  :command:`cmake_minimum_required` or :command:`cmake_policy` that set
+  the policy version to an older value now issue a deprecation diagnostic.
diff --git a/Help/release/dev/dll-name-soversion.rst b/Help/release/dev/dll-name-soversion.rst
new file mode 100644
index 0000000..56d0842
--- /dev/null
+++ b/Help/release/dev/dll-name-soversion.rst
@@ -0,0 +1,7 @@
+dll-name-soversion
+------------------
+
+* The :variable:`CMAKE_DLL_NAME_WITH_SOVERSION` variable and associated
+  :prop_tgt:`DLL_NAME_WITH_SOVERSION` target property were added to
+  optionally append the :prop_tgt:`SOVERSION` to the filename of the
+  ``.dll`` part of a shared library on Windows.
diff --git a/Help/release/dev/ep-update-disconnected.rst b/Help/release/dev/ep-update-disconnected.rst
new file mode 100644
index 0000000..a162698
--- /dev/null
+++ b/Help/release/dev/ep-update-disconnected.rst
@@ -0,0 +1,14 @@
+ep-update-disconnected
+----------------------
+
+* The ``update`` and ``patch`` steps of an :module:`ExternalProject` will now
+  always re-execute if any of their details change, even if
+  ``UPDATE_DISCONNECTED`` was set to true in the call to
+  :command:`ExternalProject_Add`. If using the GIT download method and the
+  ``GIT_TAG`` is changed and the new ``GIT_TAG`` isn't already known locally,
+  this is now a fatal error instead of silently using the previous ``GIT_TAG``.
+
+* When ``UPDATE_DISCONNECTED`` is set to true in a call to
+  :command:`ExternalProject_Add`, the ``configure`` step will no longer
+  re-run on every build. It will only re-run if details of the ``download``,
+  ``update`` or ``patch`` step change.
diff --git a/Help/release/dev/file-GET_RUNTIME_DEPENDENCIES-windows-casing.rst b/Help/release/dev/file-GET_RUNTIME_DEPENDENCIES-windows-casing.rst
new file mode 100644
index 0000000..858f8b3
--- /dev/null
+++ b/Help/release/dev/file-GET_RUNTIME_DEPENDENCIES-windows-casing.rst
@@ -0,0 +1,7 @@
+file-GET_RUNTIME_DEPENDENCIES-windows-casing
+--------------------------------------------
+
+
+* The :command:`file(GET_RUNTIME_DEPENDENCIES)` command now case-preserves
+  DLL names reported on Windows.  They are still converted to lowercase
+  for filter matching.
diff --git a/Help/release/dev/find_package-PACKAGENAME_ROOT.rst b/Help/release/dev/find_package-PACKAGENAME_ROOT.rst
new file mode 100644
index 0000000..0388271
--- /dev/null
+++ b/Help/release/dev/find_package-PACKAGENAME_ROOT.rst
@@ -0,0 +1,7 @@
+find_package-PACKAGENAME_ROOT
+-----------------------------
+
+* The :command:`find_package` command now searches prefixes specified by
+  upper-case :variable:`<PACKAGENAME>_ROOT` CMake variables and upper-case
+  :envvar:`<PACKAGENAME>_ROOT` environment variables.
+  See policy :policy:`CMP0144`.
diff --git a/Help/release/dev/genex-compile-only.rst b/Help/release/dev/genex-compile-only.rst
new file mode 100644
index 0000000..1f898d8
--- /dev/null
+++ b/Help/release/dev/genex-compile-only.rst
@@ -0,0 +1,5 @@
+genex-compile-only
+------------------
+
+* The :genex:`COMPILE_ONLY` generator expression has been added which provides
+  compilation usage requirements without any linking requirements.
diff --git a/Help/release/dev/install-prefix-genex-install-code-script.rst b/Help/release/dev/install-prefix-genex-install-code-script.rst
new file mode 100644
index 0000000..810f448
--- /dev/null
+++ b/Help/release/dev/install-prefix-genex-install-code-script.rst
@@ -0,0 +1,5 @@
+install-prefix-genex-install-code-script
+----------------------------------------
+
+* The :command:`install(CODE)` and :command:`install(SCRIPT)` commands
+  now support the :genex:`$<INSTALL_PREFIX>` generator expression.
diff --git a/Help/release/dev/lang-linker-launcher-genex.rst b/Help/release/dev/lang-linker-launcher-genex.rst
new file mode 100644
index 0000000..b6494eb
--- /dev/null
+++ b/Help/release/dev/lang-linker-launcher-genex.rst
@@ -0,0 +1,5 @@
+lang-linker-launcher-genex
+--------------------------
+
+* The :prop_tgt:`<LANG>_LINKER_LAUNCHER` target property now supports
+  :manual:`generator expressions <cmake-generator-expressions(7)>`.
diff --git a/Help/release/dev/lint-genex.rst b/Help/release/dev/lint-genex.rst
new file mode 100644
index 0000000..8da30b0
--- /dev/null
+++ b/Help/release/dev/lint-genex.rst
@@ -0,0 +1,7 @@
+lint-genex
+----------
+
+* The :prop_tgt:`<LANG>_CLANG_TIDY`, :prop_tgt:`<LANG>_CPPCHECK`,
+  :prop_tgt:`<LANG>_CPPLINT`, and :prop_tgt:`<LANG>_INCLUDE_WHAT_YOU_USE`,
+  target properties now support
+  :manual:`generator expressions <cmake-generator-expressions(7)>`.
diff --git a/Help/release/dev/ninja-custom-command-depends.rst b/Help/release/dev/ninja-custom-command-depends.rst
new file mode 100644
index 0000000..0b7840c
--- /dev/null
+++ b/Help/release/dev/ninja-custom-command-depends.rst
@@ -0,0 +1,11 @@
+ninja-custom-command-depends
+----------------------------
+
+* The :command:`add_custom_command` command gained a new
+  ``DEPENDS_EXPLICIT_ONLY`` option to tell the :ref:`Ninja Generators`
+  not to add any dependencies implied by the target to which it is
+  attached.
+
+* The :variable:`CMAKE_ADD_CUSTOM_COMMAND_DEPENDS_EXPLICIT_ONLY` variable can
+  be set to enable ``DEPENDS_EXPLICIT_ONLY`` in all uses of
+  :command:`add_custom_command` command.
diff --git a/Help/release/dev/preset-includes-macro-expansion.rst b/Help/release/dev/preset-includes-macro-expansion.rst
new file mode 100644
index 0000000..e1f0030
--- /dev/null
+++ b/Help/release/dev/preset-includes-macro-expansion.rst
@@ -0,0 +1,7 @@
+preset-includes-macro-expansion
+-------------------------------
+
+* :manual:`cmake-presets(7)` files now support schema version ``7``.
+
+* :manual:`cmake-presets(7)` now supports ``$penv{}`` macro expansion
+  in ``include`` fields.
diff --git a/Help/release/dev/remove-FindPythonInterp-FindPythonLibs.rst b/Help/release/dev/remove-FindPythonInterp-FindPythonLibs.rst
new file mode 100644
index 0000000..426febb
--- /dev/null
+++ b/Help/release/dev/remove-FindPythonInterp-FindPythonLibs.rst
@@ -0,0 +1,6 @@
+remove-FindPythonInterp-FindPythonLibs
+--------------------------------------
+
+* The :module:`FindPythonInterp` and :module:`FindPythonLibs` modules have
+  been fully deprecated via policy :policy:`CMP0148`.  Port projects to
+  :module:`FindPython3`, :module:`FindPython2`, or :module:`FindPython`.
diff --git a/Help/release/dev/remove-dart-modules.rst b/Help/release/dev/remove-dart-modules.rst
new file mode 100644
index 0000000..5da2eda
--- /dev/null
+++ b/Help/release/dev/remove-dart-modules.rst
@@ -0,0 +1,5 @@
+remove-dart-modules
+-------------------
+
+* The :module:`Dart` and :module:`FindDart` modules have been deprecated via
+  policy :policy:`CMP0145`.  Port projects to the :module:`CTest` module.
diff --git a/Help/release/dev/skip-linting.rst b/Help/release/dev/skip-linting.rst
new file mode 100644
index 0000000..199571c
--- /dev/null
+++ b/Help/release/dev/skip-linting.rst
@@ -0,0 +1,5 @@
+skip-linting
+------------
+
+* The :prop_sf:`SKIP_LINTING` source file property was added to suppress
+  target-wide code checks on specific sources.
diff --git a/Help/release/dev/use-linker-depfile.rst b/Help/release/dev/use-linker-depfile.rst
new file mode 100644
index 0000000..1123707
--- /dev/null
+++ b/Help/release/dev/use-linker-depfile.rst
@@ -0,0 +1,11 @@
+use-linker-depfile
+------------------
+
+* GNU (and GNU-compatible) linkers gained support for a ``--dependency-file``
+  flag in GNU Binutils 2.35 and LLVM's LLD 12.0.0. The
+  :ref:`Makefile <Makefile Generators>` and :ref:`Ninja <Ninja Generators>`
+  generators will now add these flags so that files read by the linker will
+  cause a relink if they change (typically modified timestamps).
+
+  This feature can be controlled by the variable
+  :variable:`CMAKE_LINK_DEPENDS_USE_LINKER`.
diff --git a/Help/release/dev/vs-BuildInParallel.rst b/Help/release/dev/vs-BuildInParallel.rst
new file mode 100644
index 0000000..ef344c7
--- /dev/null
+++ b/Help/release/dev/vs-BuildInParallel.rst
@@ -0,0 +1,5 @@
+vs-BuildInParallel
+------------------
+
+* :ref:`Visual Studio Generators`, for VS 15.8 (2017) and newer, now
+  build custom commands in parallel.  See policy :policy:`CMP0147`.
diff --git a/Help/release/dev/vs-debugger-init.rst b/Help/release/dev/vs-debugger-init.rst
new file mode 100644
index 0000000..aa86839
--- /dev/null
+++ b/Help/release/dev/vs-debugger-init.rst
@@ -0,0 +1,8 @@
+vs-debugger-init
+----------------
+
+* Variables :variable:`CMAKE_VS_DEBUGGER_COMMAND`,
+  :variable:`CMAKE_VS_DEBUGGER_COMMAND_ARGUMENTS`,
+  :variable:`CMAKE_VS_DEBUGGER_ENVIRONMENT`, and
+  :variable:`CMAKE_VS_DEBUGGER_WORKING_DIRECTORY` were added to initialize
+  corresponding target properties.
diff --git a/Help/release/dev/vs-sdk-selection.rst b/Help/release/dev/vs-sdk-selection.rst
new file mode 100644
index 0000000..856a203
--- /dev/null
+++ b/Help/release/dev/vs-sdk-selection.rst
@@ -0,0 +1,7 @@
+vs-sdk-selection
+----------------
+
+* The :ref:`Visual Studio Generators` for VS 2015 and above learned to
+  select the Windows SDK version explicitly using a ``version=`` field
+  in the :variable:`CMAKE_GENERATOR_PLATFORM` variable.
+  See :ref:`Visual Studio Platform Selection`.
diff --git a/Help/release/dev/vs-windows-min-version.rst b/Help/release/dev/vs-windows-min-version.rst
new file mode 100644
index 0000000..cb39159
--- /dev/null
+++ b/Help/release/dev/vs-windows-min-version.rst
@@ -0,0 +1,6 @@
+vs-windows-min-version
+----------------------
+
+* The :variable:`CMAKE_VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION` variable
+  was added to initialize the :prop_tgt:`VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION`
+  target property on all targets when they are created.
diff --git a/Help/release/dev/vs9-deprecate.rst b/Help/release/dev/vs9-deprecate.rst
new file mode 100644
index 0000000..46568f8
--- /dev/null
+++ b/Help/release/dev/vs9-deprecate.rst
@@ -0,0 +1,5 @@
+vs9-deprecate
+-------------
+
+* The :generator:`Visual Studio 9 2008` generator is now deprecated
+  and will be removed in a future version of CMake.
diff --git a/Help/release/index.rst b/Help/release/index.rst
index c82889f..d434a3a 100644
--- a/Help/release/index.rst
+++ b/Help/release/index.rst
@@ -7,6 +7,8 @@
   This file should include the adjacent "dev.txt" file
   in development versions but not in release versions.
 
+.. include:: dev.txt
+
 Releases
 ========
 
diff --git a/Help/variable/CMAKE_ADD_CUSTOM_COMMAND_DEPENDS_EXPLICIT_ONLY.rst b/Help/variable/CMAKE_ADD_CUSTOM_COMMAND_DEPENDS_EXPLICIT_ONLY.rst
new file mode 100644
index 0000000..9c9bd2c
--- /dev/null
+++ b/Help/variable/CMAKE_ADD_CUSTOM_COMMAND_DEPENDS_EXPLICIT_ONLY.rst
@@ -0,0 +1,11 @@
+CMAKE_ADD_CUSTOM_COMMAND_DEPENDS_EXPLICIT_ONLY
+----------------------------------------------
+
+.. versionadded:: 3.27
+
+Whether to enable DEPENDS_EXPLICIT_ONLY option by default in
+:command:`add_custom_command`.
+
+This variable affects the default behavior of the :command:`add_custom_command`
+command.  Setting this variable to ``ON`` is equivalent to using the ``DEPENDS_EXPLICIT_ONLY``
+option in all uses of that command.
diff --git a/Help/variable/CMAKE_APPBUNDLE_PATH.rst b/Help/variable/CMAKE_APPBUNDLE_PATH.rst
index 1c7ca51..441ee8e 100644
--- a/Help/variable/CMAKE_APPBUNDLE_PATH.rst
+++ b/Help/variable/CMAKE_APPBUNDLE_PATH.rst
@@ -4,3 +4,6 @@
 :ref:`Semicolon-separated list <CMake Language Lists>` of directories specifying a search path
 for macOS application bundles used by the :command:`find_program`, and
 :command:`find_package` commands.
+
+There is also an environment variable :envvar:`CMAKE_APPBUNDLE_PATH`, which is used
+as an additional list of search directories.
diff --git a/Help/variable/CMAKE_AUTOGEN_USE_SYSTEM_INCLUDE.rst b/Help/variable/CMAKE_AUTOGEN_USE_SYSTEM_INCLUDE.rst
new file mode 100644
index 0000000..80ed847
--- /dev/null
+++ b/Help/variable/CMAKE_AUTOGEN_USE_SYSTEM_INCLUDE.rst
@@ -0,0 +1,10 @@
+CMAKE_AUTOGEN_USE_SYSTEM_INCLUDE
+--------------------------------
+
+.. versionadded:: 3.27
+
+This variable is used to initialize the :prop_tgt:`AUTOGEN_USE_SYSTEM_INCLUDE`
+property on all targets as they are created.  See that target property for
+additional information.
+
+By default ``CMAKE_AUTOGEN_USE_SYSTEM_INCLUDE`` is unset.
diff --git a/Help/variable/CMAKE_AUTOMOC_EXECUTABLE.rst b/Help/variable/CMAKE_AUTOMOC_EXECUTABLE.rst
new file mode 100644
index 0000000..150a73a
--- /dev/null
+++ b/Help/variable/CMAKE_AUTOMOC_EXECUTABLE.rst
@@ -0,0 +1,10 @@
+CMAKE_AUTOMOC_EXECUTABLE
+------------------------
+
+.. versionadded:: 3.27
+
+This variable is used to initialize the :prop_tgt:`AUTOMOC_EXECUTABLE`
+property on all the targets. See that target property for additional
+information.
+
+By default it is empty.
diff --git a/Help/variable/CMAKE_AUTORCC_EXECUTABLE.rst b/Help/variable/CMAKE_AUTORCC_EXECUTABLE.rst
new file mode 100644
index 0000000..52d7faa
--- /dev/null
+++ b/Help/variable/CMAKE_AUTORCC_EXECUTABLE.rst
@@ -0,0 +1,10 @@
+CMAKE_AUTORCC_EXECUTABLE
+------------------------
+
+.. versionadded:: 3.27
+
+This variable is used to initialize the :prop_tgt:`AUTORCC_EXECUTABLE`
+property on all the targets. See that target property for additional
+information.
+
+By default it is empty.
diff --git a/Help/variable/CMAKE_AUTOUIC_EXECUTABLE.rst b/Help/variable/CMAKE_AUTOUIC_EXECUTABLE.rst
new file mode 100644
index 0000000..b2ebd7f
--- /dev/null
+++ b/Help/variable/CMAKE_AUTOUIC_EXECUTABLE.rst
@@ -0,0 +1,10 @@
+CMAKE_AUTOUIC_EXECUTABLE
+------------------------
+
+.. versionadded:: 3.27
+
+This variable is used to initialize the :prop_tgt:`AUTOUIC_EXECUTABLE`
+property on all the targets. See that target property for additional
+information.
+
+By default it is empty.
diff --git a/Help/variable/CMAKE_CROSSCOMPILING.rst b/Help/variable/CMAKE_CROSSCOMPILING.rst
index 7e6ec33..16dbfa5 100644
--- a/Help/variable/CMAKE_CROSSCOMPILING.rst
+++ b/Help/variable/CMAKE_CROSSCOMPILING.rst
@@ -1,15 +1,15 @@
 CMAKE_CROSSCOMPILING
 --------------------
 
-Intended to indicate whether CMake is cross compiling, but note limitations
-discussed below.
+This variable is set by CMake to indicate whether it is cross compiling,
+but note limitations discussed below.
 
 This variable will be set to true by CMake if the :variable:`CMAKE_SYSTEM_NAME`
 variable has been set manually (i.e. in a toolchain file or as a cache entry
 from the :manual:`cmake <cmake(1)>` command line). In most cases, manually
-setting :variable:`CMAKE_SYSTEM_NAME` will only be done when cross compiling,
-since it will otherwise be given the same value as
-:variable:`CMAKE_HOST_SYSTEM_NAME` if not manually set, which is correct for
+setting :variable:`CMAKE_SYSTEM_NAME` will only be done when cross compiling
+since, if not manually set, it will be given the same value as
+:variable:`CMAKE_HOST_SYSTEM_NAME`, which is correct for
 the non-cross-compiling case. In the event that :variable:`CMAKE_SYSTEM_NAME`
 is manually set to the same value as :variable:`CMAKE_HOST_SYSTEM_NAME`, then
 ``CMAKE_CROSSCOMPILING`` will still be set to true.
diff --git a/Help/variable/CMAKE_DLL_NAME_WITH_SOVERSION.rst b/Help/variable/CMAKE_DLL_NAME_WITH_SOVERSION.rst
new file mode 100644
index 0000000..5fa49de
--- /dev/null
+++ b/Help/variable/CMAKE_DLL_NAME_WITH_SOVERSION.rst
@@ -0,0 +1,14 @@
+CMAKE_DLL_NAME_WITH_SOVERSION
+-----------------------------
+
+.. versionadded:: 3.27
+
+This variable is used to initialize the :prop_tgt:`DLL_NAME_WITH_SOVERSION`
+property on shared library targets for the Windows platform, which is selected
+when the :variable:`WIN32` variable is set.
+
+See this target property for additional information.
+
+Please note that setting this variable has no effect if versioned filenames
+are globally disabled with the :variable:`CMAKE_PLATFORM_NO_VERSIONED_SONAME`
+variable.
diff --git a/Help/variable/CMAKE_EDIT_COMMAND.rst b/Help/variable/CMAKE_EDIT_COMMAND.rst
index 2f4ab1f..b21434f 100644
--- a/Help/variable/CMAKE_EDIT_COMMAND.rst
+++ b/Help/variable/CMAKE_EDIT_COMMAND.rst
@@ -2,7 +2,8 @@
 ------------------
 
 Full path to :manual:`cmake-gui(1)` or :manual:`ccmake(1)`.  Defined only for
-:ref:`Makefile Generators` when not using an "extra" generator for an IDE.
+:ref:`Makefile Generators` and :ref:`Ninja Generators` when not using any
+:ref:`Extra Generators`.
 
 This is the full path to the CMake executable that can graphically
 edit the cache.  For example, :manual:`cmake-gui(1)` or :manual:`ccmake(1)`.
diff --git a/Help/variable/CMAKE_ENABLE_EXPORTS.rst b/Help/variable/CMAKE_ENABLE_EXPORTS.rst
index 9f43de3..d2c5ed0 100644
--- a/Help/variable/CMAKE_ENABLE_EXPORTS.rst
+++ b/Help/variable/CMAKE_ENABLE_EXPORTS.rst
@@ -8,3 +8,7 @@
 This variable is used to initialize the :prop_tgt:`ENABLE_EXPORTS` target
 property for executable targets when they are created by calls to the
 :command:`add_executable` command.  See the property documentation for details.
+
+This command has been superseded by the
+:variable:`CMAKE_EXECUTABLE_ENABLE_EXPORTS` command.  It is provided for
+compatibility with older CMake code.
diff --git a/Help/variable/CMAKE_EXECUTABLE_ENABLE_EXPORTS.rst b/Help/variable/CMAKE_EXECUTABLE_ENABLE_EXPORTS.rst
new file mode 100644
index 0000000..aa6dda2
--- /dev/null
+++ b/Help/variable/CMAKE_EXECUTABLE_ENABLE_EXPORTS.rst
@@ -0,0 +1,12 @@
+CMAKE_EXECUTABLE_ENABLE_EXPORTS
+-------------------------------
+
+.. versionadded:: 3.27
+
+Specify whether executables export symbols for loadable modules.
+
+This variable is used to initialize the :prop_tgt:`ENABLE_EXPORTS` target
+property for executable targets when they are created by calls to the
+:command:`add_executable` command.  See the property documentation for details.
+
+This variable supersede the :variable:`CMAKE_ENABLE_EXPORTS` variable.
diff --git a/Help/variable/CMAKE_EXTRA_GENERATOR.rst b/Help/variable/CMAKE_EXTRA_GENERATOR.rst
index 2c92323..0a113a5 100644
--- a/Help/variable/CMAKE_EXTRA_GENERATOR.rst
+++ b/Help/variable/CMAKE_EXTRA_GENERATOR.rst
@@ -1,6 +1,12 @@
 CMAKE_EXTRA_GENERATOR
 ---------------------
 
+.. deprecated:: 3.27
+
+  Support for :ref:`Extra Generators` is deprecated and will be removed from
+  a future version of CMake.  IDEs may use the :manual:`cmake-file-api(7)`
+  to view CMake-generated project build trees.
+
 The extra generator used to build the project.  See
 :manual:`cmake-generators(7)`.
 
diff --git a/Help/variable/CMAKE_FRAMEWORK_PATH.rst b/Help/variable/CMAKE_FRAMEWORK_PATH.rst
index 13ade4e..8d62b02 100644
--- a/Help/variable/CMAKE_FRAMEWORK_PATH.rst
+++ b/Help/variable/CMAKE_FRAMEWORK_PATH.rst
@@ -5,3 +5,6 @@
 for macOS frameworks used by the :command:`find_library`,
 :command:`find_package`, :command:`find_path`, and :command:`find_file`
 commands.
+
+There is also an environment variable :envvar:`CMAKE_FRAMEWORK_PATH`, which is used
+as an additional list of search directories.
diff --git a/Help/variable/CMAKE_GENERATOR_PLATFORM.rst b/Help/variable/CMAKE_GENERATOR_PLATFORM.rst
index acb7b2e..416ff60 100644
--- a/Help/variable/CMAKE_GENERATOR_PLATFORM.rst
+++ b/Help/variable/CMAKE_GENERATOR_PLATFORM.rst
@@ -26,8 +26,46 @@
 
 See native build system documentation for allowed platform names.
 
+.. _`Visual Studio Platform Selection`:
+
 Visual Studio Platform Selection
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-On :ref:`Visual Studio Generators` the selected platform name
-is provided in the :variable:`CMAKE_VS_PLATFORM_NAME` variable.
+The :ref:`Visual Studio Generators` support platform specification
+using one of these forms:
+
+* ``platform``
+* ``platform[,key=value]*``
+* ``key=value[,key=value]*``
+
+The ``platform`` specifies the target platform (VS target architecture),
+such as ``x64``, ``ARM64``, or ``Win32``.  The selected platform
+name is provided in the :variable:`CMAKE_VS_PLATFORM_NAME` variable.
+
+The ``key=value`` pairs form a comma-separated list of options to
+specify generator-specific details of the platform selection.
+Supported pairs are:
+
+``version=<version>``
+  .. versionadded:: 3.27
+
+  Specify the Windows SDK version to use.  This is supported by VS 2015 and
+  above when targeting Windows 10.0+ or Windows Store.  CMake will set the
+  :variable:`CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION` variable to the
+  selected SDK version.
+
+  The ``<version>`` may be one of:
+
+  ``10.0``
+    Specify that any 10.0 SDK version may be used, and let Visual Studio
+    pick one.  This is supported by VS 2019 and above.
+
+  ``10.0.<build>.<increment>``
+    Specify the exact 4-component SDK version, e.g., ``10.0.19041.0``.
+    The specified version of the SDK must be installed.  It may not exceed
+    the value of :variable:`CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION_MAXIMUM`,
+    if that variable is set.
+
+  If the ``version`` field is not specified, CMake selects a version as
+  described in the :variable:`CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION`
+  variable documentation.
diff --git a/Help/variable/CMAKE_INCLUDE_PATH.rst b/Help/variable/CMAKE_INCLUDE_PATH.rst
index 4918e99..3a4472a 100644
--- a/Help/variable/CMAKE_INCLUDE_PATH.rst
+++ b/Help/variable/CMAKE_INCLUDE_PATH.rst
@@ -3,5 +3,10 @@
 
 :ref:`Semicolon-separated list <CMake Language Lists>` of directories specifying a search path
 for the :command:`find_file` and :command:`find_path` commands.  By default it
-is empty, it is intended to be set by the project.  See also
-:variable:`CMAKE_SYSTEM_INCLUDE_PATH` and :variable:`CMAKE_PREFIX_PATH`.
+is empty, it is intended to be set by the project.
+
+
+There is also an environment variable :envvar:`CMAKE_INCLUDE_PATH`, which is used
+as an additional list of search directories.
+
+See also :variable:`CMAKE_SYSTEM_INCLUDE_PATH` and :variable:`CMAKE_PREFIX_PATH`.
diff --git a/Help/variable/CMAKE_KATE_FILES_MODE.rst b/Help/variable/CMAKE_KATE_FILES_MODE.rst
new file mode 100644
index 0000000..195c15d
--- /dev/null
+++ b/Help/variable/CMAKE_KATE_FILES_MODE.rst
@@ -0,0 +1,20 @@
+CMAKE_KATE_FILES_MODE
+---------------------
+
+.. versionadded:: 3.27
+
+This cache variable is used by the Kate project generator and controls
+to what mode the ``files`` entry in the project file will be set.  See
+:manual:`cmake-generators(7)`.
+
+Possible values are ``AUTO``, ``SVN``, ``GIT``, ``HG``, ``FOSSIL`` and ``LIST``.
+
+When set to ``LIST``, CMake will put the list of source files known to CMake
+in the project file.
+When set to ``SVN``, ``GIT``, ``HG`` or ``FOSSIL``, CMake will set
+the generated project accordingly to Subversion, git, Mercurial
+or Fossil, and Kate will then use the respective command line tool to
+retrieve the list of files in the project.
+When unset or set to ``AUTO``, CMake will try to detect whether the
+source directory is part of a git or svn checkout or not, and put the
+respective entry into the project file.
diff --git a/Help/variable/CMAKE_KATE_MAKE_ARGUMENTS.rst b/Help/variable/CMAKE_KATE_MAKE_ARGUMENTS.rst
new file mode 100644
index 0000000..c830332
--- /dev/null
+++ b/Help/variable/CMAKE_KATE_MAKE_ARGUMENTS.rst
@@ -0,0 +1,11 @@
+CMAKE_KATE_MAKE_ARGUMENTS
+-------------------------
+
+.. versionadded:: 3.0
+
+This cache variable is used by the Kate project generator.  See
+:manual:`cmake-generators(7)`.
+
+This variable holds arguments which are used when Kate invokes the make
+tool. By default it is initialized to hold flags to enable parallel builds
+(using -j typically).
diff --git a/Help/variable/CMAKE_LANG_COMPILER_ID.rst b/Help/variable/CMAKE_LANG_COMPILER_ID.rst
index 3b27fc3..5eb86c6 100644
--- a/Help/variable/CMAKE_LANG_COMPILER_ID.rst
+++ b/Help/variable/CMAKE_LANG_COMPILER_ID.rst
@@ -9,7 +9,7 @@
 =============================== ===============================================
 Value                           Name
 =============================== ===============================================
-``Absoft``                      `Absoft Fortran`_
+``Absoft``                      Absoft Fortran
 ``ADSP``                        Analog VisualDSP++
 ``AppleClang``                  Apple Clang
 ``ARMCC``                       ARM Compiler
@@ -50,7 +50,6 @@
 This variable is not guaranteed to be defined for all compilers or
 languages.
 
-.. _Absoft Fortran: https://www.absoft.com
 .. _LLVM Clang: https://clang.llvm.org
 .. _Embarcadero: https://www.embarcadero.com
 .. _Classic Flang Fortran Compiler: https://github.com/flang-compiler/flang
diff --git a/Help/variable/CMAKE_LANG_FLAGS.rst b/Help/variable/CMAKE_LANG_FLAGS.rst
index 4b39b1d..909a001 100644
--- a/Help/variable/CMAKE_LANG_FLAGS.rst
+++ b/Help/variable/CMAKE_LANG_FLAGS.rst
@@ -1,9 +1,10 @@
 CMAKE_<LANG>_FLAGS
 ------------------
 
-Flags for all build types.
-
-``<LANG>`` flags used regardless of the value of :variable:`CMAKE_BUILD_TYPE`.
+Language-wide flags for language ``<LANG>`` used when building for
+all configurations.  These flags will be passed to all invocations
+of the compiler.  This includes invocations that drive compiling
+and those that drive linking.
 
 For each language, if this variable is not defined, it is initialized
 and stored in the cache using values from environment variables in
@@ -27,7 +28,10 @@
 This value is a command-line string fragment. Therefore, multiple options
 should be separated by spaces, and options with spaces should be quoted.
 
-The flags in this variable will be passed to the compiler before those
-in the per-configuration :variable:`CMAKE_<LANG>_FLAGS_<CONFIG>` variant,
-and before flags added by the :command:`add_compile_options` or
-:command:`target_compile_options` commands.
+The flags in this variable will be passed before those in the
+per-configuration :variable:`CMAKE_<LANG>_FLAGS_<CONFIG>` variable.
+On invocations driving compiling, flags from both variables will be passed
+before flags added by commands such as :command:`add_compile_options` and
+:command:`target_compile_options`. On invocations driving linking,
+they will be passed before flags added by commands such as
+:command:`add_link_options` and :command:`target_link_options`.
diff --git a/Help/variable/CMAKE_LANG_FLAGS_CONFIG.rst b/Help/variable/CMAKE_LANG_FLAGS_CONFIG.rst
index f0900fd..5daa4c0 100644
--- a/Help/variable/CMAKE_LANG_FLAGS_CONFIG.rst
+++ b/Help/variable/CMAKE_LANG_FLAGS_CONFIG.rst
@@ -1,9 +1,16 @@
 CMAKE_<LANG>_FLAGS_<CONFIG>
 ---------------------------
 
-Flags for language ``<LANG>`` when building for the ``<CONFIG>`` configuration.
+Language-wide flags for language ``<LANG>`` used when building for
+the ``<CONFIG>`` configuration.  These flags will be passed to all
+invocations of the compiler in the corresponding configuration.
+This includes invocations that drive compiling and those that drive
+linking.
 
-The flags in this variable will be passed to the compiler after those
-in the :variable:`CMAKE_<LANG>_FLAGS` variable, but before flags added
-by the :command:`add_compile_options` or :command:`target_compile_options`
-commands.
+The flags in this variable will be passed after those in the
+:variable:`CMAKE_<LANG>_FLAGS` variable.  On invocations driving compiling,
+flags from both variables will be passed before flags added by commands
+such as :command:`add_compile_options` and :command:`target_compile_options`.
+On invocations driving linking, they will be passed before flags added by
+commands such as :command:`add_link_options` and
+:command:`target_link_options`.
diff --git a/Help/variable/CMAKE_LANG_IMPLICIT_LINK_DIRECTORIES.rst b/Help/variable/CMAKE_LANG_IMPLICIT_LINK_DIRECTORIES.rst
index 081c4da..7e008df 100644
--- a/Help/variable/CMAKE_LANG_IMPLICIT_LINK_DIRECTORIES.rst
+++ b/Help/variable/CMAKE_LANG_IMPLICIT_LINK_DIRECTORIES.rst
@@ -6,9 +6,14 @@
 Compilers typically pass directories containing language runtime
 libraries and default library search paths when they invoke a linker.
 These paths are implicit linker search directories for the compiler's
-language.  For each language enabled by the :command:`project` or
+language.
+
+For each language enabled by the :command:`project` or
 :command:`enable_language` command, CMake automatically detects these
 directories and reports the results in this variable.
+The :envvar:`CMAKE_<LANG>_IMPLICIT_LINK_DIRECTORIES_EXCLUDE` environment
+variable may be set to exclude specific directories from the automatically
+detected results.
 
 When linking to a static library, CMake adds the implicit link directories
 from this variable for each language used in the static library (except
diff --git a/Help/variable/CMAKE_LIBRARY_PATH.rst b/Help/variable/CMAKE_LIBRARY_PATH.rst
index 8135b65..4265982 100644
--- a/Help/variable/CMAKE_LIBRARY_PATH.rst
+++ b/Help/variable/CMAKE_LIBRARY_PATH.rst
@@ -3,5 +3,9 @@
 
 :ref:`Semicolon-separated list <CMake Language Lists>` of directories specifying a search path
 for the :command:`find_library` command.  By default it is empty, it is
-intended to be set by the project.  See also
-:variable:`CMAKE_SYSTEM_LIBRARY_PATH` and :variable:`CMAKE_PREFIX_PATH`.
+intended to be set by the project.
+
+There is also an environment variable :envvar:`CMAKE_LIBRARY_PATH`, which is used
+as an additional list of search directories.
+
+See also :variable:`CMAKE_SYSTEM_LIBRARY_PATH` and :variable:`CMAKE_PREFIX_PATH`.
diff --git a/Help/variable/CMAKE_LINK_DEPENDS_USE_LINKER.rst b/Help/variable/CMAKE_LINK_DEPENDS_USE_LINKER.rst
new file mode 100644
index 0000000..e1b37a5
--- /dev/null
+++ b/Help/variable/CMAKE_LINK_DEPENDS_USE_LINKER.rst
@@ -0,0 +1,12 @@
+CMAKE_LINK_DEPENDS_USE_LINKER
+-----------------------------
+
+.. versionadded:: 3.27
+
+For the :ref:`Makefile <Makefile Generators>` and
+:ref:`Ninja <Ninja Generators>` generators, link dependencies are now, for a
+selection of linkers, generated by the linker itself. By defining this
+variable with value ``FALSE``, you can deactivate this feature.
+
+This feature is also deactivated if the :prop_tgt:`LINK_DEPENDS_NO_SHARED`
+target property is true.
diff --git a/Help/variable/CMAKE_MAXIMUM_RECURSION_DEPTH.rst b/Help/variable/CMAKE_MAXIMUM_RECURSION_DEPTH.rst
index 59c60d3..b611967 100644
--- a/Help/variable/CMAKE_MAXIMUM_RECURSION_DEPTH.rst
+++ b/Help/variable/CMAKE_MAXIMUM_RECURSION_DEPTH.rst
@@ -33,3 +33,5 @@
   depth)
 * Reading or writing variables that are being watched by a
   :command:`variable_watch`
+
+See also the :envvar:`CMAKE_MAXIMUM_RECURSION_DEPTH` environment variable.
diff --git a/Help/variable/CMAKE_NINJA_OUTPUT_PATH_PREFIX.rst b/Help/variable/CMAKE_NINJA_OUTPUT_PATH_PREFIX.rst
index a8c4035..79a65b8 100644
--- a/Help/variable/CMAKE_NINJA_OUTPUT_PATH_PREFIX.rst
+++ b/Help/variable/CMAKE_NINJA_OUTPUT_PATH_PREFIX.rst
@@ -3,27 +3,38 @@
 
 .. versionadded:: 3.6
 
-Set output files path prefix for the :generator:`Ninja` generator.
+Tell the :ref:`Ninja Generators` to add a prefix to every output path in
+``build.ninja``.  A trailing slash is appended to the prefix, if missing.
 
-Every output files listed in the generated ``build.ninja`` will be
-prefixed by the contents of this variable (a trailing slash is
-appended if missing).  This is useful when the generated ninja file is
-meant to be embedded as a ``subninja`` file into a *super* ninja
-project.  For example, a ninja build file generated with a command
-like::
+This is useful when the generated ninja file is meant to be embedded as a
+``subninja`` file into a *super* ninja project.  For example, the command:
 
-  cd top-build-dir/sub &&
-  cmake -G Ninja -DCMAKE_NINJA_OUTPUT_PATH_PREFIX=sub/ path/to/source
+.. code-block:: shell
 
-can be embedded in ``top-build-dir/build.ninja`` with a directive like
-this::
+  cd super-build-dir &&
+  cmake -G Ninja -S /path/to/src -B sub -DCMAKE_NINJA_OUTPUT_PATH_PREFIX=sub/
+  #                                 ^^^---------- these match -----------^^^
+
+generates a build directory with its top-level (:variable:`CMAKE_BINARY_DIR`)
+in ``super-build-dir/sub``.  The path to the build directory ends in the
+output path prefix.  This makes it suitable for use in a separately-written
+``super-build-dir/build.ninja`` file with a directive like this::
 
   subninja sub/build.ninja
 
-The ``auto-regeneration`` rule in ``top-build-dir/build.ninja`` must have an
-order-only dependency on ``sub/build.ninja``.
+The ``auto-regeneration`` rule in ``super-build-dir/build.ninja`` must
+have an order-only dependency on ``sub/build.ninja``.
+
+.. versionadded:: 3.27
+
+  The :generator:`Ninja Multi-Config` generator supports this variable.
 
 .. note::
   When ``CMAKE_NINJA_OUTPUT_PATH_PREFIX`` is set, the project generated
   by CMake cannot be used as a standalone project.  No default targets
   are specified.
+
+  The value of ``CMAKE_NINJA_OUTPUT_PATH_PREFIX`` must match one or more
+  path components at the *end* of :variable:`CMAKE_BINARY_DIR`, or the
+  behavior is undefined.  However, this requirement is not checked
+  automatically.
diff --git a/Help/variable/CMAKE_PREFIX_PATH.rst b/Help/variable/CMAKE_PREFIX_PATH.rst
index 1d4fd0b..54f2aec 100644
--- a/Help/variable/CMAKE_PREFIX_PATH.rst
+++ b/Help/variable/CMAKE_PREFIX_PATH.rst
@@ -10,6 +10,9 @@
 
 By default this is empty.  It is intended to be set by the project.
 
+There is also an environment variable :envvar:`CMAKE_PREFIX_PATH`, which is used
+as an additional list of search prefixes.
+
 See also :variable:`CMAKE_SYSTEM_PREFIX_PATH`, :variable:`CMAKE_INCLUDE_PATH`,
 :variable:`CMAKE_LIBRARY_PATH`, :variable:`CMAKE_PROGRAM_PATH`, and
 :variable:`CMAKE_IGNORE_PATH`.
diff --git a/Help/variable/CMAKE_PROGRAM_PATH.rst b/Help/variable/CMAKE_PROGRAM_PATH.rst
index 2d0c090..240bacb 100644
--- a/Help/variable/CMAKE_PROGRAM_PATH.rst
+++ b/Help/variable/CMAKE_PROGRAM_PATH.rst
@@ -3,5 +3,9 @@
 
 :ref:`Semicolon-separated list <CMake Language Lists>` of directories specifying a search path
 for the :command:`find_program` command.  By default it is empty, it is
-intended to be set by the project.  See also
-:variable:`CMAKE_SYSTEM_PROGRAM_PATH` and :variable:`CMAKE_PREFIX_PATH`.
+intended to be set by the project.
+
+There is also an environment variable :envvar:`CMAKE_PROGRAM_PATH`, which is used
+as an additional list of search directories.
+
+See also :variable:`CMAKE_SYSTEM_PROGRAM_PATH` and :variable:`CMAKE_PREFIX_PATH`.
diff --git a/Help/variable/CMAKE_SHARED_LIBRARY_ENABLE_EXPORTS.rst b/Help/variable/CMAKE_SHARED_LIBRARY_ENABLE_EXPORTS.rst
new file mode 100644
index 0000000..3e2c6df
--- /dev/null
+++ b/Help/variable/CMAKE_SHARED_LIBRARY_ENABLE_EXPORTS.rst
@@ -0,0 +1,10 @@
+CMAKE_SHARED_LIBRARY_ENABLE_EXPORTS
+-----------------------------------
+
+.. versionadded:: 3.27
+
+Specify whether shared library generates an import file.
+
+This variable is used to initialize the :prop_tgt:`ENABLE_EXPORTS` target
+property for shared library targets when they are created by calls to the
+:command:`add_library` command.  See the property documentation for details.
diff --git a/Help/variable/CMAKE_VS_DEBUGGER_COMMAND.rst b/Help/variable/CMAKE_VS_DEBUGGER_COMMAND.rst
new file mode 100644
index 0000000..b2c03a1
--- /dev/null
+++ b/Help/variable/CMAKE_VS_DEBUGGER_COMMAND.rst
@@ -0,0 +1,8 @@
+CMAKE_VS_DEBUGGER_COMMAND
+-------------------------
+
+.. versionadded:: 3.27
+
+This variable is used to initialize the :prop_tgt:`VS_DEBUGGER_COMMAND`
+property on each target as it is created.  See that target property
+for additional information.
diff --git a/Help/variable/CMAKE_VS_DEBUGGER_COMMAND_ARGUMENTS.rst b/Help/variable/CMAKE_VS_DEBUGGER_COMMAND_ARGUMENTS.rst
new file mode 100644
index 0000000..482aa67
--- /dev/null
+++ b/Help/variable/CMAKE_VS_DEBUGGER_COMMAND_ARGUMENTS.rst
@@ -0,0 +1,8 @@
+CMAKE_VS_DEBUGGER_COMMAND_ARGUMENTS
+-----------------------------------
+
+.. versionadded:: 3.27
+
+This variable is used to initialize the :prop_tgt:`VS_DEBUGGER_COMMAND_ARGUMENTS`
+property on each target as it is created.  See that target property
+for additional information.
diff --git a/Help/variable/CMAKE_VS_DEBUGGER_ENVIRONMENT.rst b/Help/variable/CMAKE_VS_DEBUGGER_ENVIRONMENT.rst
new file mode 100644
index 0000000..245ac5d
--- /dev/null
+++ b/Help/variable/CMAKE_VS_DEBUGGER_ENVIRONMENT.rst
@@ -0,0 +1,8 @@
+CMAKE_VS_DEBUGGER_ENVIRONMENT
+-----------------------------
+
+.. versionadded:: 3.27
+
+This variable is used to initialize the :prop_tgt:`VS_DEBUGGER_ENVIRONMENT`
+property on each target as it is created.  See that target property
+for additional information.
diff --git a/Help/variable/CMAKE_VS_DEBUGGER_WORKING_DIRECTORY.rst b/Help/variable/CMAKE_VS_DEBUGGER_WORKING_DIRECTORY.rst
new file mode 100644
index 0000000..9100adb
--- /dev/null
+++ b/Help/variable/CMAKE_VS_DEBUGGER_WORKING_DIRECTORY.rst
@@ -0,0 +1,8 @@
+CMAKE_VS_DEBUGGER_WORKING_DIRECTORY
+-----------------------------------
+
+.. versionadded:: 3.27
+
+This variable is used to initialize the :prop_tgt:`VS_DEBUGGER_WORKING_DIRECTORY`
+property on each target as it is created.  See that target property
+for additional information.
diff --git a/Help/variable/CMAKE_VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION.rst b/Help/variable/CMAKE_VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION.rst
new file mode 100644
index 0000000..8ef54cd
--- /dev/null
+++ b/Help/variable/CMAKE_VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION.rst
@@ -0,0 +1,12 @@
+CMAKE_VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION
+--------------------------------------------
+
+.. versionadded:: 3.27
+
+Tell :ref:`Visual Studio Generators` to use the given
+Windows Target Platform Minimum Version.
+
+This variable is used to initialize the
+:prop_tgt:`VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION` property on all
+targets when they are created.  See that target property for
+additional information.
diff --git a/Help/variable/CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION.rst b/Help/variable/CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION.rst
index eb71049..2c14d39 100644
--- a/Help/variable/CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION.rst
+++ b/Help/variable/CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION.rst
@@ -5,11 +5,30 @@
 
 Visual Studio Windows Target Platform Version.
 
-When targeting Windows 10 and above Visual Studio 2015 and above support
-specification of a target Windows version to select a corresponding SDK.
-The :variable:`CMAKE_SYSTEM_VERSION` variable may be set to specify a
-version.  Otherwise CMake computes a default version based on the Windows
-SDK versions available.  The chosen Windows target version number is provided
+When targeting Windows 10 and above, :ref:`Visual Studio Generators` for
+VS 2015 and above support specification of a Windows SDK version:
+
+* If :variable:`CMAKE_GENERATOR_PLATFORM` specifies a ``version=`` field,
+  as documented by :ref:`Visual Studio Platform Selection`, that SDK
+  version is selected.
+
+* Otherwise, if the ``WindowsSDKVersion`` environment variable
+  is set to an available SDK version, that version is selected.
+  This is intended for use in environments established by ``vcvarsall.bat``
+  or similar scripts.
+
+  .. versionadded:: 3.27
+    This is enabled by policy :policy:`CMP0149`.
+
+* Otherwise, if :variable:`CMAKE_SYSTEM_VERSION` is set to an available
+  SDK version, that version is selected.
+
+  .. versionchanged:: 3.27
+    This is disabled by policy :policy:`CMP0149`.
+
+* Otherwise, CMake uses the latest Windows SDK version available.
+
+The chosen Windows target version number is provided
 in ``CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION``.  If no Windows 10 SDK
 is available this value will be empty.
 
diff --git a/Help/variable/CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION_MAXIMUM.rst b/Help/variable/CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION_MAXIMUM.rst
index f1a1977..727ccc9 100644
--- a/Help/variable/CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION_MAXIMUM.rst
+++ b/Help/variable/CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION_MAXIMUM.rst
@@ -10,5 +10,5 @@
 to use as the maximum (e.g. ``10.0.14393.0``).  If unset, the default depends
 on which version of Visual Studio is targeted by the current generator.
 
-This can be used in conjunction with :variable:`CMAKE_SYSTEM_VERSION`, which
-CMake uses to select :variable:`CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION`.
+This can be used to exclude Windows SDK versions from consideration for
+:variable:`CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION`.
diff --git a/Help/variable/MINGW.rst b/Help/variable/MINGW.rst
index 27c56ea..fc2af2d 100644
--- a/Help/variable/MINGW.rst
+++ b/Help/variable/MINGW.rst
@@ -3,6 +3,7 @@
 
 .. versionadded:: 3.2
 
-``True`` when using MinGW
+Set to a true value when at least one language is enabled
+with a compiler targeting the GNU ABI on Windows (MinGW).
 
-Set to ``true`` when the compiler is some version of MinGW.
+Otherwise, this variable is not set by CMake.
diff --git a/Help/variable/PackageName_ROOT.rst b/Help/variable/PackageName_ROOT.rst
index 6b17be3..8b728ba 100644
--- a/Help/variable/PackageName_ROOT.rst
+++ b/Help/variable/PackageName_ROOT.rst
@@ -14,3 +14,11 @@
 :ref:`semicolon-separated list <CMake Language Lists>` of multiple prefixes.
 
 See also the :envvar:`<PackageName>_ROOT` environment variable.
+
+.. variable:: <PACKAGENAME>_ROOT
+
+  .. versionadded:: 3.27
+
+  Calls to :command:`find_package(<PackageName>)` will also search in
+  prefixes specified by the upper-case ``<PACKAGENAME>_ROOT`` CMake
+  variable.  See policy :policy:`CMP0144`.
diff --git a/Modules/CMakeASMCompiler.cmake.in b/Modules/CMakeASMCompiler.cmake.in
index 3953b30..1efd9f5 100644
--- a/Modules/CMakeASMCompiler.cmake.in
+++ b/Modules/CMakeASMCompiler.cmake.in
@@ -6,6 +6,7 @@
 set(CMAKE_ASM@ASM_DIALECT@_COMPILER_RANLIB "@_CMAKE_ASM_COMPILER_RANLIB@")
 set(CMAKE_LINKER "@CMAKE_LINKER@")
 set(CMAKE_MT "@CMAKE_MT@")
+set(CMAKE_TAPI "@CMAKE_TAPI@")
 set(CMAKE_ASM@ASM_DIALECT@_COMPILER_LOADED 1)
 set(CMAKE_ASM@ASM_DIALECT@_COMPILER_ID "@_CMAKE_ASM_COMPILER_ID@")
 set(CMAKE_ASM@ASM_DIALECT@_COMPILER_VERSION "@_CMAKE_ASM_COMPILER_VERSION@")
@@ -16,5 +17,6 @@
 
 set(CMAKE_ASM@ASM_DIALECT@_IGNORE_EXTENSIONS h;H;o;O;obj;OBJ;def;DEF;rc;RC)
 set(CMAKE_ASM@ASM_DIALECT@_LINKER_PREFERENCE 0)
+set(CMAKE_ASM@ASM_DIALECT@_LINKER_DEPFILE_SUPPORTED @CMAKE_ASM_LINKER_DEPFILE_SUPPORTED@)
 
 @CMAKE_ASM_COMPILER_CUSTOM_CODE@
diff --git a/Modules/CMakeCCompiler.cmake.in b/Modules/CMakeCCompiler.cmake.in
index 2b24ff2..2f0b774 100644
--- a/Modules/CMakeCCompiler.cmake.in
+++ b/Modules/CMakeCCompiler.cmake.in
@@ -27,6 +27,7 @@
 set(CMAKE_C_COMPILER_RANLIB "@CMAKE_C_COMPILER_RANLIB@")
 set(CMAKE_LINKER "@CMAKE_LINKER@")
 set(CMAKE_MT "@CMAKE_MT@")
+set(CMAKE_TAPI "@CMAKE_TAPI@")
 set(CMAKE_COMPILER_IS_GNUCC @CMAKE_COMPILER_IS_GNUCC@)
 set(CMAKE_C_COMPILER_LOADED 1)
 set(CMAKE_C_COMPILER_WORKS @CMAKE_C_COMPILER_WORKS@)
@@ -38,6 +39,7 @@
 set(CMAKE_C_SOURCE_FILE_EXTENSIONS c;m)
 set(CMAKE_C_IGNORE_EXTENSIONS h;H;o;O;obj;OBJ;def;DEF;rc;RC)
 set(CMAKE_C_LINKER_PREFERENCE 10)
+set(CMAKE_C_LINKER_DEPFILE_SUPPORTED @CMAKE_C_LINKER_DEPFILE_SUPPORTED@)
 
 # Save compiler ABI information.
 set(CMAKE_C_SIZEOF_DATA_PTR "@CMAKE_C_SIZEOF_DATA_PTR@")
diff --git a/Modules/CMakeCUDACompiler.cmake.in b/Modules/CMakeCUDACompiler.cmake.in
index 57d595a..3c28c28 100644
--- a/Modules/CMakeCUDACompiler.cmake.in
+++ b/Modules/CMakeCUDACompiler.cmake.in
@@ -30,6 +30,7 @@
 set(CMAKE_CUDA_SOURCE_FILE_EXTENSIONS cu)
 set(CMAKE_CUDA_LINKER_PREFERENCE 15)
 set(CMAKE_CUDA_LINKER_PREFERENCE_PROPAGATES 1)
+set(CMAKE_CUDA_LINKER_DEPFILE_SUPPORTED @CMAKE_CUDA_LINKER_DEPFILE_SUPPORTED@)
 
 set(CMAKE_CUDA_SIZEOF_DATA_PTR "@CMAKE_CUDA_SIZEOF_DATA_PTR@")
 set(CMAKE_CUDA_COMPILER_ABI "@CMAKE_CUDA_COMPILER_ABI@")
diff --git a/Modules/CMakeCUDAInformation.cmake b/Modules/CMakeCUDAInformation.cmake
index dea721e..e774088 100644
--- a/Modules/CMakeCUDAInformation.cmake
+++ b/Modules/CMakeCUDAInformation.cmake
@@ -134,7 +134,6 @@
 # CMAKE_CUDA_CREATE_SHARED_LIBRARY
 # CMAKE_CUDA_CREATE_SHARED_MODULE
 # CMAKE_CUDA_COMPILE_WHOLE_COMPILATION
-# CMAKE_CUDA_COMPILE_PTX_COMPILATION
 # CMAKE_CUDA_COMPILE_SEPARABLE_COMPILATION
 # CMAKE_CUDA_LINK_EXECUTABLE
 
diff --git a/Modules/CMakeCXXCompiler.cmake.in b/Modules/CMakeCXXCompiler.cmake.in
index 534e960..8b6f82b 100644
--- a/Modules/CMakeCXXCompiler.cmake.in
+++ b/Modules/CMakeCXXCompiler.cmake.in
@@ -28,6 +28,7 @@
 set(CMAKE_CXX_COMPILER_RANLIB "@CMAKE_CXX_COMPILER_RANLIB@")
 set(CMAKE_LINKER "@CMAKE_LINKER@")
 set(CMAKE_MT "@CMAKE_MT@")
+set(CMAKE_TAPI "@CMAKE_TAPI@")
 set(CMAKE_COMPILER_IS_GNUCXX @CMAKE_COMPILER_IS_GNUCXX@)
 set(CMAKE_CXX_COMPILER_LOADED 1)
 set(CMAKE_CXX_COMPILER_WORKS @CMAKE_CXX_COMPILER_WORKS@)
@@ -36,7 +37,7 @@
 set(CMAKE_CXX_COMPILER_ENV_VAR "CXX")
 
 set(CMAKE_CXX_COMPILER_ID_RUN 1)
-set(CMAKE_CXX_SOURCE_FILE_EXTENSIONS C;M;c++;cc;cpp;cxx;m;mm;mpp;CPP;ixx;cppm)
+set(CMAKE_CXX_SOURCE_FILE_EXTENSIONS C;M;c++;cc;cpp;cxx;m;mm;mpp;CPP;ixx;cppm;ccm;cxxm;c++m)
 set(CMAKE_CXX_IGNORE_EXTENSIONS inl;h;hpp;HPP;H;o;O;obj;OBJ;def;DEF;rc;RC)
 
 foreach (lang C OBJC OBJCXX)
@@ -49,6 +50,7 @@
 
 set(CMAKE_CXX_LINKER_PREFERENCE 30)
 set(CMAKE_CXX_LINKER_PREFERENCE_PROPAGATES 1)
+set(CMAKE_CXX_LINKER_DEPFILE_SUPPORTED @CMAKE_CXX_LINKER_DEPFILE_SUPPORTED@)
 
 # Save compiler ABI information.
 set(CMAKE_CXX_SIZEOF_DATA_PTR "@CMAKE_CXX_SIZEOF_DATA_PTR@")
diff --git a/Modules/CMakeDetermineCCompiler.cmake b/Modules/CMakeDetermineCCompiler.cmake
index f5298df..8beebc5 100644
--- a/Modules/CMakeDetermineCCompiler.cmake
+++ b/Modules/CMakeDetermineCCompiler.cmake
@@ -162,7 +162,7 @@
 
   if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang|QCC|LCC")
     get_filename_component(COMPILER_BASENAME "${CMAKE_C_COMPILER}" NAME)
-    if (COMPILER_BASENAME MATCHES "^(.+-)?(clang|g?cc)(-cl)?(-[0-9]+(\\.[0-9]+)*)?(-[^.]+)?(\\.exe)?$")
+    if (COMPILER_BASENAME MATCHES "^(.+-)?(clang|g?cc)(-cl)?(-?[0-9]+(\\.[0-9]+)*)?(-[^.]+)?(\\.exe)?$")
       set(_CMAKE_TOOLCHAIN_PREFIX ${CMAKE_MATCH_1})
       set(_CMAKE_TOOLCHAIN_SUFFIX ${CMAKE_MATCH_4})
       set(_CMAKE_COMPILER_SUFFIX ${CMAKE_MATCH_6})
diff --git a/Modules/CMakeDetermineCXXCompiler.cmake b/Modules/CMakeDetermineCXXCompiler.cmake
index c4ddf75..40934ec 100644
--- a/Modules/CMakeDetermineCXXCompiler.cmake
+++ b/Modules/CMakeDetermineCXXCompiler.cmake
@@ -165,13 +165,13 @@
 
 if (NOT _CMAKE_TOOLCHAIN_PREFIX)
 
-  if("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU|Clang|QCC|LCC")
+  if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|QCC|LCC")
     get_filename_component(COMPILER_BASENAME "${CMAKE_CXX_COMPILER}" NAME)
-    if (COMPILER_BASENAME MATCHES "^(.+-)?(clang\\+\\+|[gc]\\+\\+|clang-cl)(-[0-9]+(\\.[0-9]+)*)?(-[^.]+)?(\\.exe)?$")
+    if (COMPILER_BASENAME MATCHES "^(.+-)?(clang\\+\\+|[gc]\\+\\+|clang-cl)(-?[0-9]+(\\.[0-9]+)*)?(-[^.]+)?(\\.exe)?$")
       set(_CMAKE_TOOLCHAIN_PREFIX ${CMAKE_MATCH_1})
       set(_CMAKE_TOOLCHAIN_SUFFIX ${CMAKE_MATCH_3})
       set(_CMAKE_COMPILER_SUFFIX ${CMAKE_MATCH_5})
-    elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
+    elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
       if(CMAKE_CXX_COMPILER_TARGET)
         set(_CMAKE_TOOLCHAIN_PREFIX ${CMAKE_CXX_COMPILER_TARGET}-)
       endif()
@@ -186,7 +186,7 @@
     if ("${_CMAKE_TOOLCHAIN_PREFIX}" MATCHES "(.+-)?llvm-$")
       set(_CMAKE_TOOLCHAIN_PREFIX ${CMAKE_MATCH_1})
     endif ()
-  elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "TI")
+  elseif(CMAKE_CXX_COMPILER_ID MATCHES "TI")
     # TI compilers are named e.g. cl6x, cl470 or armcl.exe
     get_filename_component(COMPILER_BASENAME "${CMAKE_CXX_COMPILER}" NAME)
     if (COMPILER_BASENAME MATCHES "^(.+)?cl([^.]+)?(\\.exe)?$")
diff --git a/Modules/CMakeDetermineCompilerABI.cmake b/Modules/CMakeDetermineCompilerABI.cmake
index df17796..13bfeec 100644
--- a/Modules/CMakeDetermineCompilerABI.cmake
+++ b/Modules/CMakeDetermineCompilerABI.cmake
@@ -181,6 +181,10 @@
         endif()
       endif()
 
+      if(DEFINED ENV{CMAKE_${lang}_IMPLICIT_LINK_DIRECTORIES_EXCLUDE})
+        list(REMOVE_ITEM implicit_dirs $ENV{CMAKE_${lang}_IMPLICIT_LINK_DIRECTORIES_EXCLUDE})
+      endif()
+
       set(CMAKE_${lang}_IMPLICIT_LINK_LIBRARIES "${implicit_libs}" PARENT_SCOPE)
       set(CMAKE_${lang}_IMPLICIT_LINK_DIRECTORIES "${implicit_dirs}" PARENT_SCOPE)
       set(CMAKE_${lang}_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES "${implicit_fwks}" PARENT_SCOPE)
diff --git a/Modules/CMakeDetermineCompilerId.cmake b/Modules/CMakeDetermineCompilerId.cmake
index 2250d63..6c49096 100644
--- a/Modules/CMakeDetermineCompilerId.cmake
+++ b/Modules/CMakeDetermineCompilerId.cmake
@@ -35,7 +35,7 @@
   else(CMAKE_${lang}_FLAGS_INIT)
     set(CMAKE_${lang}_COMPILER_ID_FLAGS ${CMAKE_${lang}_FLAGS_INIT})
   endif()
-  string(REPLACE " " ";" CMAKE_${lang}_COMPILER_ID_FLAGS_LIST "${CMAKE_${lang}_COMPILER_ID_FLAGS}")
+  separate_arguments(CMAKE_${lang}_COMPILER_ID_FLAGS_LIST NATIVE_COMMAND "${CMAKE_${lang}_COMPILER_ID_FLAGS}")
 
   # Compute the directory in which to run the test.
   set(CMAKE_${lang}_COMPILER_ID_DIR ${CMAKE_PLATFORM_INFO_DIR}/CompilerId${lang})
@@ -345,6 +345,7 @@
     set(id_platform ${CMAKE_VS_PLATFORM_NAME})
     set(id_lang "${lang}")
     set(id_PostBuildEvent_Command "")
+    set(id_api_level "")
     if(CMAKE_VS_PLATFORM_TOOLSET MATCHES "^[Ll][Ll][Vv][Mm](_v[0-9]+(_xp)?)?$")
       set(id_cl_var "ClangClExecutable")
     elseif(CMAKE_VS_PLATFORM_TOOLSET MATCHES "^[Cc][Ll][Aa][Nn][Gg]([Cc][Ll]$|_[0-9])")
@@ -401,6 +402,9 @@
             endif()
           endif()
           set(id_toolset_version_props "<Import Project=\"${CMAKE_GENERATOR_INSTANCE}\\VC\\Auxiliary\\Build${id_sep}${CMAKE_VS_PLATFORM_TOOLSET_VERSION}\\Microsoft.VCToolsVersion.${CMAKE_VS_PLATFORM_TOOLSET_VERSION}.props\" />")
+          if(lang STREQUAL CXX OR lang STREQUAL C)
+            set(id_toolset_version_props "<Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />${id_toolset_version_props}")
+          endif()
           unset(id_sep)
         endif()
       endif()
@@ -427,9 +431,10 @@
       set(id_system "")
     endif()
     if(id_keyword STREQUAL "Android")
+      set(id_api_level "<AndroidAPILevel>android-${CMAKE_SYSTEM_VERSION}</AndroidAPILevel>")
       if(CMAKE_GENERATOR MATCHES "Visual Studio 14")
         set(id_system_version "<ApplicationTypeRevision>2.0</ApplicationTypeRevision>")
-      elseif(CMAKE_GENERATOR MATCHES "Visual Studio 1[56]")
+      elseif(CMAKE_GENERATOR MATCHES "Visual Studio 1[567]")
         set(id_system_version "<ApplicationTypeRevision>3.0</ApplicationTypeRevision>")
       else()
         set(id_system_version "")
diff --git a/Modules/CMakeDetermineSystem.cmake b/Modules/CMakeDetermineSystem.cmake
index d4dcc62..386be73 100644
--- a/Modules/CMakeDetermineSystem.cmake
+++ b/Modules/CMakeDetermineSystem.cmake
@@ -176,6 +176,13 @@
     set(CMAKE_SYSTEM_VERSION "${CMAKE_HOST_SYSTEM_VERSION}")
   endif()
   set(CMAKE_SYSTEM_PROCESSOR "${CMAKE_HOST_SYSTEM_PROCESSOR}")
+  if(CMAKE_CROSSCOMPILING)
+    message(AUTHOR_WARNING
+      "CMAKE_CROSSCOMPILING has been set by the project, toolchain file, or user.  "
+      "CMake is resetting it to false because CMAKE_SYSTEM_NAME was not set.  "
+      "To indicate cross compilation, only CMAKE_SYSTEM_NAME needs to be set."
+      )
+  endif()
   set(CMAKE_CROSSCOMPILING FALSE)
   set(PRESET_CMAKE_SYSTEM_NAME FALSE)
 endif()
diff --git a/Modules/CMakeFindBinUtils.cmake b/Modules/CMakeFindBinUtils.cmake
index 2ac8879..461839a 100644
--- a/Modules/CMakeFindBinUtils.cmake
+++ b/Modules/CMakeFindBinUtils.cmake
@@ -121,9 +121,9 @@
   set(_CMAKE_IAR_ITOOLS "ARM" "RX" "RH850" "RL78" "RISCV" "RISC-V" "STM8")
   set(_CMAKE_IAR_XTOOLS "AVR" "MSP430" "V850" "8051")
 
-  if("${CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER_ARCHITECTURE_ID}" IN_LIST _CMAKE_IAR_ITOOLS)
-    string(TOLOWER "${CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER_ARCHITECTURE_ID}" _CMAKE_IAR_LOWER_ARCHITECTURE_ID)
+  string(TOLOWER "${CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER_ARCHITECTURE_ID}" _CMAKE_IAR_LOWER_ARCHITECTURE_ID)
 
+  if("${CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER_ARCHITECTURE_ID}" IN_LIST _CMAKE_IAR_ITOOLS)
     __append_IAR_tool(AR "iarchive")
     __append_IAR_tool(LINKER "ilink${_CMAKE_IAR_LOWER_ARCHITECTURE_ID}")
 
@@ -132,16 +132,21 @@
     __append_IAR_tool(IAR_OBJMANIP "iobjmanip")
     __append_IAR_tool(IAR_SYMEXPORT "isymexport")
 
-    unset(_CMAKE_IAR_LOWER_ARCHITECTURE_ID)
-
   elseif("${CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER_ARCHITECTURE_ID}" IN_LIST _CMAKE_IAR_XTOOLS)
     __append_IAR_tool(AR "xar")
-    __append_IAR_tool(LINKER "xlink")
+    if("${CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER_ARCHITECTURE_ID}" STREQUAL "AVR" AND
+      (CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER_VERSION VERSION_GREATER_EQUAL 8))
+      # IAR UBROF Linker V8.10+ for Microchip AVR is `xlinkavr`
+      __append_IAR_tool(LINKER "xlink${_CMAKE_IAR_LOWER_ARCHITECTURE_ID}")
+    else()
+      __append_IAR_tool(LINKER "xlink")
+    endif()
 
   else()
     message(FATAL_ERROR "Failed to find linker and librarian for ${CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER_ID} on ${CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER_ARCHITECTURE_ID}.")
   endif()
 
+  unset(_CMAKE_IAR_LOWER_ARCHITECTURE_ID)
   unset(_CMAKE_IAR_ITOOLS)
   unset(_CMAKE_IAR_XTOOLS)
 
@@ -165,6 +170,7 @@
   set(_CMAKE_READELF_NAMES "readelf")
   set(_CMAKE_DLLTOOL_NAMES "dlltool")
   set(_CMAKE_ADDR2LINE_NAMES "addr2line")
+  set(_CMAKE_TAPI_NAMES "tapi")
 
   # Prepend toolchain-specific names.
   if("${CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER_ID}" STREQUAL Clang)
@@ -185,7 +191,15 @@
     list(PREPEND _CMAKE_RANLIB_NAMES "llvm-ranlib")
     if("${CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER_VERSION}" VERSION_GREATER_EQUAL 11)
       # llvm-strip versions prior to 11 require additional flags we do not yet add.
-      list(PREPEND _CMAKE_STRIP_NAMES "llvm-strip")
+      if(APPLE)
+        # llvm-strip does not seem to support chained fixup format correctly.
+        # FIXME(#23333): We still need to consider 'llvm-strip' as a fallback
+        # because the 'APPLE' definition may be based on the host in this context,
+        # and a cross-compiling toolchain may not have 'strip'.
+        list(APPEND _CMAKE_STRIP_NAMES "llvm-strip")
+      else()
+        list(PREPEND _CMAKE_STRIP_NAMES "llvm-strip")
+      endif()
     endif()
     list(PREPEND _CMAKE_NM_NAMES "llvm-nm")
     if("${CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER_VERSION}" VERSION_GREATER_EQUAL 9)
@@ -201,7 +215,7 @@
     list(PREPEND _CMAKE_LINKER_NAMES "armlink")
   endif()
 
-  list(APPEND _CMAKE_TOOL_VARS AR RANLIB STRIP LINKER NM OBJDUMP OBJCOPY READELF DLLTOOL ADDR2LINE)
+  list(APPEND _CMAKE_TOOL_VARS AR RANLIB STRIP LINKER NM OBJDUMP OBJCOPY READELF DLLTOOL ADDR2LINE TAPI)
 endif()
 
 foreach(_CMAKE_TOOL IN LISTS _CMAKE_TOOL_VARS)
@@ -225,6 +239,20 @@
     set(CMAKE_RANLIB : CACHE INTERNAL "noop for ranlib")
 endif()
 
+if(NOT CMAKE_TAPI)
+  # try to pick-up from Apple toolchain
+  execute_process(COMMAND xcrun --find tapi
+    OUTPUT_VARIABLE _xcrun_out
+    OUTPUT_STRIP_TRAILING_WHITESPACE
+    ERROR_QUIET
+    RESULT_VARIABLE _xcrun_failed)
+  if(NOT _xcrun_failed AND EXISTS "${_xcrun_out}")
+    set_property(CACHE CMAKE_TAPI PROPERTY VALUE "${_xcrun_out}")
+  endif()
+  unset(_xcrun_out)
+  unset(_xcrun_failed)
+endif()
+
 
 if(CMAKE_PLATFORM_HAS_INSTALLNAME)
   find_program(CMAKE_INSTALL_NAME_TOOL NAMES ${_CMAKE_TOOLCHAIN_PREFIX}install_name_tool HINTS ${_CMAKE_TOOLCHAIN_LOCATION} NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH)
diff --git a/Modules/CMakeFindKate.cmake b/Modules/CMakeFindKate.cmake
index 9aaf6e5..521bc5c 100644
--- a/Modules/CMakeFindKate.cmake
+++ b/Modules/CMakeFindKate.cmake
@@ -3,7 +3,7 @@
 
 
 # This file is included in CMakeSystemSpecificInformation.cmake if
-# the Eclipse CDT4 extra generator has been selected.
+# the Kate extra generator has been selected.
 
 
 # Try to find out how many CPUs we have and set the -j argument for make accordingly
@@ -17,5 +17,9 @@
   set(_CMAKE_KATE_INITIAL_MAKE_ARGS "-j${_CMAKE_KATE_PROCESSOR_COUNT}")
 endif()
 
-# This variable is used by the Eclipse generator and appended to the make invocation commands.
+# This variable is used by the Kate generator and appended to the make invocation commands.
 set(CMAKE_KATE_MAKE_ARGUMENTS "${_CMAKE_KATE_INITIAL_MAKE_ARGS}" CACHE STRING "Additional command line arguments when Kate invokes make. Enter e.g. -j<some_number> to get parallel builds")
+
+
+set(CMAKE_KATE_FILES_MODE "AUTO" CACHE STRING "Option to override the version control detection and force a mode for the Kate project.")
+set_property(CACHE CMAKE_KATE_FILES_MODE PROPERTY STRINGS "AUTO;SVN;GIT;LIST")
diff --git a/Modules/CMakeFortranCompiler.cmake.in b/Modules/CMakeFortranCompiler.cmake.in
index f52ad02..6a2be28 100644
--- a/Modules/CMakeFortranCompiler.cmake.in
+++ b/Modules/CMakeFortranCompiler.cmake.in
@@ -14,6 +14,7 @@
 set(CMAKE_AR "@CMAKE_AR@")
 set(CMAKE_Fortran_COMPILER_AR "@CMAKE_Fortran_COMPILER_AR@")
 set(CMAKE_RANLIB "@CMAKE_RANLIB@")
+set(CMAKE_TAPI "@CMAKE_TAPI@")
 set(CMAKE_Fortran_COMPILER_RANLIB "@CMAKE_Fortran_COMPILER_RANLIB@")
 set(CMAKE_COMPILER_IS_GNUG77 @CMAKE_COMPILER_IS_GNUG77@)
 set(CMAKE_Fortran_COMPILER_LOADED 1)
@@ -25,9 +26,10 @@
 set(CMAKE_Fortran_COMPILER_SUPPORTS_F90 @CMAKE_Fortran_COMPILER_SUPPORTS_F90@)
 
 set(CMAKE_Fortran_COMPILER_ID_RUN 1)
-set(CMAKE_Fortran_SOURCE_FILE_EXTENSIONS f;F;fpp;FPP;f77;F77;f90;F90;for;For;FOR;f95;F95@CMAKE_Fortran_VENDOR_SOURCE_FILE_EXTENSIONS@)
+set(CMAKE_Fortran_SOURCE_FILE_EXTENSIONS f;F;fpp;FPP;f77;F77;f90;F90;for;For;FOR;f95;F95;f03;F03;f08;F08@CMAKE_Fortran_VENDOR_SOURCE_FILE_EXTENSIONS@)
 set(CMAKE_Fortran_IGNORE_EXTENSIONS h;H;o;O;obj;OBJ;def;DEF;rc;RC)
 set(CMAKE_Fortran_LINKER_PREFERENCE 20)
+set(CMAKE_Fortran_LINKER_DEPFILE_SUPPORTED @CMAKE_Fortran_LINKER_DEPFILE_SUPPORTED@)
 if(UNIX)
   set(CMAKE_Fortran_OUTPUT_EXTENSION .o)
 else()
diff --git a/Modules/CMakeHIPCompiler.cmake.in b/Modules/CMakeHIPCompiler.cmake.in
index ce4e2cf..c94153b 100644
--- a/Modules/CMakeHIPCompiler.cmake.in
+++ b/Modules/CMakeHIPCompiler.cmake.in
@@ -26,6 +26,7 @@
 set(CMAKE_HIP_SOURCE_FILE_EXTENSIONS hip)
 set(CMAKE_HIP_LINKER_PREFERENCE 90)
 set(CMAKE_HIP_LINKER_PREFERENCE_PROPAGATES 1)
+set(CMAKE_HIP_LINKER_DEPFILE_SUPPORTED @CMAKE_HIP_LINKER_DEPFILE_SUPPORTED@)
 
 set(CMAKE_HIP_SIZEOF_DATA_PTR "@CMAKE_HIP_SIZEOF_DATA_PTR@")
 set(CMAKE_HIP_COMPILER_ABI "@CMAKE_HIP_COMPILER_ABI@")
@@ -58,3 +59,4 @@
 set(CMAKE_HIP_COMPILER_RANLIB "@CMAKE_HIP_COMPILER_RANLIB@")
 set(CMAKE_LINKER "@CMAKE_LINKER@")
 set(CMAKE_MT "@CMAKE_MT@")
+set(CMAKE_TAPI "@CMAKE_TAPI@")
diff --git a/Modules/CMakeOBJCCompiler.cmake.in b/Modules/CMakeOBJCCompiler.cmake.in
index 36f6ec1..de73645 100644
--- a/Modules/CMakeOBJCCompiler.cmake.in
+++ b/Modules/CMakeOBJCCompiler.cmake.in
@@ -25,6 +25,7 @@
 set(CMAKE_OBJC_COMPILER_RANLIB "@CMAKE_OBJC_COMPILER_RANLIB@")
 set(CMAKE_LINKER "@CMAKE_LINKER@")
 set(CMAKE_MT "@CMAKE_MT@")
+set(CMAKE_TAPI "@CMAKE_TAPI@")
 set(CMAKE_COMPILER_IS_GNUOBJC @CMAKE_COMPILER_IS_GNUOBJC@)
 set(CMAKE_OBJC_COMPILER_LOADED 1)
 set(CMAKE_OBJC_COMPILER_WORKS @CMAKE_OBJC_COMPILER_WORKS@)
@@ -36,6 +37,7 @@
 set(CMAKE_OBJC_SOURCE_FILE_EXTENSIONS m)
 set(CMAKE_OBJC_IGNORE_EXTENSIONS h;H;o;O)
 set(CMAKE_OBJC_LINKER_PREFERENCE 5)
+set(CMAKE_OBJC_LINKER_DEPFILE_SUPPORTED @CMAKE_OBJC_LINKER_DEPFILE_SUPPORTED@)
 
 foreach (lang C CXX OBJCXX)
   foreach(extension IN LISTS CMAKE_OBJC_SOURCE_FILE_EXTENSIONS)
diff --git a/Modules/CMakeOBJCXXCompiler.cmake.in b/Modules/CMakeOBJCXXCompiler.cmake.in
index 4f27100..94d24ff 100644
--- a/Modules/CMakeOBJCXXCompiler.cmake.in
+++ b/Modules/CMakeOBJCXXCompiler.cmake.in
@@ -26,6 +26,7 @@
 set(CMAKE_OBJCXX_COMPILER_RANLIB "@CMAKE_OBJCXX_COMPILER_RANLIB@")
 set(CMAKE_LINKER "@CMAKE_LINKER@")
 set(CMAKE_MT "@CMAKE_MT@")
+set(CMAKE_TAPI "@CMAKE_TAPI@")
 set(CMAKE_COMPILER_IS_GNUOBJCXX @CMAKE_COMPILER_IS_GNUOBJCXX@)
 set(CMAKE_OBJCXX_COMPILER_LOADED 1)
 set(CMAKE_OBJCXX_COMPILER_WORKS @CMAKE_OBJCXX_COMPILER_WORKS@)
@@ -53,6 +54,7 @@
 
 set(CMAKE_OBJCXX_LINKER_PREFERENCE 25)
 set(CMAKE_OBJCXX_LINKER_PREFERENCE_PROPAGATES 1)
+set(CMAKE_OBJCXX_LINKER_DEPFILE_SUPPORTED @CMAKE_OBJCXX_LINKER_DEPFILE_SUPPORTED@)
 
 # Save compiler ABI information.
 set(CMAKE_OBJCXX_SIZEOF_DATA_PTR "@CMAKE_OBJCXX_SIZEOF_DATA_PTR@")
diff --git a/Modules/CMakePackageConfigHelpers.cmake b/Modules/CMakePackageConfigHelpers.cmake
index 1dc850a..581e65c 100644
--- a/Modules/CMakePackageConfigHelpers.cmake
+++ b/Modules/CMakePackageConfigHelpers.cmake
@@ -188,7 +188,7 @@
 ``BasicConfigVersion-<COMPATIBILITY>.cmake.in`` file is used.
 Please note that these files are internal to CMake and you should not call
 :command:`configure_file()` on them yourself, but they can be used as starting
-point to create more sophisticted custom ``ConfigVersion.cmake`` files.
+point to create more sophisticated custom ``ConfigVersion.cmake`` files.
 
 Example Generating Package Files
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/Modules/CMakeSwiftInformation.cmake b/Modules/CMakeSwiftInformation.cmake
index f524955..777c680 100644
--- a/Modules/CMakeSwiftInformation.cmake
+++ b/Modules/CMakeSwiftInformation.cmake
@@ -72,9 +72,9 @@
   # these options here will have no effect when compiling with the built-in driver,
   # and will explode violently, leaving build products in the source directory, when
   # using the old swift driver.
-  set(CMAKE_Swift_FLAGS_DEBUG_INIT "-Onone -g")
+  set(CMAKE_Swift_FLAGS_DEBUG_INIT "-Onone -g ${CMAKE_Swift_FLAGS_DEBUG_LINKER_FLAGS}")
   set(CMAKE_Swift_FLAGS_RELEASE_INIT "-O")
-  set(CMAKE_Swift_FLAGS_RELWITHDEBINFO_INIT "-O -g")
+  set(CMAKE_Swift_FLAGS_RELWITHDEBINFO_INIT "-O -g ${CMAKE_Swift_FLAGS_RELWITHDEBINFO_LINKER_FLAGS}")
   set(CMAKE_Swift_FLAGS_MINSIZEREL_INIT "-Osize")
 else()
   set(CMAKE_Swift_FLAGS_DEBUG_INIT "-Onone -g -incremental")
diff --git a/Modules/CPack.cmake b/Modules/CPack.cmake
index f9cf33f..ff1cb7e 100644
--- a/Modules/CPack.cmake
+++ b/Modules/CPack.cmake
@@ -262,8 +262,8 @@
   Lists each of the executables and associated text label to be used to
   create Start Menu shortcuts.  For example, setting this to the list
   ``ccmake;CMake`` will create a shortcut named "CMake" that will execute the
-  installed executable :program:`ccmake`.  Not all CPack generators use it (at least
-  NSIS, and WIX do).
+  installed executable :program:`ccmake`. Not all CPack generators use it (at least
+  NSIS, Inno Setup and WIX do).
 
 .. variable:: CPACK_STRIP_FILES
 
@@ -738,14 +738,16 @@
         )
     endif()
   else()
-    option(CPACK_BINARY_7Z    "Enable to build 7-Zip packages" OFF)
-    option(CPACK_BINARY_NSIS  "Enable to build NSIS packages" ON)
-    option(CPACK_BINARY_NUGET "Enable to build NuGet packages" OFF)
-    option(CPACK_BINARY_WIX   "Enable to build WiX packages" OFF)
-    option(CPACK_BINARY_ZIP   "Enable to build ZIP packages" OFF)
+    option(CPACK_BINARY_7Z        "Enable to build 7-Zip packages" OFF)
+    option(CPACK_BINARY_NSIS      "Enable to build NSIS packages" ON)
+    option(CPACK_BINARY_INNOSETUP "Enable to build Inno Setup packages" OFF)
+    option(CPACK_BINARY_NUGET     "Enable to build NuGet packages" OFF)
+    option(CPACK_BINARY_WIX       "Enable to build WiX packages" OFF)
+    option(CPACK_BINARY_ZIP       "Enable to build ZIP packages" OFF)
     mark_as_advanced(
       CPACK_BINARY_7Z
       CPACK_BINARY_NSIS
+      CPACK_BINARY_INNOSETUP
       CPACK_BINARY_NUGET
       CPACK_BINARY_WIX
       CPACK_BINARY_ZIP
@@ -762,6 +764,7 @@
   cpack_optional_append(CPACK_GENERATOR  CPACK_BINARY_FREEBSD      FREEBSD)
   cpack_optional_append(CPACK_GENERATOR  CPACK_BINARY_IFW          IFW)
   cpack_optional_append(CPACK_GENERATOR  CPACK_BINARY_NSIS         NSIS)
+  cpack_optional_append(CPACK_GENERATOR  CPACK_BINARY_INNOSETUP    INNOSETUP)
   cpack_optional_append(CPACK_GENERATOR  CPACK_BINARY_NUGET        NuGet)
   cpack_optional_append(CPACK_GENERATOR  CPACK_BINARY_PRODUCTBUILD productbuild)
   cpack_optional_append(CPACK_GENERATOR  CPACK_BINARY_RPM          RPM)
@@ -869,6 +872,13 @@
   unset(_CPack_CMP0133)
 endif()
 
+# Inno Setup specific variables
+if(CMAKE_SIZEOF_VOID_P EQUAL 4)
+  _cpack_set_default(CPACK_INNOSETUP_ARCHITECTURE "x86")
+elseif(CMAKE_SIZEOF_VOID_P EQUAL 8)
+  _cpack_set_default(CPACK_INNOSETUP_ARCHITECTURE "x64")
+endif()
+
 # WiX specific variables
 _cpack_set_default(CPACK_WIX_SIZEOF_VOID_P "${CMAKE_SIZEOF_VOID_P}")
 
diff --git a/Modules/CPackComponent.cmake b/Modules/CPackComponent.cmake
index 529f4e7..3b23b9f 100644
--- a/Modules/CPackComponent.cmake
+++ b/Modules/CPackComponent.cmake
@@ -149,7 +149,7 @@
 REQUIRED indicates that this component is required, and therefore will
 always be installed.  It will be visible in the graphical installer,
 but it cannot be unselected.  (Typically, required components are
-shown greyed out).
+shown grayed out).
 
 DISABLED indicates that this component should be disabled (unselected)
 by default.  The user is free to select this component for
diff --git a/Modules/CPackIFW.cmake b/Modules/CPackIFW.cmake
index d4e02f1..2338b79 100644
--- a/Modules/CPackIFW.cmake
+++ b/Modules/CPackIFW.cmake
@@ -8,7 +8,7 @@
 .. versionadded:: 3.1
 
 This module looks for the location of the command-line utilities supplied with the
-`Qt Installer Framework <http://doc.qt.io/qtinstallerframework/index.html>`_
+`Qt Installer Framework <https://doc.qt.io/qtinstallerframework/index.html>`_
 (QtIFW).
 
 The module also defines several commands to control the behavior of the
diff --git a/Modules/CTestTargets.cmake b/Modules/CTestTargets.cmake
index b91b48e..99ef8e5 100644
--- a/Modules/CTestTargets.cmake
+++ b/Modules/CTestTargets.cmake
@@ -41,7 +41,7 @@
 get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
 if(_isMultiConfig)
   # We need to pass the configuration type on the test command line.
-  set(__conf_types -C "${CMAKE_CFG_INTDIR}")
+  set(__conf_types -C "$<CONFIG>")
 endif()
 
 # Add convenience targets.  Do this at most once in case of nested
diff --git a/Modules/CheckCCompilerFlag.cmake b/Modules/CheckCCompilerFlag.cmake
index 335b437..cd89a55 100644
--- a/Modules/CheckCCompilerFlag.cmake
+++ b/Modules/CheckCCompilerFlag.cmake
@@ -11,29 +11,40 @@
 
   .. code-block:: cmake
 
-    check_c_compiler_flag(<flag> <var>)
+    check_c_compiler_flag(<flag> <resultVar>)
 
   Check that the ``<flag>`` is accepted by the compiler without
   a diagnostic.  Stores the result in an internal cache entry
-  named ``<var>``.
-
-This command temporarily sets the ``CMAKE_REQUIRED_DEFINITIONS`` variable
-and calls the ``check_c_source_compiles`` macro from the
-:module:`CheckCSourceCompiles` module.  See documentation of that
-module for a listing of variables that can otherwise modify the build.
+  named ``<resultVar>``.
 
 A positive result from this check indicates only that the compiler did not
 issue a diagnostic message when given the flag.  Whether the flag has any
 effect or even a specific one is beyond the scope of this module.
 
-.. note::
-  Since the :command:`try_compile` command forwards flags from variables
-  like :variable:`CMAKE_C_FLAGS <CMAKE_<LANG>_FLAGS>`, unknown flags
-  in such variables may cause a false negative for this check.
+The check is only performed once, with the result cached in the variable named
+by ``<resultVar>``. Every subsequent CMake run will re-use this cached value
+rather than performing the check again, even if the ``<code>`` changes. In
+order to force the check to be re-evaluated, the variable named by
+``<resultVar>`` must be manually removed from the cache.
+
+The compile and link commands can be influenced by setting any of the
+following variables prior to calling ``check_c_compiler_flag()``
+
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
+
 #]=======================================================================]
 
 include_guard(GLOBAL)
-include(CheckCSourceCompiles)
 include(Internal/CheckCompilerFlag)
 
 macro (CHECK_C_COMPILER_FLAG _FLAG _RESULT)
diff --git a/Modules/CheckCSourceCompiles.cmake b/Modules/CheckCSourceCompiles.cmake
index b24da49..ce4719a 100644
--- a/Modules/CheckCSourceCompiles.cmake
+++ b/Modules/CheckCSourceCompiles.cmake
@@ -22,51 +22,27 @@
   checking if anything in the output matches any of the specified regular
   expressions.
 
-  The underlying check is performed by the :command:`try_compile` command. The
-  compile and link commands can be influenced by setting any of the following
-  variables prior to calling ``check_c_source_compiles()``:
-
-  ``CMAKE_REQUIRED_FLAGS``
-    Additional flags to pass to the compiler. Note that the contents of
-    :variable:`CMAKE_C_FLAGS <CMAKE_<LANG>_FLAGS>` and its associated
-    configuration-specific variable are automatically added to the compiler
-    command before the contents of ``CMAKE_REQUIRED_FLAGS``.
-
-  ``CMAKE_REQUIRED_DEFINITIONS``
-    A :ref:`;-list <CMake Language Lists>` of compiler definitions of the form
-    ``-DFOO`` or ``-DFOO=bar``. A definition for the name specified by
-    ``<resultVar>`` will also be added automatically.
-
-  ``CMAKE_REQUIRED_INCLUDES``
-    A :ref:`;-list <CMake Language Lists>` of header search paths to pass to
-    the compiler. These will be the only header search paths used by
-    ``try_compile()``, i.e. the contents of the :prop_dir:`INCLUDE_DIRECTORIES`
-    directory property will be ignored.
-
-  ``CMAKE_REQUIRED_LINK_OPTIONS``
-    .. versionadded:: 3.14
-
-    A :ref:`;-list <CMake Language Lists>` of options to add to the link
-    command (see :command:`try_compile` for further details).
-
-  ``CMAKE_REQUIRED_LIBRARIES``
-    A :ref:`;-list <CMake Language Lists>` of libraries to add to the link
-    command. These can be the name of system libraries or they can be
-    :ref:`Imported Targets <Imported Targets>` (see :command:`try_compile` for
-    further details).
-
-  ``CMAKE_REQUIRED_QUIET``
-    .. versionadded:: 3.1
-
-    If this variable evaluates to a boolean true value, all status messages
-    associated with the check will be suppressed.
-
-  The check is only performed once, with the result cached in the variable
-  named by ``<resultVar>``. Every subsequent CMake run will re-use this cached
-  value rather than performing the check again, even if the ``<code>`` changes.
-  In order to force the check to be re-evaluated, the variable named by
+  The check is only performed once, with the result cached in the variable named
+  by ``<resultVar>``. Every subsequent CMake run will re-use this cached value
+  rather than performing the check again, even if the ``<code>`` changes. In
+  order to force the check to be re-evaluated, the variable named by
   ``<resultVar>`` must be manually removed from the cache.
 
+  The compile and link commands can be influenced by setting any of the
+  following variables prior to calling ``check_c_source_compiles()``:
+
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
+
 #]=======================================================================]
 
 include_guard(GLOBAL)
diff --git a/Modules/CheckCSourceRuns.cmake b/Modules/CheckCSourceRuns.cmake
index a6081ff..d5a8fda 100644
--- a/Modules/CheckCSourceRuns.cmake
+++ b/Modules/CheckCSourceRuns.cmake
@@ -21,51 +21,27 @@
   be set to 1, otherwise it will be set to an value that evaluates to boolean
   false (e.g. an empty string or an error message).
 
-  The underlying check is performed by the :command:`try_run` command. The
-  compile and link commands can be influenced by setting any of the following
-  variables prior to calling ``check_c_source_runs()``:
-
-  ``CMAKE_REQUIRED_FLAGS``
-    Additional flags to pass to the compiler. Note that the contents of
-    :variable:`CMAKE_C_FLAGS <CMAKE_<LANG>_FLAGS>` and its associated
-    configuration-specific variable are automatically added to the compiler
-    command before the contents of ``CMAKE_REQUIRED_FLAGS``.
-
-  ``CMAKE_REQUIRED_DEFINITIONS``
-    A :ref:`;-list <CMake Language Lists>` of compiler definitions of the form
-    ``-DFOO`` or ``-DFOO=bar``. A definition for the name specified by
-    ``<resultVar>`` will also be added automatically.
-
-  ``CMAKE_REQUIRED_INCLUDES``
-    A :ref:`;-list <CMake Language Lists>` of header search paths to pass to
-    the compiler. These will be the only header search paths used by
-    ``try_run()``, i.e. the contents of the :prop_dir:`INCLUDE_DIRECTORIES`
-    directory property will be ignored.
-
-  ``CMAKE_REQUIRED_LINK_OPTIONS``
-    .. versionadded:: 3.14
-
-    A :ref:`;-list <CMake Language Lists>` of options to add to the link
-    command (see :command:`try_run` for further details).
-
-  ``CMAKE_REQUIRED_LIBRARIES``
-    A :ref:`;-list <CMake Language Lists>` of libraries to add to the link
-    command. These can be the name of system libraries or they can be
-    :ref:`Imported Targets <Imported Targets>` (see :command:`try_run` for
-    further details).
-
-  ``CMAKE_REQUIRED_QUIET``
-    .. versionadded:: 3.1
-
-    If this variable evaluates to a boolean true value, all status messages
-    associated with the check will be suppressed.
-
-  The check is only performed once, with the result cached in the variable
-  named by ``<resultVar>``. Every subsequent CMake run will re-use this cached
-  value rather than performing the check again, even if the ``<code>`` changes.
-  In order to force the check to be re-evaluated, the variable named by
+  The check is only performed once, with the result cached in the variable named
+  by ``<resultVar>``. Every subsequent CMake run will re-use this cached value
+  rather than performing the check again, even if the ``<code>`` changes. In
+  order to force the check to be re-evaluated, the variable named by
   ``<resultVar>`` must be manually removed from the cache.
 
+  The compile and link commands can be influenced by setting any of the
+  following variables prior to calling ``check_c_source_runs()``:
+
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
+
 #]=======================================================================]
 
 include_guard(GLOBAL)
diff --git a/Modules/CheckCXXCompilerFlag.cmake b/Modules/CheckCXXCompilerFlag.cmake
index 3bc3463..a6884f5 100644
--- a/Modules/CheckCXXCompilerFlag.cmake
+++ b/Modules/CheckCXXCompilerFlag.cmake
@@ -17,11 +17,6 @@
   a diagnostic.  Stores the result in an internal cache entry
   named ``<var>``.
 
-This command temporarily sets the ``CMAKE_REQUIRED_DEFINITIONS`` variable
-and calls the ``check_cxx_source_compiles`` macro from the
-:module:`CheckCXXSourceCompiles` module.  See documentation of that
-module for a listing of variables that can otherwise modify the build.
-
 A positive result from this check indicates only that the compiler did not
 issue a diagnostic message when given the flag.  Whether the flag has any
 effect or even a specific one is beyond the scope of this module.
@@ -33,7 +28,6 @@
 #]=======================================================================]
 
 include_guard(GLOBAL)
-include(CheckCXXSourceCompiles)
 include(Internal/CheckCompilerFlag)
 
 macro (CHECK_CXX_COMPILER_FLAG _FLAG _RESULT)
diff --git a/Modules/CheckCXXSourceCompiles.cmake b/Modules/CheckCXXSourceCompiles.cmake
index 502bfa7..4b33aa8 100644
--- a/Modules/CheckCXXSourceCompiles.cmake
+++ b/Modules/CheckCXXSourceCompiles.cmake
@@ -22,51 +22,27 @@
   checking if anything in the output matches any of the specified regular
   expressions.
 
-  The underlying check is performed by the :command:`try_compile` command. The
-  compile and link commands can be influenced by setting any of the following
-  variables prior to calling ``check_cxx_source_compiles()``:
-
-  ``CMAKE_REQUIRED_FLAGS``
-    Additional flags to pass to the compiler. Note that the contents of
-    :variable:`CMAKE_CXX_FLAGS <CMAKE_<LANG>_FLAGS>` and its associated
-    configuration-specific variable are automatically added to the compiler
-    command before the contents of ``CMAKE_REQUIRED_FLAGS``.
-
-  ``CMAKE_REQUIRED_DEFINITIONS``
-    A :ref:`;-list <CMake Language Lists>` of compiler definitions of the form
-    ``-DFOO`` or ``-DFOO=bar``. A definition for the name specified by
-    ``<resultVar>`` will also be added automatically.
-
-  ``CMAKE_REQUIRED_INCLUDES``
-    A :ref:`;-list <CMake Language Lists>` of header search paths to pass to
-    the compiler. These will be the only header search paths used by
-    ``try_compile()``, i.e. the contents of the :prop_dir:`INCLUDE_DIRECTORIES`
-    directory property will be ignored.
-
-  ``CMAKE_REQUIRED_LINK_OPTIONS``
-    .. versionadded:: 3.14
-
-    A :ref:`;-list <CMake Language Lists>` of options to add to the link
-    command (see :command:`try_compile` for further details).
-
-  ``CMAKE_REQUIRED_LIBRARIES``
-    A :ref:`;-list <CMake Language Lists>` of libraries to add to the link
-    command. These can be the name of system libraries or they can be
-    :ref:`Imported Targets <Imported Targets>` (see :command:`try_compile` for
-    further details).
-
-  ``CMAKE_REQUIRED_QUIET``
-    .. versionadded:: 3.1
-
-    If this variable evaluates to a boolean true value, all status messages
-    associated with the check will be suppressed.
-
-  The check is only performed once, with the result cached in the variable
-  named by ``<resultVar>``. Every subsequent CMake run will re-use this cached
-  value rather than performing the check again, even if the ``<code>`` changes.
-  In order to force the check to be re-evaluated, the variable named by
+  The check is only performed once, with the result cached in the variable named
+  by ``<resultVar>``. Every subsequent CMake run will re-use this cached value
+  rather than performing the check again, even if the ``<code>`` changes. In
+  order to force the check to be re-evaluated, the variable named by
   ``<resultVar>`` must be manually removed from the cache.
 
+  The compile and link commands can be influenced by setting any of the
+  following variables prior to calling ``check_cxx_source_compiles()``:
+
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
+
 #]=======================================================================]
 
 include_guard(GLOBAL)
diff --git a/Modules/CheckCXXSourceRuns.cmake b/Modules/CheckCXXSourceRuns.cmake
index af03453..3402715 100644
--- a/Modules/CheckCXXSourceRuns.cmake
+++ b/Modules/CheckCXXSourceRuns.cmake
@@ -21,51 +21,27 @@
   be set to 1, otherwise it will be set to an value that evaluates to boolean
   false (e.g. an empty string or an error message).
 
-  The underlying check is performed by the :command:`try_run` command. The
-  compile and link commands can be influenced by setting any of the following
-  variables prior to calling ``check_cxx_source_runs()``:
-
-  ``CMAKE_REQUIRED_FLAGS``
-    Additional flags to pass to the compiler. Note that the contents of
-    :variable:`CMAKE_CXX_FLAGS <CMAKE_<LANG>_FLAGS>` and its associated
-    configuration-specific variable are automatically added to the compiler
-    command before the contents of ``CMAKE_REQUIRED_FLAGS``.
-
-  ``CMAKE_REQUIRED_DEFINITIONS``
-    A :ref:`;-list <CMake Language Lists>` of compiler definitions of the form
-    ``-DFOO`` or ``-DFOO=bar``. A definition for the name specified by
-    ``<resultVar>`` will also be added automatically.
-
-  ``CMAKE_REQUIRED_INCLUDES``
-    A :ref:`;-list <CMake Language Lists>` of header search paths to pass to
-    the compiler. These will be the only header search paths used by
-    ``try_run()``, i.e. the contents of the :prop_dir:`INCLUDE_DIRECTORIES`
-    directory property will be ignored.
-
-  ``CMAKE_REQUIRED_LINK_OPTIONS``
-    .. versionadded:: 3.14
-
-    A :ref:`;-list <CMake Language Lists>` of options to add to the link
-    command (see :command:`try_run` for further details).
-
-  ``CMAKE_REQUIRED_LIBRARIES``
-    A :ref:`;-list <CMake Language Lists>` of libraries to add to the link
-    command. These can be the name of system libraries or they can be
-    :ref:`Imported Targets <Imported Targets>` (see :command:`try_run` for
-    further details).
-
-  ``CMAKE_REQUIRED_QUIET``
-    .. versionadded:: 3.1
-
-    If this variable evaluates to a boolean true value, all status messages
-    associated with the check will be suppressed.
-
-  The check is only performed once, with the result cached in the variable
-  named by ``<resultVar>``. Every subsequent CMake run will re-use this cached
-  value rather than performing the check again, even if the ``<code>`` changes.
-  In order to force the check to be re-evaluated, the variable named by
+  The check is only performed once, with the result cached in the variable named
+  by ``<resultVar>``. Every subsequent CMake run will re-use this cached value
+  rather than performing the check again, even if the ``<code>`` changes. In
+  order to force the check to be re-evaluated, the variable named by
   ``<resultVar>`` must be manually removed from the cache.
 
+  The compile and link commands can be influenced by setting any of the
+  following variables prior to calling ``check_cxx_source_runs()``:
+
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
+
 #]=======================================================================]
 
 include_guard(GLOBAL)
diff --git a/Modules/CheckCXXSymbolExists.cmake b/Modules/CheckCXXSymbolExists.cmake
index 1fa0898..569dafd 100644
--- a/Modules/CheckCXXSymbolExists.cmake
+++ b/Modules/CheckCXXSymbolExists.cmake
@@ -41,22 +41,17 @@
 The following variables may be set before calling this macro to modify
 the way the check is run:
 
-``CMAKE_REQUIRED_FLAGS``
-  string of compile command line flags.
-``CMAKE_REQUIRED_DEFINITIONS``
-  a :ref:`;-list <CMake Language Lists>` of macros to define (-DFOO=bar).
-``CMAKE_REQUIRED_INCLUDES``
-  a :ref:`;-list <CMake Language Lists>` of header search paths to pass to
-  the compiler.
-``CMAKE_REQUIRED_LINK_OPTIONS``
-  .. versionadded:: 3.14
-    a :ref:`;-list <CMake Language Lists>` of options to add to the link command.
-``CMAKE_REQUIRED_LIBRARIES``
-  a :ref:`;-list <CMake Language Lists>` of libraries to add to the link
-  command. See policy :policy:`CMP0075`.
-``CMAKE_REQUIRED_QUIET``
-  .. versionadded:: 3.1
-    execute quietly without messages.
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
 
 For example:
 
diff --git a/Modules/CheckCompilerFlag.cmake b/Modules/CheckCompilerFlag.cmake
index 77c07b9..a18435b 100644
--- a/Modules/CheckCompilerFlag.cmake
+++ b/Modules/CheckCompilerFlag.cmake
@@ -13,24 +13,36 @@
 
   .. code-block:: cmake
 
-    check_compiler_flag(<lang> <flag> <var>)
+    check_compiler_flag(<lang> <flag> <resultVar>)
 
 Check that the ``<flag>`` is accepted by the compiler without a diagnostic.
-Stores the result in an internal cache entry named ``<var>``.
-
-This command temporarily sets the ``CMAKE_REQUIRED_DEFINITIONS`` variable
-and calls the ``check_source_compiles(<LANG>)`` function from the
-:module:`CheckSourceCompiles` module.  See documentation of that
-module for a listing of variables that can otherwise modify the build.
+Stores the result in an internal cache entry named ``<resultVar>``.
 
 A positive result from this check indicates only that the compiler did not
 issue a diagnostic message when given the flag.  Whether the flag has any
 effect or even a specific one is beyond the scope of this module.
 
-.. note::
-  Since the :command:`try_compile` command forwards flags from variables
-  like :variable:`CMAKE_<LANG>_FLAGS <CMAKE_<LANG>_FLAGS>`, unknown flags
-  in such variables may cause a false negative for this check.
+The check is only performed once, with the result cached in the variable named
+by ``<resultVar>``. Every subsequent CMake run will re-use this cached value
+rather than performing the check again, even if the ``<code>`` changes. In
+order to force the check to be re-evaluated, the variable named by
+``<resultVar>`` must be manually removed from the cache.
+
+The compile and link commands can be influenced by setting any of the
+following variables prior to calling ``check_compiler_flag()``
+
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
+
 #]=======================================================================]
 
 include_guard(GLOBAL)
diff --git a/Modules/CheckFortranCompilerFlag.cmake b/Modules/CheckFortranCompilerFlag.cmake
index 5b1cd02..c3cd088 100644
--- a/Modules/CheckFortranCompilerFlag.cmake
+++ b/Modules/CheckFortranCompilerFlag.cmake
@@ -13,29 +13,40 @@
 
   .. code-block:: cmake
 
-    check_fortran_compiler_flag(<flag> <var>)
+    check_fortran_compiler_flag(<flag> <resultVar>)
 
   Check that the ``<flag>`` is accepted by the compiler without
   a diagnostic.  Stores the result in an internal cache entry
-  named ``<var>``.
-
-This command temporarily sets the ``CMAKE_REQUIRED_DEFINITIONS`` variable
-and calls the ``check_fortran_source_compiles`` macro from the
-:module:`CheckFortranSourceCompiles` module.  See documentation of that
-module for a listing of variables that can otherwise modify the build.
+  named ``<resultVar>``.
 
 A positive result from this check indicates only that the compiler did not
 issue a diagnostic message when given the flag.  Whether the flag has any
 effect or even a specific one is beyond the scope of this module.
 
-.. note::
-  Since the :command:`try_compile` command forwards flags from variables
-  like :variable:`CMAKE_Fortran_FLAGS <CMAKE_<LANG>_FLAGS>`, unknown flags
-  in such variables may cause a false negative for this check.
+The check is only performed once, with the result cached in the variable named
+by ``<resultVar>``. Every subsequent CMake run will re-use this cached value
+rather than performing the check again, even if the ``<code>`` changes. In
+order to force the check to be re-evaluated, the variable named by
+``<resultVar>`` must be manually removed from the cache.
+
+The compile and link commands can be influenced by setting any of the
+following variables prior to calling ``check_fortran_compiler_flag()``
+
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
+
 #]=======================================================================]
 
 include_guard(GLOBAL)
-include(CheckFortranSourceCompiles)
 include(Internal/CheckCompilerFlag)
 
 macro (CHECK_FORTRAN_COMPILER_FLAG _FLAG _RESULT)
diff --git a/Modules/CheckFortranSourceCompiles.cmake b/Modules/CheckFortranSourceCompiles.cmake
index 8dcc1d5..5158b7e 100644
--- a/Modules/CheckFortranSourceCompiles.cmake
+++ b/Modules/CheckFortranSourceCompiles.cmake
@@ -47,49 +47,27 @@
   ``SRC_EXT`` option can be used to override this with ``.<extension>`` instead--
   ``.F90`` is a typical choice.
 
-  The underlying check is performed by the :command:`try_compile` command. The
-  compile and link commands can be influenced by setting any of the following
-  variables prior to calling ``check_fortran_source_compiles()``:
-
-  ``CMAKE_REQUIRED_FLAGS``
-    Additional flags to pass to the compiler. Note that the contents of
-    :variable:`CMAKE_Fortran_FLAGS <CMAKE_<LANG>_FLAGS>` and its associated
-    configuration-specific variable are automatically added to the compiler
-    command before the contents of ``CMAKE_REQUIRED_FLAGS``.
-
-  ``CMAKE_REQUIRED_DEFINITIONS``
-    A :ref:`;-list <CMake Language Lists>` of compiler definitions of the form
-    ``-DFOO`` or ``-DFOO=bar``. A definition for the name specified by
-    ``<resultVar>`` will also be added automatically.
-
-  ``CMAKE_REQUIRED_INCLUDES``
-    A :ref:`;-list <CMake Language Lists>` of header search paths to pass to
-    the compiler. These will be the only header search paths used by
-    ``try_compile()``, i.e. the contents of the :prop_dir:`INCLUDE_DIRECTORIES`
-    directory property will be ignored.
-
-  ``CMAKE_REQUIRED_LINK_OPTIONS``
-    .. versionadded:: 3.14
-
-    A :ref:`;-list <CMake Language Lists>` of options to add to the link
-    command (see :command:`try_compile` for further details).
-
-  ``CMAKE_REQUIRED_LIBRARIES``
-    A :ref:`;-list <CMake Language Lists>` of libraries to add to the link
-    command. These can be the name of system libraries or they can be
-    :ref:`Imported Targets <Imported Targets>` (see :command:`try_compile` for
-    further details).
-
-  ``CMAKE_REQUIRED_QUIET``
-    If this variable evaluates to a boolean true value, all status messages
-    associated with the check will be suppressed.
-
-  The check is only performed once, with the result cached in the variable
-  named by ``<resultVar>``. Every subsequent CMake run will re-use this cached
-  value rather than performing the check again, even if the ``<code>`` changes.
-  In order to force the check to be re-evaluated, the variable named by
+  The check is only performed once, with the result cached in the variable named
+  by ``<resultVar>``. Every subsequent CMake run will re-use this cached value
+  rather than performing the check again, even if the ``<code>`` changes. In
+  order to force the check to be re-evaluated, the variable named by
   ``<resultVar>`` must be manually removed from the cache.
 
+  The compile and link commands can be influenced by setting any of the
+  following variables prior to calling ``check_fortran_source_compiles()``:
+
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
+
 #]=======================================================================]
 
 include_guard(GLOBAL)
diff --git a/Modules/CheckFortranSourceRuns.cmake b/Modules/CheckFortranSourceRuns.cmake
index 985c765..f996749 100644
--- a/Modules/CheckFortranSourceRuns.cmake
+++ b/Modules/CheckFortranSourceRuns.cmake
@@ -43,47 +43,27 @@
   By default, the test source file will be given a ``.F90`` file extension. The
   ``SRC_EXT`` option can be used to override this with ``.<extension>`` instead.
 
-  The underlying check is performed by the :command:`try_run` command. The
-  compile and link commands can be influenced by setting any of the following
-  variables prior to calling ``check_fortran_source_runs()``:
-
-  ``CMAKE_REQUIRED_FLAGS``
-    Additional flags to pass to the compiler. Note that the contents of
-    :variable:`CMAKE_Fortran_FLAGS <CMAKE_<LANG>_FLAGS>` and its associated
-    configuration-specific variable are automatically added to the compiler
-    command before the contents of ``CMAKE_REQUIRED_FLAGS``.
-
-  ``CMAKE_REQUIRED_DEFINITIONS``
-    A :ref:`;-list <CMake Language Lists>` of compiler definitions of the form
-    ``-DFOO`` or ``-DFOO=bar``. A definition for the name specified by
-    ``<resultVar>`` will also be added automatically.
-
-  ``CMAKE_REQUIRED_INCLUDES``
-    A :ref:`;-list <CMake Language Lists>` of header search paths to pass to
-    the compiler. These will be the only header search paths used by
-    ``try_run()``, i.e. the contents of the :prop_dir:`INCLUDE_DIRECTORIES`
-    directory property will be ignored.
-
-  ``CMAKE_REQUIRED_LINK_OPTIONS``
-    A :ref:`;-list <CMake Language Lists>` of options to add to the link
-    command (see :command:`try_run` for further details).
-
-  ``CMAKE_REQUIRED_LIBRARIES``
-    A :ref:`;-list <CMake Language Lists>` of libraries to add to the link
-    command. These can be the name of system libraries or they can be
-    :ref:`Imported Targets <Imported Targets>` (see :command:`try_run` for
-    further details).
-
-  ``CMAKE_REQUIRED_QUIET``
-    If this variable evaluates to a boolean true value, all status messages
-    associated with the check will be suppressed.
-
-  The check is only performed once, with the result cached in the variable
-  named by ``<resultVar>``. Every subsequent CMake run will re-use this cached
-  value rather than performing the check again, even if the ``<code>`` changes.
-  In order to force the check to be re-evaluated, the variable named by
+  The check is only performed once, with the result cached in the variable named
+  by ``<resultVar>``. Every subsequent CMake run will re-use this cached value
+  rather than performing the check again, even if the ``<code>`` changes. In
+  order to force the check to be re-evaluated, the variable named by
   ``<resultVar>`` must be manually removed from the cache.
 
+  The compile and link commands can be influenced by setting any of the
+  following variables prior to calling ``check_fortran_source_runs()``:
+
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
+
 #]=======================================================================]
 
 include_guard(GLOBAL)
diff --git a/Modules/CheckFunctionExists.cmake b/Modules/CheckFunctionExists.cmake
index e2939ed..e7c47a4 100644
--- a/Modules/CheckFunctionExists.cmake
+++ b/Modules/CheckFunctionExists.cmake
@@ -20,22 +20,17 @@
 The following variables may be set before calling this macro to modify the
 way the check is run:
 
-``CMAKE_REQUIRED_FLAGS``
-  string of compile command line flags.
-``CMAKE_REQUIRED_DEFINITIONS``
-  a :ref:`;-list <CMake Language Lists>` of macros to define (-DFOO=bar).
-``CMAKE_REQUIRED_INCLUDES``
-  a :ref:`;-list <CMake Language Lists>` of header search paths to pass to
-  the compiler.
-``CMAKE_REQUIRED_LINK_OPTIONS``
-  .. versionadded:: 3.14
-    a :ref:`;-list <CMake Language Lists>` of options to add to the link command.
-``CMAKE_REQUIRED_LIBRARIES``
-  a :ref:`;-list <CMake Language Lists>` of libraries to add to the link
-  command. See policy :policy:`CMP0075`.
-``CMAKE_REQUIRED_QUIET``
-  .. versionadded:: 3.1
-    execute quietly without messages.
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
 
 .. note::
 
diff --git a/Modules/CheckIncludeFile.cmake b/Modules/CheckIncludeFile.cmake
index 5771307..1d8c9f7 100644
--- a/Modules/CheckIncludeFile.cmake
+++ b/Modules/CheckIncludeFile.cmake
@@ -21,22 +21,17 @@
 The following variables may be set before calling this macro to modify
 the way the check is run:
 
-``CMAKE_REQUIRED_FLAGS``
-  string of compile command line flags.
-``CMAKE_REQUIRED_DEFINITIONS``
-  a :ref:`;-list <CMake Language Lists>` of macros to define (-DFOO=bar).
-``CMAKE_REQUIRED_INCLUDES``
-  a :ref:`;-list <CMake Language Lists>` of header search paths to pass to
-  the compiler.
-``CMAKE_REQUIRED_LINK_OPTIONS``
-  .. versionadded:: 3.14
-    a :ref:`;-list <CMake Language Lists>` of options to add to the link command.
-``CMAKE_REQUIRED_LIBRARIES``
-  a :ref:`;-list <CMake Language Lists>` of libraries to add to the link
-  command. See policy :policy:`CMP0075`.
-``CMAKE_REQUIRED_QUIET``
-  .. versionadded:: 3.1
-    execute quietly without messages.
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. 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
diff --git a/Modules/CheckIncludeFileCXX.cmake b/Modules/CheckIncludeFileCXX.cmake
index d27b485..53d9a45 100644
--- a/Modules/CheckIncludeFileCXX.cmake
+++ b/Modules/CheckIncludeFileCXX.cmake
@@ -21,22 +21,17 @@
 The following variables may be set before calling this macro to modify
 the way the check is run:
 
-``CMAKE_REQUIRED_FLAGS``
-  string of compile command line flags.
-``CMAKE_REQUIRED_DEFINITIONS``
-  a :ref:`;-list <CMake Language Lists>` of macros to define (-DFOO=bar).
-``CMAKE_REQUIRED_INCLUDES``
-  a :ref:`;-list <CMake Language Lists>` of header search paths to pass to
-  the compiler.
-``CMAKE_REQUIRED_LINK_OPTIONS``
-  .. versionadded:: 3.14
-    a :ref:`;-list <CMake Language Lists>` of options to add to the link command.
-``CMAKE_REQUIRED_LIBRARIES``
-  a :ref:`;-list <CMake Language Lists>` of libraries to add to the link
-  command. See policy :policy:`CMP0075`.
-``CMAKE_REQUIRED_QUIET``
-  .. versionadded:: 3.1
-    execute quietly without messages.
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
 
 See modules :module:`CheckIncludeFile` and :module:`CheckIncludeFiles`
 to check for one or more ``C`` headers.
diff --git a/Modules/CheckIncludeFiles.cmake b/Modules/CheckIncludeFiles.cmake
index 2f50c61..071df0c 100644
--- a/Modules/CheckIncludeFiles.cmake
+++ b/Modules/CheckIncludeFiles.cmake
@@ -27,22 +27,17 @@
 The following variables may be set before calling this macro to modify
 the way the check is run:
 
-``CMAKE_REQUIRED_FLAGS``
-  string of compile command line flags.
-``CMAKE_REQUIRED_DEFINITIONS``
-  a :ref:`;-list <CMake Language Lists>` of macros to define (-DFOO=bar).
-``CMAKE_REQUIRED_INCLUDES``
-  a :ref:`;-list <CMake Language Lists>` of header search paths to pass to
-  the compiler.
-``CMAKE_REQUIRED_LINK_OPTIONS``
-  .. versionadded:: 3.14
-    a :ref:`;-list <CMake Language Lists>` of options to add to the link command.
-``CMAKE_REQUIRED_LIBRARIES``
-  a :ref:`;-list <CMake Language Lists>` of libraries to add to the link
-  command. See policy :policy:`CMP0075`.
-``CMAKE_REQUIRED_QUIET``
-  .. versionadded:: 3.1
-    execute quietly without messages.
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. 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.
diff --git a/Modules/CheckLanguage.cmake b/Modules/CheckLanguage.cmake
index 2e56a19..69913a3 100644
--- a/Modules/CheckLanguage.cmake
+++ b/Modules/CheckLanguage.cmake
@@ -36,7 +36,7 @@
 
 include_guard(GLOBAL)
 
-cmake_policy(PUSH)
+block(SCOPE_FOR POLICIES)
 cmake_policy(SET CMP0126 NEW)
 
 macro(check_language lang)
@@ -114,4 +114,4 @@
   endif()
 endmacro()
 
-cmake_policy(POP)
+endblock()
diff --git a/Modules/CheckLibraryExists.cmake b/Modules/CheckLibraryExists.cmake
index 5f1a914..8340500 100644
--- a/Modules/CheckLibraryExists.cmake
+++ b/Modules/CheckLibraryExists.cmake
@@ -26,18 +26,16 @@
 The following variables may be set before calling this macro to modify
 the way the check is run:
 
-``CMAKE_REQUIRED_FLAGS``
-  string of compile command line flags.
-``CMAKE_REQUIRED_DEFINITIONS``
-  list of macros to define (-DFOO=bar).
-``CMAKE_REQUIRED_LINK_OPTIONS``
-  .. versionadded:: 3.14
-    list of options to pass to link command.
-``CMAKE_REQUIRED_LIBRARIES``
-  list of libraries to link.
-``CMAKE_REQUIRED_QUIET``
-  .. versionadded:: 3.1
-    execute quietly without messages.
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
+
 #]=======================================================================]
 
 include_guard(GLOBAL)
diff --git a/Modules/CheckOBJCCompilerFlag.cmake b/Modules/CheckOBJCCompilerFlag.cmake
index d8d8741..ceb7e17 100644
--- a/Modules/CheckOBJCCompilerFlag.cmake
+++ b/Modules/CheckOBJCCompilerFlag.cmake
@@ -13,29 +13,40 @@
 
   .. code-block:: cmake
 
-    check_objc_compiler_flag(<flag> <var>)
+    check_objc_compiler_flag(<flag> <resultVar>)
 
   Check that the ``<flag>`` is accepted by the compiler without
   a diagnostic.  Stores the result in an internal cache entry
-  named ``<var>``.
-
-This command temporarily sets the ``CMAKE_REQUIRED_DEFINITIONS`` variable
-and calls the ``check_objc_source_compiles`` macro from the
-:module:`CheckOBJCSourceCompiles` module.  See documentation of that
-module for a listing of variables that can otherwise modify the build.
+  named ``<resultVar>``.
 
 A positive result from this check indicates only that the compiler did not
 issue a diagnostic message when given the flag.  Whether the flag has any
 effect or even a specific one is beyond the scope of this module.
 
-.. note::
-  Since the :command:`try_compile` command forwards flags from variables
-  like :variable:`CMAKE_OBJC_FLAGS <CMAKE_<LANG>_FLAGS>`, unknown flags
-  in such variables may cause a false negative for this check.
+The check is only performed once, with the result cached in the variable named
+by ``<resultVar>``. Every subsequent CMake run will re-use this cached value
+rather than performing the check again, even if the ``<code>`` changes. In
+order to force the check to be re-evaluated, the variable named by
+``<resultVar>`` must be manually removed from the cache.
+
+The compile and link commands can be influenced by setting any of the
+following variables prior to calling ``check_objc_compiler_flag()``
+
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
+
 #]=======================================================================]
 
 include_guard(GLOBAL)
-include(CheckOBJCSourceCompiles)
 include(Internal/CheckCompilerFlag)
 
 macro (CHECK_OBJC_COMPILER_FLAG _FLAG _RESULT)
diff --git a/Modules/CheckOBJCSourceCompiles.cmake b/Modules/CheckOBJCSourceCompiles.cmake
index c268ef9..7663054 100644
--- a/Modules/CheckOBJCSourceCompiles.cmake
+++ b/Modules/CheckOBJCSourceCompiles.cmake
@@ -24,47 +24,27 @@
   checking if anything in the output matches any of the specified regular
   expressions.
 
-  The underlying check is performed by the :command:`try_compile` command. The
-  compile and link commands can be influenced by setting any of the following
-  variables prior to calling ``check_objc_source_compiles()``:
-
-  ``CMAKE_REQUIRED_FLAGS``
-    Additional flags to pass to the compiler. Note that the contents of
-    :variable:`CMAKE_OBJC_FLAGS <CMAKE_<LANG>_FLAGS>` and its associated
-    configuration-specific variable are automatically added to the compiler
-    command before the contents of ``CMAKE_REQUIRED_FLAGS``.
-
-  ``CMAKE_REQUIRED_DEFINITIONS``
-    A :ref:`;-list <CMake Language Lists>` of compiler definitions of the form
-    ``-DFOO`` or ``-DFOO=bar``. A definition for the name specified by
-    ``<resultVar>`` will also be added automatically.
-
-  ``CMAKE_REQUIRED_INCLUDES``
-    A :ref:`;-list <CMake Language Lists>` of header search paths to pass to
-    the compiler. These will be the only header search paths used by
-    ``try_compile()``, i.e. the contents of the :prop_dir:`INCLUDE_DIRECTORIES`
-    directory property will be ignored.
-
-  ``CMAKE_REQUIRED_LINK_OPTIONS``
-    A :ref:`;-list <CMake Language Lists>` of options to add to the link
-    command (see :command:`try_compile` for further details).
-
-  ``CMAKE_REQUIRED_LIBRARIES``
-    A :ref:`;-list <CMake Language Lists>` of libraries to add to the link
-    command. These can be the name of system libraries or they can be
-    :ref:`Imported Targets <Imported Targets>` (see :command:`try_compile` for
-    further details).
-
-  ``CMAKE_REQUIRED_QUIET``
-    If this variable evaluates to a boolean true value, all status messages
-    associated with the check will be suppressed.
-
-  The check is only performed once, with the result cached in the variable
-  named by ``<resultVar>``. Every subsequent CMake run will re-use this cached
-  value rather than performing the check again, even if the ``<code>`` changes.
-  In order to force the check to be re-evaluated, the variable named by
+  The check is only performed once, with the result cached in the variable named
+  by ``<resultVar>``. Every subsequent CMake run will re-use this cached value
+  rather than performing the check again, even if the ``<code>`` changes. In
+  order to force the check to be re-evaluated, the variable named by
   ``<resultVar>`` must be manually removed from the cache.
 
+  The compile and link commands can be influenced by setting any of the
+  following variables prior to calling ``check_objc_source_compiles()``
+
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
+
 #]=======================================================================]
 
 include_guard(GLOBAL)
diff --git a/Modules/CheckOBJCSourceRuns.cmake b/Modules/CheckOBJCSourceRuns.cmake
index dd03309..a23a0c7 100644
--- a/Modules/CheckOBJCSourceRuns.cmake
+++ b/Modules/CheckOBJCSourceRuns.cmake
@@ -23,47 +23,27 @@
   be set to 1, otherwise it will be set to an value that evaluates to boolean
   false (e.g. an empty string or an error message).
 
-  The underlying check is performed by the :command:`try_run` command. The
-  compile and link commands can be influenced by setting any of the following
-  variables prior to calling ``check_objc_source_runs()``:
-
-  ``CMAKE_REQUIRED_FLAGS``
-    Additional flags to pass to the compiler. Note that the contents of
-    :variable:`CMAKE_OBJC_FLAGS <CMAKE_<LANG>_FLAGS>` and its associated
-    configuration-specific variable are automatically added to the compiler
-    command before the contents of ``CMAKE_REQUIRED_FLAGS``.
-
-  ``CMAKE_REQUIRED_DEFINITIONS``
-    A :ref:`;-list <CMake Language Lists>` of compiler definitions of the form
-    ``-DFOO`` or ``-DFOO=bar``. A definition for the name specified by
-    ``<resultVar>`` will also be added automatically.
-
-  ``CMAKE_REQUIRED_INCLUDES``
-    A :ref:`;-list <CMake Language Lists>` of header search paths to pass to
-    the compiler. These will be the only header search paths used by
-    ``try_run()``, i.e. the contents of the :prop_dir:`INCLUDE_DIRECTORIES`
-    directory property will be ignored.
-
-  ``CMAKE_REQUIRED_LINK_OPTIONS``
-    A :ref:`;-list <CMake Language Lists>` of options to add to the link
-    command (see :command:`try_run` for further details).
-
-  ``CMAKE_REQUIRED_LIBRARIES``
-    A :ref:`;-list <CMake Language Lists>` of libraries to add to the link
-    command. These can be the name of system libraries or they can be
-    :ref:`Imported Targets <Imported Targets>` (see :command:`try_run` for
-    further details).
-
-  ``CMAKE_REQUIRED_QUIET``
-    If this variable evaluates to a boolean true value, all status messages
-    associated with the check will be suppressed.
-
-  The check is only performed once, with the result cached in the variable
-  named by ``<resultVar>``. Every subsequent CMake run will re-use this cached
-  value rather than performing the check again, even if the ``<code>`` changes.
-  In order to force the check to be re-evaluated, the variable named by
+  The check is only performed once, with the result cached in the variable named
+  by ``<resultVar>``. Every subsequent CMake run will re-use this cached value
+  rather than performing the check again, even if the ``<code>`` changes. In
+  order to force the check to be re-evaluated, the variable named by
   ``<resultVar>`` must be manually removed from the cache.
 
+  The compile and link commands can be influenced by setting any of the
+  following variables prior to calling ``check_objc_source_runs()``
+
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
+
 #]=======================================================================]
 
 include_guard(GLOBAL)
diff --git a/Modules/CheckOBJCXXCompilerFlag.cmake b/Modules/CheckOBJCXXCompilerFlag.cmake
index 3f3f8fe..47dacc2 100644
--- a/Modules/CheckOBJCXXCompilerFlag.cmake
+++ b/Modules/CheckOBJCXXCompilerFlag.cmake
@@ -13,29 +13,40 @@
 
   .. code-block:: cmake
 
-    check_objcxx_compiler_flag(<flag> <var>)
+    check_objcxx_compiler_flag(<flag> <resultVar>)
 
   Check that the ``<flag>`` is accepted by the compiler without
   a diagnostic.  Stores the result in an internal cache entry
-  named ``<var>``.
-
-This command temporarily sets the ``CMAKE_REQUIRED_DEFINITIONS`` variable
-and calls the ``check_objcxx_source_compiles`` macro from the
-:module:`CheckOBJCXXSourceCompiles` module.  See documentation of that
-module for a listing of variables that can otherwise modify the build.
+  named ``<resultVar>``.
 
 A positive result from this check indicates only that the compiler did not
 issue a diagnostic message when given the flag.  Whether the flag has any
 effect or even a specific one is beyond the scope of this module.
 
-.. note::
-  Since the :command:`try_compile` command forwards flags from variables
-  like :variable:`CMAKE_OBJCXX_FLAGS <CMAKE_<LANG>_FLAGS>`, unknown flags
-  in such variables may cause a false negative for this check.
+The check is only performed once, with the result cached in the variable named
+by ``<resultVar>``. Every subsequent CMake run will re-use this cached value
+rather than performing the check again, even if the ``<code>`` changes. In
+order to force the check to be re-evaluated, the variable named by
+``<resultVar>`` must be manually removed from the cache.
+
+The compile and link commands can be influenced by setting any of the
+following variables prior to calling ``check_objcxx_compiler_flag()``
+
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
+
 #]=======================================================================]
 
 include_guard(GLOBAL)
-include(CheckOBJCXXSourceCompiles)
 include(Internal/CheckCompilerFlag)
 
 macro (CHECK_OBJCXX_COMPILER_FLAG _FLAG _RESULT)
diff --git a/Modules/CheckOBJCXXSourceCompiles.cmake b/Modules/CheckOBJCXXSourceCompiles.cmake
index 1186934..cfbda3a 100644
--- a/Modules/CheckOBJCXXSourceCompiles.cmake
+++ b/Modules/CheckOBJCXXSourceCompiles.cmake
@@ -24,47 +24,27 @@
   checking if anything in the output matches any of the specified regular
   expressions.
 
-  The underlying check is performed by the :command:`try_compile` command. The
-  compile and link commands can be influenced by setting any of the following
-  variables prior to calling ``check_objcxx_source_compiles()``:
-
-  ``CMAKE_REQUIRED_FLAGS``
-    Additional flags to pass to the compiler. Note that the contents of
-    :variable:`CMAKE_OBJCXX_FLAGS <CMAKE_<LANG>_FLAGS>` and its associated
-    configuration-specific variable are automatically added to the compiler
-    command before the contents of ``CMAKE_REQUIRED_FLAGS``.
-
-  ``CMAKE_REQUIRED_DEFINITIONS``
-    A :ref:`;-list <CMake Language Lists>` of compiler definitions of the form
-    ``-DFOO`` or ``-DFOO=bar``. A definition for the name specified by
-    ``<resultVar>`` will also be added automatically.
-
-  ``CMAKE_REQUIRED_INCLUDES``
-    A :ref:`;-list <CMake Language Lists>` of header search paths to pass to
-    the compiler. These will be the only header search paths used by
-    ``try_compile()``, i.e. the contents of the :prop_dir:`INCLUDE_DIRECTORIES`
-    directory property will be ignored.
-
-  ``CMAKE_REQUIRED_LINK_OPTIONS``
-    A :ref:`;-list <CMake Language Lists>` of options to add to the link
-    command (see :command:`try_compile` for further details).
-
-  ``CMAKE_REQUIRED_LIBRARIES``
-    A :ref:`;-list <CMake Language Lists>` of libraries to add to the link
-    command. These can be the name of system libraries or they can be
-    :ref:`Imported Targets <Imported Targets>` (see :command:`try_compile` for
-    further details).
-
-  ``CMAKE_REQUIRED_QUIET``
-    If this variable evaluates to a boolean true value, all status messages
-    associated with the check will be suppressed.
-
-  The check is only performed once, with the result cached in the variable
-  named by ``<resultVar>``. Every subsequent CMake run will re-use this cached
-  value rather than performing the check again, even if the ``<code>`` changes.
-  In order to force the check to be re-evaluated, the variable named by
+  The check is only performed once, with the result cached in the variable named
+  by ``<resultVar>``. Every subsequent CMake run will re-use this cached value
+  rather than performing the check again, even if the ``<code>`` changes. In
+  order to force the check to be re-evaluated, the variable named by
   ``<resultVar>`` must be manually removed from the cache.
 
+  The compile and link commands can be influenced by setting any of the
+  following variables prior to calling ``check_objcxx_source_compiles()``
+
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
+
 #]=======================================================================]
 
 include_guard(GLOBAL)
diff --git a/Modules/CheckOBJCXXSourceRuns.cmake b/Modules/CheckOBJCXXSourceRuns.cmake
index 05a5e4c..b2831b6 100644
--- a/Modules/CheckOBJCXXSourceRuns.cmake
+++ b/Modules/CheckOBJCXXSourceRuns.cmake
@@ -23,47 +23,27 @@
   be set to 1, otherwise it will be set to an value that evaluates to boolean
   false (e.g. an empty string or an error message).
 
-  The underlying check is performed by the :command:`try_run` command. The
-  compile and link commands can be influenced by setting any of the following
-  variables prior to calling ``check_objcxx_source_runs()``:
-
-  ``CMAKE_REQUIRED_FLAGS``
-    Additional flags to pass to the compiler. Note that the contents of
-    :variable:`CMAKE_OBJCXX_FLAGS <CMAKE_<LANG>_FLAGS>` and its associated
-    configuration-specific variable are automatically added to the compiler
-    command before the contents of ``CMAKE_REQUIRED_FLAGS``.
-
-  ``CMAKE_REQUIRED_DEFINITIONS``
-    A :ref:`;-list <CMake Language Lists>` of compiler definitions of the form
-    ``-DFOO`` or ``-DFOO=bar``. A definition for the name specified by
-    ``<resultVar>`` will also be added automatically.
-
-  ``CMAKE_REQUIRED_INCLUDES``
-    A :ref:`;-list <CMake Language Lists>` of header search paths to pass to
-    the compiler. These will be the only header search paths used by
-    ``try_run()``, i.e. the contents of the :prop_dir:`INCLUDE_DIRECTORIES`
-    directory property will be ignored.
-
-  ``CMAKE_REQUIRED_LINK_OPTIONS``
-    A :ref:`;-list <CMake Language Lists>` of options to add to the link
-    command (see :command:`try_run` for further details).
-
-  ``CMAKE_REQUIRED_LIBRARIES``
-    A :ref:`;-list <CMake Language Lists>` of libraries to add to the link
-    command. These can be the name of system libraries or they can be
-    :ref:`Imported Targets <Imported Targets>` (see :command:`try_run` for
-    further details).
-
-  ``CMAKE_REQUIRED_QUIET``
-    If this variable evaluates to a boolean true value, all status messages
-    associated with the check will be suppressed.
-
-  The check is only performed once, with the result cached in the variable
-  named by ``<resultVar>``. Every subsequent CMake run will re-use this cached
-  value rather than performing the check again, even if the ``<code>`` changes.
-  In order to force the check to be re-evaluated, the variable named by
+  The check is only performed once, with the result cached in the variable named
+  by ``<resultVar>``. Every subsequent CMake run will re-use this cached value
+  rather than performing the check again, even if the ``<code>`` changes. In
+  order to force the check to be re-evaluated, the variable named by
   ``<resultVar>`` must be manually removed from the cache.
 
+  The compile and link commands can be influenced by setting any of the
+  following variables prior to calling ``check_objcxx_source_runs()``
+
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
+
 #]=======================================================================]
 
 include_guard(GLOBAL)
diff --git a/Modules/CheckPrototypeDefinition.cmake b/Modules/CheckPrototypeDefinition.cmake
index 3d53b93..c1a7a1c 100644
--- a/Modules/CheckPrototypeDefinition.cmake
+++ b/Modules/CheckPrototypeDefinition.cmake
@@ -35,20 +35,18 @@
 The following variables may be set before calling this function to modify
 the way the check is run:
 
-``CMAKE_REQUIRED_FLAGS``
-  string of compile command line flags.
-``CMAKE_REQUIRED_DEFINITIONS``
-  list of macros to define (-DFOO=bar).
-``CMAKE_REQUIRED_INCLUDES``
-  list of include directories.
-``CMAKE_REQUIRED_LINK_OPTIONS``
-  .. versionadded:: 3.14
-    list of options to pass to link command.
-``CMAKE_REQUIRED_LIBRARIES``
-  list of libraries to link.
-``CMAKE_REQUIRED_QUIET``
-  .. versionadded:: 3.1
-    execute quietly without messages.
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
+
 #]=======================================================================]
 
 #
diff --git a/Modules/CheckSourceCompiles.cmake b/Modules/CheckSourceCompiles.cmake
index 9788798..041b59c 100644
--- a/Modules/CheckSourceCompiles.cmake
+++ b/Modules/CheckSourceCompiles.cmake
@@ -47,47 +47,27 @@
     end program"
     HAVE_ERROR_STOP)
 
-  The underlying check is performed by the :command:`try_compile` command. The
-  compile and link commands can be influenced by setting any of the following
-  variables prior to calling ``check_source_compiles()``:
-
-  ``CMAKE_REQUIRED_FLAGS``
-    Additional flags to pass to the compiler. Note that the contents of
-    :variable:`CMAKE_<LANG>_FLAGS <CMAKE_<LANG>_FLAGS>` and its associated
-    configuration-specific variable are automatically added to the compiler
-    command before the contents of ``CMAKE_REQUIRED_FLAGS``.
-
-  ``CMAKE_REQUIRED_DEFINITIONS``
-    A :ref:`;-list <CMake Language Lists>` of compiler definitions of the form
-    ``-DFOO`` or ``-DFOO=bar``. A definition for the name specified by
-    ``<resultVar>`` will also be added automatically.
-
-  ``CMAKE_REQUIRED_INCLUDES``
-    A :ref:`;-list <CMake Language Lists>` of header search paths to pass to
-    the compiler. These will be the only header search paths used by
-    ``try_compile()``, i.e. the contents of the :prop_dir:`INCLUDE_DIRECTORIES`
-    directory property will be ignored.
-
-  ``CMAKE_REQUIRED_LINK_OPTIONS``
-    A :ref:`;-list <CMake Language Lists>` of options to add to the link
-    command (see :command:`try_compile` for further details).
-
-  ``CMAKE_REQUIRED_LIBRARIES``
-    A :ref:`;-list <CMake Language Lists>` of libraries to add to the link
-    command. These can be the name of system libraries or they can be
-    :ref:`Imported Targets <Imported Targets>` (see :command:`try_compile` for
-    further details).
-
-  ``CMAKE_REQUIRED_QUIET``
-    If this variable evaluates to a boolean true value, all status messages
-    associated with the check will be suppressed.
-
   The check is only performed once, with the result cached in the variable
   named by ``<resultVar>``. Every subsequent CMake run will re-use this cached
   value rather than performing the check again, even if the ``<code>`` changes.
   In order to force the check to be re-evaluated, the variable named by
   ``<resultVar>`` must be manually removed from the cache.
 
+  The compile and link commands can be influenced by setting any of the
+  following variables prior to calling ``check_source_compiles()``:
+
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
+
 #]=======================================================================]
 
 include_guard(GLOBAL)
diff --git a/Modules/CheckSourceRuns.cmake b/Modules/CheckSourceRuns.cmake
index e2fa579..822ee07 100644
--- a/Modules/CheckSourceRuns.cmake
+++ b/Modules/CheckSourceRuns.cmake
@@ -47,47 +47,27 @@
     end program"
     HAVE_COARRAY)
 
-  The underlying check is performed by the :command:`try_run` command. The
-  compile and link commands can be influenced by setting any of the following
-  variables prior to calling ``check_source_runs()``:
-
-  ``CMAKE_REQUIRED_FLAGS``
-    Additional flags to pass to the compiler. Note that the contents of
-    :variable:`CMAKE_<LANG>_FLAGS <CMAKE_<LANG>_FLAGS>` and its associated
-    configuration-specific variable are automatically added to the compiler
-    command before the contents of ``CMAKE_REQUIRED_FLAGS``.
-
-  ``CMAKE_REQUIRED_DEFINITIONS``
-    A :ref:`;-list <CMake Language Lists>` of compiler definitions of the form
-    ``-DFOO`` or ``-DFOO=bar``. A definition for the name specified by
-    ``<resultVar>`` will also be added automatically.
-
-  ``CMAKE_REQUIRED_INCLUDES``
-    A :ref:`;-list <CMake Language Lists>` of header search paths to pass to
-    the compiler. These will be the only header search paths used by
-    ``try_run()``, i.e. the contents of the :prop_dir:`INCLUDE_DIRECTORIES`
-    directory property will be ignored.
-
-  ``CMAKE_REQUIRED_LINK_OPTIONS``
-    A :ref:`;-list <CMake Language Lists>` of options to add to the link
-    command (see :command:`try_run` for further details).
-
-  ``CMAKE_REQUIRED_LIBRARIES``
-    A :ref:`;-list <CMake Language Lists>` of libraries to add to the link
-    command. These can be the name of system libraries or they can be
-    :ref:`Imported Targets <Imported Targets>` (see :command:`try_run` for
-    further details).
-
-  ``CMAKE_REQUIRED_QUIET``
-    If this variable evaluates to a boolean true value, all status messages
-    associated with the check will be suppressed.
-
-  The check is only performed once, with the result cached in the variable
-  named by ``<resultVar>``. Every subsequent CMake run will re-use this cached
-  value rather than performing the check again, even if the ``<code>`` changes.
-  In order to force the check to be re-evaluated, the variable named by
+  The check is only performed once, with the result cached in the variable named
+  by ``<resultVar>``. Every subsequent CMake run will re-use this cached value
+  rather than performing the check again, even if the ``<code>`` changes. In
+  order to force the check to be re-evaluated, the variable named by
   ``<resultVar>`` must be manually removed from the cache.
 
+  The compile and link commands can be influenced by setting any of the
+  following variables prior to calling ``check_source_runs()``
+
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
+
 #]=======================================================================]
 
 include_guard(GLOBAL)
diff --git a/Modules/CheckStructHasMember.cmake b/Modules/CheckStructHasMember.cmake
index 8217c84..81ea9fd 100644
--- a/Modules/CheckStructHasMember.cmake
+++ b/Modules/CheckStructHasMember.cmake
@@ -26,20 +26,17 @@
 The following variables may be set before calling this macro to modify
 the way the check is run:
 
-``CMAKE_REQUIRED_FLAGS``
-  string of compile command line flags.
-``CMAKE_REQUIRED_DEFINITIONS``
-  list of macros to define (-DFOO=bar).
-``CMAKE_REQUIRED_INCLUDES``
-  list of include directories.
-``CMAKE_REQUIRED_LINK_OPTIONS``
-  .. versionadded:: 3.14
-    list of options to pass to link command.
-``CMAKE_REQUIRED_LIBRARIES``
-  list of libraries to link.
-``CMAKE_REQUIRED_QUIET``
-  .. versionadded:: 3.1
-    execute quietly without messages.
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
 
 
 Example:
@@ -51,8 +48,7 @@
 #]=======================================================================]
 
 include_guard(GLOBAL)
-include(CheckCSourceCompiles)
-include(CheckCXXSourceCompiles)
+include(CheckSourceCompiles)
 
 macro (CHECK_STRUCT_HAS_MEMBER _STRUCT _MEMBER _HEADER _RESULT)
   set(_INCLUDE_FILES)
@@ -78,9 +74,9 @@
 ")
 
   if("${_lang}" STREQUAL "C")
-    CHECK_C_SOURCE_COMPILES("${_CHECK_STRUCT_MEMBER_SOURCE_CODE}" ${_RESULT})
+    CHECK_SOURCE_COMPILES(C "${_CHECK_STRUCT_MEMBER_SOURCE_CODE}" ${_RESULT})
   elseif("${_lang}" STREQUAL "CXX")
-    CHECK_CXX_SOURCE_COMPILES("${_CHECK_STRUCT_MEMBER_SOURCE_CODE}" ${_RESULT})
+    CHECK_SOURCE_COMPILES(CXX "${_CHECK_STRUCT_MEMBER_SOURCE_CODE}" ${_RESULT})
   else()
     message(FATAL_ERROR "Unknown language:\n  ${_lang}\nSupported languages: C, CXX.\n")
   endif()
diff --git a/Modules/CheckSymbolExists.cmake b/Modules/CheckSymbolExists.cmake
index c4a1574..931ed4a 100644
--- a/Modules/CheckSymbolExists.cmake
+++ b/Modules/CheckSymbolExists.cmake
@@ -31,22 +31,17 @@
 The following variables may be set before calling this macro to modify
 the way the check is run:
 
-``CMAKE_REQUIRED_FLAGS``
-  string of compile command line flags.
-``CMAKE_REQUIRED_DEFINITIONS``
-  a :ref:`;-list <CMake Language Lists>` of macros to define (-DFOO=bar).
-``CMAKE_REQUIRED_INCLUDES``
-  a :ref:`;-list <CMake Language Lists>` of header search paths to pass to
-  the compiler.
-``CMAKE_REQUIRED_LINK_OPTIONS``
-  .. versionadded:: 3.14
-    a :ref:`;-list <CMake Language Lists>` of options to add to the link command.
-``CMAKE_REQUIRED_LIBRARIES``
-  a :ref:`;-list <CMake Language Lists>` of libraries to add to the link
-  command. See policy :policy:`CMP0075`.
-``CMAKE_REQUIRED_QUIET``
-  .. versionadded:: 3.1
-    execute quietly without messages.
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
 
 For example:
 
@@ -62,7 +57,7 @@
 
 include_guard(GLOBAL)
 
-cmake_policy(PUSH)
+block(SCOPE_FOR POLICIES)
 cmake_policy(SET CMP0054 NEW) # if() quoted variables not dereferenced
 
 macro(CHECK_SYMBOL_EXISTS SYMBOL FILES VARIABLE)
@@ -166,4 +161,4 @@
   endif()
 endmacro()
 
-cmake_policy(POP)
+endblock()
diff --git a/Modules/CheckTypeSize.cmake b/Modules/CheckTypeSize.cmake
index 579d189..01ce1d2 100644
--- a/Modules/CheckTypeSize.cmake
+++ b/Modules/CheckTypeSize.cmake
@@ -67,20 +67,18 @@
 The following variables may be set before calling this macro to modify
 the way the check is run:
 
-``CMAKE_REQUIRED_FLAGS``
-  string of compile command line flags.
-``CMAKE_REQUIRED_DEFINITIONS``
-  list of macros to define (-DFOO=bar).
-``CMAKE_REQUIRED_INCLUDES``
-  list of include directories.
-``CMAKE_REQUIRED_LINK_OPTIONS``
-  .. versionadded:: 3.14
-    list of options to pass to link command.
-``CMAKE_REQUIRED_LIBRARIES``
-  list of libraries to link.
-``CMAKE_REQUIRED_QUIET``
-  .. versionadded:: 3.1
-    execute quietly without messages.
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_INCLUDES.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
+
 ``CMAKE_EXTRA_INCLUDE_FILES``
   list of extra headers to include.
 #]=======================================================================]
@@ -92,7 +90,7 @@
 
 include_guard(GLOBAL)
 
-cmake_policy(PUSH)
+block(SCOPE_FOR POLICIES)
 cmake_policy(SET CMP0054 NEW)
 
 #-----------------------------------------------------------------------------
@@ -294,4 +292,4 @@
 endmacro()
 
 #-----------------------------------------------------------------------------
-cmake_policy(POP)
+endblock()
diff --git a/Modules/CheckVariableExists.cmake b/Modules/CheckVariableExists.cmake
index 3a7a431..9e5d710 100644
--- a/Modules/CheckVariableExists.cmake
+++ b/Modules/CheckVariableExists.cmake
@@ -26,18 +26,16 @@
 The following variables may be set before calling this macro to modify
 the way the check is run:
 
-``CMAKE_REQUIRED_FLAGS``
-  string of compile command line flags.
-``CMAKE_REQUIRED_DEFINITIONS``
-  list of macros to define (-DFOO=bar).
-``CMAKE_REQUIRED_LINK_OPTIONS``
-  .. versionadded:: 3.14
-    list of options to pass to link command.
-``CMAKE_REQUIRED_LIBRARIES``
-  list of libraries to link.
-``CMAKE_REQUIRED_QUIET``
-  .. versionadded:: 3.1
-    execute quietly without messages.
+.. include:: /module/CMAKE_REQUIRED_FLAGS.txt
+
+.. include:: /module/CMAKE_REQUIRED_DEFINITIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LINK_OPTIONS.txt
+
+.. include:: /module/CMAKE_REQUIRED_LIBRARIES.txt
+
+.. include:: /module/CMAKE_REQUIRED_QUIET.txt
+
 #]=======================================================================]
 
 include_guard(GLOBAL)
diff --git a/Modules/Compiler/Clang-FindBinUtils.cmake b/Modules/Compiler/Clang-FindBinUtils.cmake
index daf0371..f4f35e6 100644
--- a/Modules/Compiler/Clang-FindBinUtils.cmake
+++ b/Modules/Compiler/Clang-FindBinUtils.cmake
@@ -26,6 +26,7 @@
 find_program(CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER_AR NAMES
     "${_CMAKE_TOOLCHAIN_PREFIX}llvm-ar-${__version_x_y}"
     "${_CMAKE_TOOLCHAIN_PREFIX}llvm-ar-${__version_x}"
+    "${_CMAKE_TOOLCHAIN_PREFIX}llvm-ar${__version_x}"
     "${_CMAKE_TOOLCHAIN_PREFIX}llvm-ar"
     HINTS ${__clang_hints}
     NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH
@@ -37,6 +38,7 @@
 find_program(CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER_RANLIB NAMES
     "${_CMAKE_TOOLCHAIN_PREFIX}llvm-ranlib-${__version_x_y}"
     "${_CMAKE_TOOLCHAIN_PREFIX}llvm-ranlib-${__version_x}"
+    "${_CMAKE_TOOLCHAIN_PREFIX}llvm-ranlib${__version_x}"
     "${_CMAKE_TOOLCHAIN_PREFIX}llvm-ranlib"
     HINTS ${__clang_hints}
     NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH
@@ -48,6 +50,7 @@
 find_program(CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER_CLANG_SCAN_DEPS NAMES
     "${_CMAKE_TOOLCHAIN_PREFIX}clang-scan-deps-${__version_x_y}"
     "${_CMAKE_TOOLCHAIN_PREFIX}clang-scan-deps-${__version_x}"
+    "${_CMAKE_TOOLCHAIN_PREFIX}clang-scan-deps${__version_x}"
     "${_CMAKE_TOOLCHAIN_PREFIX}clang-scan-deps"
     HINTS ${__clang_hints}
     NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH
diff --git a/Modules/Compiler/Clang-HIP.cmake b/Modules/Compiler/Clang-HIP.cmake
index 4dbe2e8..92925f1 100644
--- a/Modules/Compiler/Clang-HIP.cmake
+++ b/Modules/Compiler/Clang-HIP.cmake
@@ -1,4 +1,5 @@
 include(Compiler/Clang)
+
 __compiler_clang(HIP)
 __compiler_clang_cxx_standards(HIP)
 
diff --git a/Modules/Compiler/Clang.cmake b/Modules/Compiler/Clang.cmake
index 6c544fd..46f5fc1 100644
--- a/Modules/Compiler/Clang.cmake
+++ b/Modules/Compiler/Clang.cmake
@@ -173,7 +173,12 @@
 
     unset(_clang_version_std17)
 
-    if(NOT CMAKE_${lang}_COMPILER_VERSION VERSION_LESS 12.0)
+    if(NOT CMAKE_${lang}_COMPILER_VERSION VERSION_LESS 17.0)
+      set(CMAKE_${lang}23_STANDARD_COMPILE_OPTION "-std=c++23")
+      set(CMAKE_${lang}23_EXTENSION_COMPILE_OPTION "-std=gnu++23")
+      set(CMAKE_${lang}26_STANDARD_COMPILE_OPTION "-std=c++26")
+      set(CMAKE_${lang}26_EXTENSION_COMPILE_OPTION "-std=gnu++26")
+    elseif(NOT CMAKE_${lang}_COMPILER_VERSION VERSION_LESS 12.0)
       set(CMAKE_${lang}23_STANDARD_COMPILE_OPTION "-std=c++2b")
       set(CMAKE_${lang}23_EXTENSION_COMPILE_OPTION "-std=gnu++2b")
     endif()
diff --git a/Modules/Compiler/GNU-FindBinUtils.cmake b/Modules/Compiler/GNU-FindBinUtils.cmake
index 4dcdd53..3941715 100644
--- a/Modules/Compiler/GNU-FindBinUtils.cmake
+++ b/Modules/Compiler/GNU-FindBinUtils.cmake
@@ -18,6 +18,7 @@
 find_program(CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER_AR NAMES
     "${_CMAKE_TOOLCHAIN_PREFIX}gcc-ar-${__version_x_y}"
     "${_CMAKE_TOOLCHAIN_PREFIX}gcc-ar-${__version_x}"
+    "${_CMAKE_TOOLCHAIN_PREFIX}gcc-ar${__version_x}"
     "${_CMAKE_TOOLCHAIN_PREFIX}gcc-ar${_CMAKE_COMPILER_SUFFIX}"
     HINTS ${__gcc_hints}
     NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH
@@ -29,6 +30,7 @@
 find_program(CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER_RANLIB NAMES
     "${_CMAKE_TOOLCHAIN_PREFIX}gcc-ranlib-${__version_x_y}"
     "${_CMAKE_TOOLCHAIN_PREFIX}gcc-ranlib-${__version_x}"
+    "${_CMAKE_TOOLCHAIN_PREFIX}gcc-ranlib${__version_x}"
     "${_CMAKE_TOOLCHAIN_PREFIX}gcc-ranlib${_CMAKE_COMPILER_SUFFIX}"
     HINTS ${__gcc_hints}
     NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH
diff --git a/Modules/Compiler/GNU.cmake b/Modules/Compiler/GNU.cmake
index 5930e37..251e05a 100644
--- a/Modules/Compiler/GNU.cmake
+++ b/Modules/Compiler/GNU.cmake
@@ -52,6 +52,45 @@
     set(CMAKE_DEPFILE_FLAGS_${lang} "-MD -MT <DEP_TARGET> -MF <DEP_FILE>")
   endif()
 
+  # define flags for linker depfile generation
+  if(NOT DEFINED CMAKE_${lang}_LINKER_DEPFILE_SUPPORTED)
+    ## Ensure ninja tool is recent enough...
+    if(CMAKE_GENERATOR MATCHES "^Ninja")
+      # Ninja 1.10 or upper is required
+      execute_process(COMMAND "${CMAKE_MAKE_PROGRAM}" --version
+        OUTPUT_VARIABLE _ninja_version
+        ERROR_VARIABLE _ninja_version)
+      if (_ninja_version MATCHES "[0-9]+(\\.[0-9]+)*")
+        set (_ninja_version "${CMAKE_MATCH_0}")
+      endif()
+      if (_ninja_version VERSION_LESS "1.10")
+        set(CMAKE_${lang}_LINKER_DEPFILE_SUPPORTED FALSE)
+      endif()
+      unset(_ninja_version)
+    endif()
+
+    if (NOT DEFINED CMAKE_${lang}_LINKER_DEPFILE_SUPPORTED)
+      ## check if this feature is supported by the linker
+      execute_process(COMMAND "${CMAKE_${lang}_COMPILER}" -Wl,--help
+        OUTPUT_VARIABLE _linker_capabilities
+        ERROR_VARIABLE _linker_capabilities)
+      if(_linker_capabilities MATCHES "--dependency-file")
+        set(CMAKE_${lang}_LINKER_DEPFILE_SUPPORTED TRUE)
+      else()
+        set(CMAKE_${lang}_LINKER_DEPFILE_SUPPORTED FALSE)
+      endif()
+      unset(_linker_capabilities)
+    endif()
+  endif()
+
+  if (CMAKE_${lang}_LINKER_DEPFILE_SUPPORTED)
+    set(CMAKE_${lang}_LINKER_DEPFILE_FLAGS "LINKER:--dependency-file,<DEP_FILE>")
+    set(CMAKE_${lang}_LINKER_DEPFILE_FORMAT gcc)
+    set(CMAKE_${lang}_LINK_DEPENDS_USE_LINKER TRUE)
+  else()
+    unset(CMAKE_${lang}_LINK_DEPENDS_USE_LINKER)
+  endif()
+
   # Initial configuration flags.
   string(APPEND CMAKE_${lang}_FLAGS_INIT " ")
   string(APPEND CMAKE_${lang}_FLAGS_DEBUG_INIT " -g")
diff --git a/Modules/Compiler/IAR-ASM.cmake b/Modules/Compiler/IAR-ASM.cmake
index bae0fbd..5f1dac0 100644
--- a/Modules/Compiler/IAR-ASM.cmake
+++ b/Modules/Compiler/IAR-ASM.cmake
@@ -2,20 +2,73 @@
 
 include(Compiler/IAR)
 
-cmake_policy(PUSH)
-cmake_policy(SET CMP0057 NEW) # if IN_LIST
-
-set(_CMAKE_IAR_ITOOLS "ARM" "RH850"  "RL78" "RX" "RISC-V" "STM8")
-set(_CMAKE_IAR_XTOOLS "AVR" "MSP430" "V850" "8051")
-
-set(_CMAKE_IAR_ASM_SILENT   "RH850"  "RL78" "RX" "RISC-V" "STM8")
-if("${CMAKE_ASM${ASM_DIALECT}_COMPILER_ARCHITECTURE_ID}" IN_LIST _CMAKE_IAR_ASM_SILENT)
-  set(_CMAKE_IAR_SILENCER_FLAG " --silent")
-else()
+# Architecture specific
+if("${CMAKE_ASM${ASM_DIALECT}_COMPILER_ARCHITECTURE_ID}" STREQUAL "ARM")
+  __compiler_iar_ilink(ASM)
+  __assembler_iar_deps("-y" 9.30)
   set(_CMAKE_IAR_SILENCER_FLAG " -S")
+  set(CMAKE_ASM_SOURCE_FILE_EXTENSIONS s;asm;msa;S)
+
+elseif("${CMAKE_ASM${ASM_DIALECT}_COMPILER_ARCHITECTURE_ID}" STREQUAL "RX")
+  __compiler_iar_ilink(ASM)
+  __assembler_iar_deps("--dependencies=ns" 2.50.1)
+  set(_CMAKE_IAR_SILENCER_FLAG " --silent")
+  set(CMAKE_ASM_SOURCE_FILE_EXTENSIONS s;asm;msa;S)
+
+elseif("${CMAKE_ASM${ASM_DIALECT}_COMPILER_ARCHITECTURE_ID}" STREQUAL "RH850")
+  __compiler_iar_ilink(ASM)
+  __assembler_iar_deps("--dependencies=ns" 2)
+  set(_CMAKE_IAR_SILENCER_FLAG " --silent")
+  set(CMAKE_ASM_SOURCE_FILE_EXTENSIONS s;asm;msa;S)
+
+elseif("${CMAKE_ASM${ASM_DIALECT}_COMPILER_ARCHITECTURE_ID}" STREQUAL "RL78")
+  __compiler_iar_ilink(ASM)
+  __assembler_iar_deps("--dependencies=ns" 2)
+  set(_CMAKE_IAR_SILENCER_FLAG " --silent")
+  set(CMAKE_ASM_SOURCE_FILE_EXTENSIONS s;asm;msa;S)
+
+elseif("${CMAKE_ASM${ASM_DIALECT}_COMPILER_ARCHITECTURE_ID}" MATCHES "(RISCV|RISC-V)")
+  __compiler_iar_ilink(ASM)
+  __assembler_iar_deps("--dependencies=ns" 1)
+  set(_CMAKE_IAR_SILENCER_FLAG " --silent")
+  set(CMAKE_ASM_SOURCE_FILE_EXTENSIONS s;asm;msa;S)
+
+elseif("${CMAKE_ASM${ASM_DIALECT}_COMPILER_ARCHITECTURE_ID}" STREQUAL "AVR")
+  __compiler_iar_xlink(ASM)
+  __assembler_iar_deps("-y" 8)
+  set(_CMAKE_IAR_SILENCER_FLAG " -S")
+  set(CMAKE_ASM_SOURCE_FILE_EXTENSIONS s90;asm;msa)
+  set(CMAKE_ASM_OUTPUT_EXTENSION ".r90")
+
+elseif("${CMAKE_ASM${ASM_DIALECT}_COMPILER_ARCHITECTURE_ID}" STREQUAL "MSP430")
+  __compiler_iar_xlink(ASM)
+  __assembler_iar_deps("-y" 8)
+  set(_CMAKE_IAR_SILENCER_FLAG " -S")
+  set(CMAKE_ASM_SOURCE_FILE_EXTENSIONS s43;asm;msa)
+  set(CMAKE_ASM_OUTPUT_EXTENSION ".r43")
+
+elseif("${CMAKE_ASM${ASM_DIALECT}_COMPILER_ARCHITECTURE_ID}" STREQUAL "V850")
+  __compiler_iar_xlink(ASM)
+  set(_CMAKE_IAR_SILENCER_FLAG " -S")
+  set(CMAKE_ASM_SOURCE_FILE_EXTENSIONS s85;asm;msa)
+  set(CMAKE_ASM_OUTPUT_EXTENSION ".r85")
+
+elseif("${CMAKE_ASM${ASM_DIALECT}_COMPILER_ARCHITECTURE_ID}" STREQUAL "8051")
+  __compiler_iar_xlink(ASM)
+  set(_CMAKE_IAR_SILENCER_FLAG " -S")
+  set(CMAKE_ASM_SOURCE_FILE_EXTENSIONS s51;asm;msa)
+  set(CMAKE_ASM_OUTPUT_EXTENSION ".r51")
+
+elseif("${CMAKE_ASM${ASM_DIALECT}_COMPILER_ARCHITECTURE_ID}" STREQUAL "STM8")
+  __compiler_iar_ilink(ASM)
+  __assembler_iar_deps("--dependencies=ns" 2)
+  set(_CMAKE_IAR_SILENCER_FLAG " --silent")
+  set(CMAKE_ASM_SOURCE_FILE_EXTENSIONS s;asm;msa;S)
+
+else()
+  message(FATAL_ERROR "CMAKE_ASM${ASM_DIALECT}_COMPILER_ARCHITECTURE_ID not detected. This should be automatic." )
 endif()
 
-string(APPEND CMAKE_ASM_FLAGS_INIT " ")
 string(APPEND CMAKE_ASM_FLAGS_DEBUG_INIT " -r")
 string(APPEND CMAKE_ASM_FLAGS_MINSIZEREL_INIT " -DNDEBUG")
 string(APPEND CMAKE_ASM_FLAGS_RELEASE_INIT " -DNDEBUG")
@@ -23,23 +76,4 @@
 
 set(CMAKE_ASM_COMPILE_OBJECT "<CMAKE_ASM_COMPILER> ${_CMAKE_IAR_SILENCER_FLAG} <SOURCE> <DEFINES> <INCLUDES> <FLAGS> -o <OBJECT>")
 
-if("${CMAKE_ASM${ASM_DIALECT}_COMPILER_ARCHITECTURE_ID}" IN_LIST _CMAKE_IAR_ITOOLS)
-  __compiler_iar_ilink(ASM)
-  set(CMAKE_ASM_SOURCE_FILE_EXTENSIONS s;asm;msa;S)
-
-elseif("${CMAKE_ASM${ASM_DIALECT}_COMPILER_ARCHITECTURE_ID}" IN_LIST _CMAKE_IAR_XTOOLS)
-  __compiler_iar_xlink(ASM)
-  # AVR=s90, MSP430=s43, V850=s85, 8051=s51
-  set(CMAKE_ASM_SOURCE_FILE_EXTENSIONS s90;s43;s85;s51;asm;msa)
-
-else()
-  message(FATAL_ERROR "CMAKE_ASM${ASM_DIALECT}_COMPILER_ARCHITECTURE_ID not detected. This should be automatic.")
-
-endif()
-
-unset(_CMAKE_IAR_ITOOLS)
-unset(_CMAKE_IAR_XTOOLS)
-unset(_CMAKE_IAR_ASM_SILENT)
 unset(_CMAKE_IAR_SILENCER_FLAG)
-
-cmake_policy(POP)
diff --git a/Modules/Compiler/IAR-C.cmake b/Modules/Compiler/IAR-C.cmake
index 0ef1a2a..c4907c5 100644
--- a/Modules/Compiler/IAR-C.cmake
+++ b/Modules/Compiler/IAR-C.cmake
@@ -59,7 +59,7 @@
 
 elseif("${CMAKE_C_COMPILER_ARCHITECTURE_ID}" STREQUAL "AVR")
   __compiler_iar_xlink(C)
-  __compiler_check_default_language_standard(C 7.10 99)
+  __compiler_check_default_language_standard(C 7.10 99 8.10 17)
   set(CMAKE_C_OUTPUT_EXTENSION ".r90")
 
 elseif("${CMAKE_C_COMPILER_ARCHITECTURE_ID}" STREQUAL "MSP430")
diff --git a/Modules/Compiler/IAR-CXX.cmake b/Modules/Compiler/IAR-CXX.cmake
index 3f0ef1f..b598e36 100644
--- a/Modules/Compiler/IAR-CXX.cmake
+++ b/Modules/Compiler/IAR-CXX.cmake
@@ -68,7 +68,8 @@
 
 elseif("${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}" STREQUAL "AVR")
   __compiler_iar_xlink(CXX)
-  __compiler_check_default_language_standard(CXX 7.10 98)
+  __compiler_check_default_language_standard(CXX 7.10 98 8.10 17)
+  set(CMAKE_CXX_OUTPUT_EXTENSION ".r90")
 
 elseif("${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}" STREQUAL "MSP430")
   __compiler_iar_xlink(CXX)
@@ -78,12 +79,12 @@
 elseif("${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}" STREQUAL "V850")
   __compiler_iar_xlink(CXX)
   __compiler_check_default_language_standard(CXX 1.10 98)
-  set(CMAKE_C_OUTPUT_EXTENSION ".r85")
+  set(CMAKE_CXX_OUTPUT_EXTENSION ".r85")
 
 elseif("${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}" STREQUAL "8051")
   __compiler_iar_xlink(CXX)
   __compiler_check_default_language_standard(CXX 6.10 98)
-  set(CMAKE_C_OUTPUT_EXTENSION ".r51")
+  set(CMAKE_CXX_OUTPUT_EXTENSION ".r51")
 
 elseif("${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}" STREQUAL "STM8")
   __compiler_iar_ilink(CXX)
diff --git a/Modules/Compiler/IAR.cmake b/Modules/Compiler/IAR.cmake
index 7908f96..8dadb76 100644
--- a/Modules/Compiler/IAR.cmake
+++ b/Modules/Compiler/IAR.cmake
@@ -1,6 +1,6 @@
 # This file is processed when the IAR C/C++ Compiler is used
 #
-# CPU <arch> supported in CMake: 8051, Arm, AVR, MSP430, RH850, RISC-V, RL78, RX and V850
+# CPU <arch> supported in CMake: 8051, Arm, AVR, MSP430, RH850, RISC-V, RL78, RX, STM8 and V850
 #
 # The compiler user documentation is architecture-dependent
 # and it can found with the product installation under <arch>/doc/{EW,BX}<arch>_DevelopmentGuide.ENU.pdf
@@ -9,7 +9,7 @@
 include_guard()
 
 macro(__compiler_iar_common lang)
-  if (${lang} MATCHES "^(C|CXX)$")
+  if ("x${lang}" MATCHES "^x(C|CXX)$")
     set(CMAKE_${lang}_COMPILE_OBJECT             "<CMAKE_${lang}_COMPILER> ${CMAKE_IAR_${lang}_FLAG} --silent <SOURCE> <DEFINES> <INCLUDES> <FLAGS> -o <OBJECT>")
     set(CMAKE_${lang}_CREATE_PREPROCESSED_SOURCE "<CMAKE_${lang}_COMPILER> ${CMAKE_IAR_${lang}_FLAG} --silent <SOURCE> <DEFINES> <INCLUDES> <FLAGS> --preprocess=cnl <PREPROCESSED_SOURCE>")
     set(CMAKE_${lang}_CREATE_ASSEMBLY_SOURCE     "<CMAKE_${lang}_COMPILER> ${CMAKE_IAR_${lang}_FLAG} --silent <SOURCE> <DEFINES> <INCLUDES> <FLAGS> -lAH <ASSEMBLY_SOURCE> -o <OBJECT>.dummy")
@@ -23,6 +23,7 @@
     string(APPEND CMAKE_${lang}_FLAGS_RELWITHDEBINFO_INIT " -Oh -r -DNDEBUG")
   endif()
 
+  set(CMAKE_${lang}_OUTPUT_EXTENSION_REPLACE 1)
   set(CMAKE_${lang}_RESPONSE_FILE_FLAG "-f ")
   set(CMAKE_${lang}_RESPONSE_FILE_LINK_FLAG "-f ")
 
@@ -35,7 +36,9 @@
 
   __compiler_iar_common(${lang})
 
-  set(CMAKE_${lang}_LINK_EXECUTABLE "<CMAKE_LINKER> --silent <OBJECTS> <CMAKE_${lang}_LINK_FLAGS> <LINK_FLAGS> <LINK_LIBRARIES> -o <TARGET>")
+  string(APPEND CMAKE_EXE_LINKER_FLAGS_INIT " --silent")
+  set(CMAKE_${lang}_LINK_EXECUTABLE "<CMAKE_LINKER> <OBJECTS> <CMAKE_${lang}_LINK_FLAGS> <LINK_FLAGS> <LINK_LIBRARIES> -o <TARGET>")
+
   set(CMAKE_${lang}_CREATE_STATIC_LIBRARY "<CMAKE_AR> <TARGET> --create <LINK_FLAGS> <OBJECTS>")
   set(CMAKE_${lang}_ARCHIVE_CREATE "<CMAKE_AR> <TARGET> --create <LINK_FLAGS> <OBJECTS>")
   set(CMAKE_${lang}_ARCHIVE_APPEND "<CMAKE_AR> <TARGET> --replace <LINK_FLAGS> <OBJECTS>")
@@ -46,10 +49,18 @@
 
   __compiler_iar_common(${lang})
 
-  set(CMAKE_${lang}_LINK_EXECUTABLE "<CMAKE_LINKER> -S <OBJECTS> <CMAKE_${lang}_LINK_FLAGS> <LINK_FLAGS> <LINK_LIBRARIES> -o <TARGET>")
+  string(APPEND CMAKE_EXE_LINKER_FLAGS_INIT " -S")
+  set(CMAKE_${lang}_LINK_EXECUTABLE "<CMAKE_LINKER> <OBJECTS> <CMAKE_${lang}_LINK_FLAGS> <LINK_FLAGS> <LINK_LIBRARIES> -o <TARGET>")
+
   set(CMAKE_${lang}_CREATE_STATIC_LIBRARY "<CMAKE_AR> <TARGET> <LINK_FLAGS> <OBJECTS>")
   set(CMAKE_${lang}_ARCHIVE_CREATE "<CMAKE_AR> <TARGET> <LINK_FLAGS> <OBJECTS>")
   set(CMAKE_${lang}_ARCHIVE_APPEND "")
 
   set(CMAKE_LIBRARY_PATH_FLAG "-I")
 endmacro()
+
+macro(__assembler_iar_deps flag min_version)
+  if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL ${min_version})
+    set(CMAKE_DEPFILE_FLAGS_ASM "${flag} <DEP_FILE>")
+  endif()
+endmacro()
diff --git a/Modules/Compiler/IBMClang.cmake b/Modules/Compiler/IBMClang.cmake
index a9d760f..169a0f0 100644
--- a/Modules/Compiler/IBMClang.cmake
+++ b/Modules/Compiler/IBMClang.cmake
@@ -43,7 +43,10 @@
   set(_CMAKE_${lang}_IPO_SUPPORTED_BY_CMAKE YES)
   set(_CMAKE_${lang}_IPO_MAY_BE_SUPPORTED_BY_COMPILER YES)
 
-  set(_CMAKE_LTO_THIN TRUE)
+  # Thin LTO is not yet supported on AIX.
+  if(NOT (CMAKE_SYSTEM_NAME STREQUAL "AIX"))
+    set(_CMAKE_LTO_THIN TRUE)
+  endif()
 
   if(_CMAKE_LTO_THIN)
     set(CMAKE_${lang}_COMPILE_OPTIONS_IPO "-flto=thin")
diff --git a/Modules/Compiler/LCC-Fortran.cmake b/Modules/Compiler/LCC-Fortran.cmake
index 8091b29..2d82ea8 100644
--- a/Modules/Compiler/LCC-Fortran.cmake
+++ b/Modules/Compiler/LCC-Fortran.cmake
@@ -10,8 +10,11 @@
 set(CMAKE_Fortran_FORMAT_FIXED_FLAG "-ffixed-form")
 set(CMAKE_Fortran_FORMAT_FREE_FLAG "-ffree-form")
 
-set(CMAKE_Fortran_COMPILE_OPTIONS_PREPROCESS_ON "-cpp")
-set(CMAKE_Fortran_COMPILE_OPTIONS_PREPROCESS_OFF "-nocpp")
+# LCC < 1.24.00 has a broken Fortran preprocessor
+if(CMAKE_Fortran_COMPILER_VERSION VERSION_GREATER_EQUAL "1.24.00")
+  set(CMAKE_Fortran_COMPILE_OPTIONS_PREPROCESS_ON "-cpp")
+  set(CMAKE_Fortran_COMPILE_OPTIONS_PREPROCESS_OFF "-nocpp")
+endif()
 
 set(CMAKE_Fortran_POSTPROCESS_FLAG "-fpreprocessed")
 
diff --git a/Modules/Compiler/NVHPC.cmake b/Modules/Compiler/NVHPC.cmake
index 474ac80..0593456 100644
--- a/Modules/Compiler/NVHPC.cmake
+++ b/Modules/Compiler/NVHPC.cmake
@@ -13,5 +13,5 @@
 macro(__compiler_nvhpc lang)
   # Logic specific to NVHPC.
   set(CMAKE_INCLUDE_SYSTEM_FLAG_${lang} "-isystem ")
-  set(CMAKE_${lang}_COMPILE_OPTIONS_WARNING_AS_ERROR "-Werror" "all-warnings")
+  set(CMAKE_${lang}_COMPILE_OPTIONS_WARNING_AS_ERROR "-Werror")
 endmacro()
diff --git a/Modules/Compiler/NVIDIA-CUDA.cmake b/Modules/Compiler/NVIDIA-CUDA.cmake
index 0823954..c839d1c 100644
--- a/Modules/Compiler/NVIDIA-CUDA.cmake
+++ b/Modules/Compiler/NVIDIA-CUDA.cmake
@@ -8,6 +8,11 @@
 set(_CMAKE_CUDA_WHOLE_FLAG "-c")
 set(_CMAKE_CUDA_RDC_FLAG "-rdc=true")
 set(_CMAKE_CUDA_PTX_FLAG "-ptx")
+set(_CMAKE_CUDA_CUBIN_FLAG "-cubin")
+set(_CMAKE_CUDA_FATBIN_FLAG "-fatbin")
+if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL "11.7.0")
+  set(_CMAKE_CUDA_OPTIX_FLAG "-optix-ir")
+endif()
 
 if (CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 10.2.89)
   # The -forward-unknown-to-host-compiler flag was only
diff --git a/Modules/CompilerId/VS-10.vcxproj.in b/Modules/CompilerId/VS-10.vcxproj.in
index 3598fc7..fa324d8 100644
--- a/Modules/CompilerId/VS-10.vcxproj.in
+++ b/Modules/CompilerId/VS-10.vcxproj.in
@@ -26,6 +26,7 @@
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@id_platform@'" Label="Configuration">
     <ConfigurationType>@id_config_type@</ConfigurationType>
     @id_toolset@
+    @id_api_level@
     <CharacterSet>MultiByte</CharacterSet>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
@@ -44,7 +45,7 @@
       <PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <MinimalRebuild>false</MinimalRebuild>
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
-      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+      <RuntimeLibrary Condition="'$(ApplicationType)'!='Android'">MultiThreadedDebugDLL</RuntimeLibrary>
       <PrecompiledHeader>
       </PrecompiledHeader>
       <WarningLevel>TurnOffAllWarnings</WarningLevel>
diff --git a/Modules/Dart.cmake b/Modules/Dart.cmake
index 154fe9d..3610012 100644
--- a/Modules/Dart.cmake
+++ b/Modules/Dart.cmake
@@ -5,6 +5,11 @@
 Dart
 ----
 
+.. deprecated:: 3.27
+  This module is available only if policy :policy:`CMP0145`
+  is not set to ``NEW``.  Do not use it in new code.
+  Use the :module:`CTest` module instead.
+
 Configure a project for testing with CTest or old Dart Tcl Client
 
 This file is the backwards-compatibility version of the CTest module.
@@ -33,10 +38,24 @@
 #
 #
 
+# include(Dart) already warns about CMP0145, but back when this module was in
+# common use, it was often loaded via include(${CMAKE_ROOT}/Modules/Dart.cmake)
+# which will not warn.  Warn again just in case.
+cmake_policy(GET CMP0145 cmp0145)
+if(cmp0145 STREQUAL "")
+  cmake_policy(GET_WARNING CMP0145 _cmp0145_warning)
+  message(AUTHOR_WARNING "${_cmp0145_warning}")
+endif()
+
 option(BUILD_TESTING "Build the testing tree." ON)
 
 if(BUILD_TESTING)
+  # We only get here if a project already ran include(Dart),
+  # so avoid warning about CMP0145 again.
+  cmake_policy(PUSH)
+  cmake_policy(SET CMP0145 OLD)
   find_package(Dart QUIET)
+  cmake_policy(POP)
 
   #
   # Section #1:
diff --git a/Modules/ExternalProject.cmake b/Modules/ExternalProject.cmake
index b34a35b..bac126c 100644
--- a/Modules/ExternalProject.cmake
+++ b/Modules/ExternalProject.cmake
@@ -278,6 +278,13 @@
         URL of the git repository. Any URL understood by the ``git`` command
         may be used.
 
+        .. versionchanged:: 3.27
+          A relative URL will be resolved based on the parent project's
+          remote, subject to :policy:`CMP0150`.  See the policy documentation
+          for how the remote is selected, including conditions where the
+          remote selection can fail.  Local filesystem remotes should
+          always use absolute paths.
+
       ``GIT_TAG <tag>``
         Git branch name, tag or commit hash. Note that branch names and tags
         should generally be specified as remote names (i.e. ``origin/myBranch``
@@ -441,13 +448,23 @@
     ``UPDATE_DISCONNECTED <bool>``
       .. versionadded:: 3.2
 
-      When enabled, this option causes the update step to be skipped. It does
-      not, however, prevent the download step. The update step can still be
+      When enabled, this option causes the update step to be skipped (but see
+      below for changed behavior where this is not the case). It does not
+      prevent the download step. The update step can still be
       added as a step target (see :command:`ExternalProject_Add_StepTargets`)
       and called manually. This is useful if you want to allow developers to
       build the project when disconnected from the network (the network may
       still be needed for the download step though).
 
+      .. versionchanged:: 3.27
+
+        When ``UPDATE_DISCONNECTED`` is true, the update step will be executed
+        if any details about the update or download step are changed.
+        Furthermore, if using the git download/update method, the update
+        logic will be modified to skip attempts to contact the remote.
+        If the ``GIT_TAG`` mentions a ref that is not known locally, the
+        update step will halt with a fatal error.
+
       When this option is present, it is generally advisable to make the value
       a cache variable under the developer's control rather than hard-coding
       it. If this option is not present, the default value is taken from the
@@ -1188,6 +1205,8 @@
 
 #]=======================================================================]
 
+include(${CMAKE_CURRENT_LIST_DIR}/ExternalProject/shared_internal_commands.cmake)
+
 cmake_policy(PUSH)
 cmake_policy(SET CMP0054 NEW) # if() quoted variables not dereferenced
 cmake_policy(SET CMP0057 NEW) # if() supports IN_LIST
@@ -2090,13 +2109,7 @@
   set(suffix "")
   get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
   if(_isMultiConfig)
-    if(CMAKE_GENERATOR STREQUAL "Xcode")
-      # The Xcode generator does not support per-config sources,
-      # so use the underlying build system's placeholder instead.
-      set(suffix "/${CMAKE_CFG_INTDIR}")
-    else()
-      set(suffix "/$<CONFIG>")
-    endif()
+    set(suffix "/$<CONFIG>")
   endif()
   set(${suffix_var} "${suffix}" PARENT_SCOPE)
 endfunction()
@@ -2444,7 +2457,7 @@
     PROPERTY _EP_${step}_ALWAYS
   )
   if(always)
-    set(touch)
+    set(maybe_COMMAND_touch "")
     # Mark stamp files for all configs as SYMBOLIC since we do not create them.
     # Remove any existing stamp in case the option changed in an existing tree.
     get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
@@ -2466,7 +2479,7 @@
       file(REMOVE ${stamp_file})
     endif()
   else()
-    set(touch ${CMAKE_COMMAND} -E touch ${stamp_file})
+    set(maybe_COMMAND_touch "COMMAND \${CMAKE_COMMAND} -E touch \${stamp_file}")
   endif()
 
   # Wrap with log script?
@@ -2494,7 +2507,7 @@
       BYPRODUCTS \${byproducts}
       COMMENT \${comment}
       COMMAND ${__cmdQuoted}
-      COMMAND \${touch}
+      ${maybe_COMMAND_touch}
       DEPENDS \${depends}
       WORKING_DIRECTORY \${work_dir}
       VERBATIM
@@ -3213,7 +3226,7 @@
 endfunction()
 
 function(_ep_add_update_command name)
-  ExternalProject_Get_Property(${name} source_dir tmp_dir)
+  ExternalProject_Get_Property(${name} source_dir stamp_dir tmp_dir)
 
   get_property(cmd_set TARGET ${name} PROPERTY _EP_UPDATE_COMMAND SET)
   get_property(cmd TARGET ${name} PROPERTY _EP_UPDATE_COMMAND)
@@ -3227,6 +3240,7 @@
   set(work_dir)
   set(comment)
   set(always)
+  set(file_deps)
 
   if(cmd_set)
     set(work_dir ${source_dir})
@@ -3288,6 +3302,7 @@
     endif()
     set(work_dir ${source_dir})
     set(comment "Performing update step for '${name}'")
+    set(comment_disconnected "Performing disconnected update step for '${name}'")
 
     get_property(git_tag
       TARGET ${name}
@@ -3341,8 +3356,10 @@
 
     _ep_get_git_submodules_recurse(git_submodules_recurse)
 
+    set(update_script "${tmp_dir}/${name}-gitupdate.cmake")
+    list(APPEND file_deps ${update_script})
     _ep_write_gitupdate_script(
-      "${tmp_dir}/${name}-gitupdate.cmake"
+      "${update_script}"
       "${GIT_EXECUTABLE}"
       "${git_tag}"
       "${git_remote_name}"
@@ -3353,7 +3370,8 @@
       "${work_dir}"
       "${git_update_strategy}"
     )
-    set(cmd ${CMAKE_COMMAND} -P ${tmp_dir}/${name}-gitupdate.cmake)
+    set(cmd              ${CMAKE_COMMAND} -Dcan_fetch=YES -P ${update_script})
+    set(cmd_disconnected ${CMAKE_COMMAND} -Dcan_fetch=NO  -P ${update_script})
     set(always 1)
   elseif(hg_repository)
     if(NOT HG_EXECUTABLE)
@@ -3361,6 +3379,7 @@
     endif()
     set(work_dir ${source_dir})
     set(comment "Performing update step (hg pull) for '${name}'")
+    set(comment_disconnected "Performing disconnected update step for '${name}'")
 
     get_property(hg_tag
       TARGET ${name}
@@ -3386,9 +3405,23 @@
       ${HG_EXECUTABLE} pull
       COMMAND ${HG_EXECUTABLE} update ${hg_tag}
     )
+    set(cmd_disconnected ${HG_EXECUTABLE} update ${hg_tag})
     set(always 1)
   endif()
 
+  # We use configure_file() to write the update_info_file so that the file's
+  # timestamp is not updated if we don't change the contents
+  if(NOT DEFINED cmd_disconnected)
+    set(cmd_disconnected "${cmd}")
+  endif()
+  set(update_info_file ${stamp_dir}/${name}-update-info.txt)
+  list(APPEND file_deps ${update_info_file})
+  configure_file(
+    "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject/UpdateInfo.txt.in"
+    "${update_info_file}"
+    @ONLY
+  )
+
   get_property(log
     TARGET ${name}
     PROPERTY _EP_LOG_UPDATE
@@ -3422,16 +3455,39 @@
       EXCLUDE_FROM_MAIN \${update_disconnected}
       WORKING_DIRECTORY \${work_dir}
       DEPENDEES download
+      DEPENDS \${file_deps}
       ${log}
       ${uses_terminal}
     )"
   )
+  if(update_disconnected)
+    if(NOT DEFINED comment_disconnected)
+      set(comment_disconnected "${comment}")
+    endif()
+    set(__cmdQuoted)
+    foreach(__item IN LISTS cmd_disconnected)
+      string(APPEND __cmdQuoted " [==[${__item}]==]")
+    endforeach()
+
+    cmake_language(EVAL CODE "
+      ExternalProject_Add_Step(${name} update_disconnected
+        INDEPENDENT TRUE
+        COMMENT \${comment_disconnected}
+        COMMAND ${__cmdQuoted}
+        WORKING_DIRECTORY \${work_dir}
+        DEPENDEES download
+        DEPENDS \${file_deps}
+        ${log}
+        ${uses_terminal}
+      )"
+    )
+  endif()
 
 endfunction()
 
 
 function(_ep_add_patch_command name)
-  ExternalProject_Get_Property(${name} source_dir)
+  ExternalProject_Get_Property(${name} source_dir stamp_dir)
 
   get_property(cmd_set TARGET ${name} PROPERTY _EP_PATCH_COMMAND SET)
   get_property(cmd TARGET ${name} PROPERTY _EP_PATCH_COMMAND)
@@ -3442,6 +3498,15 @@
     set(work_dir ${source_dir})
   endif()
 
+  # We use configure_file() to write the patch_info_file so that the file's
+  # timestamp is not updated if we don't change the contents
+  set(patch_info_file ${stamp_dir}/${name}-patch-info.txt)
+  configure_file(
+    "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject/PatchInfo.txt.in"
+    "${patch_info_file}"
+    @ONLY
+  )
+
   get_property(log
     TARGET ${name}
     PROPERTY _EP_LOG_PATCH
@@ -3463,11 +3528,6 @@
   endif()
 
   _ep_get_update_disconnected(update_disconnected ${name})
-  if(update_disconnected)
-    set(patch_dep download)
-  else()
-    set(patch_dep update)
-  endif()
 
   set(__cmdQuoted)
   foreach(__item IN LISTS cmd)
@@ -3478,11 +3538,28 @@
       INDEPENDENT TRUE
       COMMAND ${__cmdQuoted}
       WORKING_DIRECTORY \${work_dir}
-      DEPENDEES \${patch_dep}
+      EXCLUDE_FROM_MAIN \${update_disconnected}
+      DEPENDEES update
+      DEPENDS \${patch_info_file}
       ${log}
       ${uses_terminal}
     )"
   )
+
+  if(update_disconnected)
+    cmake_language(EVAL CODE "
+      ExternalProject_Add_Step(${name} patch_disconnected
+        INDEPENDENT TRUE
+        COMMAND ${__cmdQuoted}
+        WORKING_DIRECTORY \${work_dir}
+        DEPENDEES update_disconnected
+        DEPENDS \${patch_info_file}
+        ${log}
+        ${uses_terminal}
+      )"
+    )
+  endif()
+
 endfunction()
 
 function(_ep_get_file_deps var name)
@@ -3692,6 +3769,13 @@
   list(APPEND file_deps ${tmp_dir}/${name}-cfgcmd.txt)
   list(APPEND file_deps ${_ep_cache_args_script})
 
+  _ep_get_update_disconnected(update_disconnected ${name})
+  if(update_disconnected)
+    set(dependees patch_disconnected)
+  else()
+    set(dependees patch)
+  endif()
+
   get_property(log
     TARGET ${name}
     PROPERTY _EP_LOG_CONFIGURE
@@ -3721,7 +3805,7 @@
       INDEPENDENT FALSE
       COMMAND ${__cmdQuoted}
       WORKING_DIRECTORY \${binary_dir}
-      DEPENDEES patch
+      DEPENDEES \${dependees}
       DEPENDS \${file_deps}
       ${log}
       ${uses_terminal}
@@ -4165,6 +4249,17 @@
     set_property(TARGET ${name} PROPERTY EXCLUDE_FROM_ALL TRUE)
   endif()
 
+  get_property(repo TARGET ${name} PROPERTY _EP_GIT_REPOSITORY)
+  if(NOT repo STREQUAL "")
+    cmake_policy(GET CMP0150 cmp0150
+      PARENT_SCOPE # undocumented, do not use outside of CMake
+    )
+    get_property(source_dir TARGET ${name} PROPERTY _EP_SOURCE_DIR)
+    get_filename_component(work_dir "${source_dir}" PATH)
+    _ep_resolve_git_remote(resolved_git_repository "${repo}" "${cmp0150}" "${work_dir}")
+    set_property(TARGET ${name} PROPERTY _EP_GIT_REPOSITORY ${resolved_git_repository})
+  endif()
+
   # The 'complete' step depends on all other steps and creates a
   # 'done' mark.  A dependent external project's 'configure' step
   # depends on the 'done' mark so that it rebuilds when this project
diff --git a/Modules/ExternalProject/PatchInfo.txt.in b/Modules/ExternalProject/PatchInfo.txt.in
new file mode 100644
index 0000000..112953c
--- /dev/null
+++ b/Modules/ExternalProject/PatchInfo.txt.in
@@ -0,0 +1,6 @@
+# This is a generated file and its contents are an internal implementation detail.
+# The update step will be re-executed if anything in this file changes.
+# No other meaning or use of this file is supported.
+
+command=@cmd@
+work_dir=@work_dir@
diff --git a/Modules/ExternalProject/UpdateInfo.txt.in b/Modules/ExternalProject/UpdateInfo.txt.in
new file mode 100644
index 0000000..67ee434
--- /dev/null
+++ b/Modules/ExternalProject/UpdateInfo.txt.in
@@ -0,0 +1,7 @@
+# This is a generated file and its contents are an internal implementation detail.
+# The patch step will be re-executed if anything in this file changes.
+# No other meaning or use of this file is supported.
+
+command (connected)=@cmd@
+command (disconnected)=@cmd_disconnected@
+work_dir=@work_dir@
diff --git a/Modules/ExternalProject/download.cmake.in b/Modules/ExternalProject/download.cmake.in
index ff8c659..bf7f209 100644
--- a/Modules/ExternalProject/download.cmake.in
+++ b/Modules/ExternalProject/download.cmake.in
@@ -108,7 +108,7 @@
    timeout='@TIMEOUT_MSG@'
    inactivity timeout='@INACTIVITY_TIMEOUT_MSG@'"
 )
-set(download_retry_codes 7 6 8 15)
+set(download_retry_codes 7 6 8 15 28)
 set(skip_url_list)
 set(status_code)
 foreach(i RANGE ${retry_number})
diff --git a/Modules/ExternalProject/gitupdate.cmake.in b/Modules/ExternalProject/gitupdate.cmake.in
index 50f0167..eb3cda7 100644
--- a/Modules/ExternalProject/gitupdate.cmake.in
+++ b/Modules/ExternalProject/gitupdate.cmake.in
@@ -3,6 +3,15 @@
 
 cmake_minimum_required(VERSION 3.5)
 
+function(do_fetch)
+  message(VERBOSE "Fetching latest from the remote @git_remote_name@")
+  execute_process(
+    COMMAND "@git_EXECUTABLE@" --git-dir=.git fetch --tags --force "@git_remote_name@"
+    WORKING_DIRECTORY "@work_dir@"
+    COMMAND_ERROR_IS_FATAL LAST
+  )
+endfunction()
+
 function(get_hash_for_ref ref out_var err_var)
   execute_process(
     COMMAND "@git_EXECUTABLE@" --git-dir=.git rev-parse "${ref}^0"
@@ -33,17 +42,16 @@
 )
 if(show_ref_output MATCHES "^[a-z0-9]+[ \\t]+refs/remotes/")
   # Given a full remote/branch-name and we know about it already. Since
-  # branches can move around, we always have to fetch.
-  set(fetch_required YES)
+  # branches can move around, we should always fetch, if permitted.
+  if(can_fetch)
+    do_fetch()
+  endif()
   set(checkout_name "@git_tag@")
 
 elseif(show_ref_output MATCHES "^[a-z0-9]+[ \\t]+refs/tags/")
   # Given a tag name that we already know about. We don't know if the tag we
-  # have matches the remote though (tags can move), so we should fetch.
-  set(fetch_required YES)
-  set(checkout_name "@git_tag@")
-
-  # Special case to preserve backward compatibility: if we are already at the
+  # have matches the remote though (tags can move), so we should fetch. As a
+  # special case to preserve backward compatibility, if we are already at the
   # same commit as the tag we hold locally, don't do a fetch and assume the tag
   # hasn't moved on the remote.
   # FIXME: We should provide an option to always fetch for this case
@@ -53,12 +61,20 @@
     return()
   endif()
 
+  if(can_fetch)
+    do_fetch()
+  endif()
+  set(checkout_name "@git_tag@")
+
 elseif(show_ref_output MATCHES "^[a-z0-9]+[ \\t]+refs/heads/")
   # Given a branch name without any remote and we already have a branch by that
   # name. We might already have that branch checked out or it might be a
-  # different branch. It isn't safe to use a bare branch name without the
-  # remote, so do a fetch and replace the ref with one that includes the remote.
-  set(fetch_required YES)
+  # different branch. It isn't fully safe to use a bare branch name without the
+  # remote, so do a fetch (if allowed) and replace the ref with one that
+  # includes the remote.
+  if(can_fetch)
+    do_fetch()
+  endif()
   set(checkout_name "@git_remote_name@/@git_tag@")
 
 else()
@@ -70,20 +86,26 @@
 
   elseif(tag_sha STREQUAL "")
     # We don't know about this ref yet, so we have no choice but to fetch.
+    if(NOT can_fetch)
+      message(FATAL_ERROR
+        "Requested git ref \"@git_tag@\" is not present locally, and not "
+        "allowed to contact remote due to UPDATE_DISCONNECTED setting."
+      )
+    endif()
+
     # We deliberately swallow any error message at the default log level
     # because it can be confusing for users to see a failed git command.
     # That failure is being handled here, so it isn't an error.
-    set(fetch_required YES)
-    set(checkout_name "@git_tag@")
     if(NOT error_msg STREQUAL "")
       message(VERBOSE "${error_msg}")
     endif()
+    do_fetch()
+    set(checkout_name "@git_tag@")
 
   else()
     # We have the commit, so we know we were asked to find a commit hash
     # (otherwise it would have been handled further above), but we don't
-    # have that commit checked out yet
-    set(fetch_required NO)
+    # have that commit checked out yet. We don't need to fetch from the remote.
     set(checkout_name "@git_tag@")
     if(NOT error_msg STREQUAL "")
       message(WARNING "${error_msg}")
@@ -92,15 +114,6 @@
   endif()
 endif()
 
-if(fetch_required)
-  message(VERBOSE "Fetching latest from the remote @git_remote_name@")
-  execute_process(
-    COMMAND "@git_EXECUTABLE@" --git-dir=.git fetch --tags --force "@git_remote_name@"
-    WORKING_DIRECTORY "@work_dir@"
-    COMMAND_ERROR_IS_FATAL ANY
-  )
-endif()
-
 set(git_update_strategy "@git_update_strategy@")
 if(git_update_strategy STREQUAL "")
   # Backward compatibility requires REBASE as the default behavior
diff --git a/Modules/ExternalProject/shared_internal_commands.cmake b/Modules/ExternalProject/shared_internal_commands.cmake
new file mode 100644
index 0000000..ca3cd9f
--- /dev/null
+++ b/Modules/ExternalProject/shared_internal_commands.cmake
@@ -0,0 +1,182 @@
+cmake_policy(VERSION 3.25)
+
+# Determine the remote URL of the project containing the working_directory.
+# This will leave output_variable unset if the URL can't be determined.
+function(_ep_get_git_remote_url output_variable working_directory)
+  set("${output_variable}" "" PARENT_SCOPE)
+
+  find_package(Git QUIET REQUIRED)
+
+  execute_process(
+    COMMAND ${GIT_EXECUTABLE} symbolic-ref --short HEAD
+    WORKING_DIRECTORY "${working_directory}"
+    OUTPUT_VARIABLE git_symbolic_ref
+    OUTPUT_STRIP_TRAILING_WHITESPACE
+    ERROR_QUIET
+  )
+
+  if(NOT git_symbolic_ref STREQUAL "")
+    # We are potentially on a branch. See if that branch is associated with
+    # an upstream remote (might be just a local one or not a branch at all).
+    execute_process(
+      COMMAND ${GIT_EXECUTABLE} config branch.${git_symbolic_ref}.remote
+      WORKING_DIRECTORY "${working_directory}"
+      OUTPUT_VARIABLE git_remote_name
+      OUTPUT_STRIP_TRAILING_WHITESPACE
+      ERROR_QUIET
+    )
+  endif()
+
+  if(NOT git_remote_name)
+    # Can't select a remote based on a branch. If there's only one remote,
+    # or we have multiple remotes but one is called "origin", choose that.
+    execute_process(
+      COMMAND ${GIT_EXECUTABLE} remote
+      WORKING_DIRECTORY "${working_directory}"
+      OUTPUT_VARIABLE git_remote_list
+      OUTPUT_STRIP_TRAILING_WHITESPACE
+      ERROR_QUIET
+    )
+    string(REPLACE "\n" ";" git_remote_list "${git_remote_list}")
+    list(LENGTH git_remote_list git_remote_list_length)
+
+    if(git_remote_list_length EQUAL 0)
+      message(FATAL_ERROR "Git remote not found in parent project.")
+    elseif(git_remote_list_length EQUAL 1)
+      list(GET git_remote_list 0 git_remote_name)
+    else()
+      set(base_warning_msg "Multiple git remotes found for parent project")
+      if("origin" IN_LIST git_remote_list)
+        message(WARNING "${base_warning_msg}, defaulting to origin.")
+        set(git_remote_name "origin")
+      else()
+        message(FATAL_ERROR "${base_warning_msg}, none of which are origin.")
+      endif()
+    endif()
+  endif()
+
+  if(GIT_VERSION VERSION_LESS 1.7.5)
+    set(_git_remote_url_cmd_args config remote.${git_remote_name}.url)
+  elseif(GIT_VERSION VERSION_LESS 2.7)
+    set(_git_remote_url_cmd_args ls-remote --get-url ${git_remote_name})
+  else()
+    set(_git_remote_url_cmd_args remote get-url ${git_remote_name})
+  endif()
+
+  execute_process(
+    COMMAND ${GIT_EXECUTABLE} ${_git_remote_url_cmd_args}
+    WORKING_DIRECTORY "${working_directory}"
+    OUTPUT_VARIABLE git_remote_url
+    OUTPUT_STRIP_TRAILING_WHITESPACE
+    COMMAND_ERROR_IS_FATAL LAST
+    ENCODING UTF-8   # Needed to handle non-ascii characters in local paths
+  )
+
+  set("${output_variable}" "${git_remote_url}" PARENT_SCOPE)
+endfunction()
+
+function(_ep_is_relative_git_remote output_variable remote_url)
+  if(remote_url MATCHES "^\\.\\./")
+    set("${output_variable}" TRUE PARENT_SCOPE)
+  else()
+    set("${output_variable}" FALSE PARENT_SCOPE)
+  endif()
+endfunction()
+
+# Return an absolute remote URL given an existing remote URL and relative path.
+# The output_variable will be set to an empty string if an absolute URL
+# could not be computed (no error message is output).
+function(_ep_resolve_relative_git_remote
+  output_variable
+  parent_remote_url
+  relative_remote_url
+)
+  set("${output_variable}" "" PARENT_SCOPE)
+
+  if(parent_remote_url STREQUAL "")
+    return()
+  endif()
+
+  string(REGEX MATCH
+    "^(([A-Za-z0-9][A-Za-z0-9+.-]*)://)?(([^/@]+)@)?(\\[[A-Za-z0-9:]+\\]|[^/:]+)?([/:]/?)(.+(\\.git)?/?)$"
+    git_remote_url_components
+    "${parent_remote_url}"
+  )
+
+  set(protocol "${CMAKE_MATCH_1}")
+  set(auth "${CMAKE_MATCH_3}")
+  set(host "${CMAKE_MATCH_5}")
+  set(separator "${CMAKE_MATCH_6}")
+  set(path "${CMAKE_MATCH_7}")
+
+  string(REPLACE "/" ";" remote_path_components "${path}")
+  string(REPLACE "/" ";" relative_path_components "${relative_remote_url}")
+
+  foreach(relative_path_component IN LISTS relative_path_components)
+    if(NOT relative_path_component STREQUAL "..")
+      break()
+    endif()
+
+    list(LENGTH remote_path_components remote_path_component_count)
+
+    if(remote_path_component_count LESS 1)
+      return()
+    endif()
+
+    list(POP_BACK remote_path_components)
+    list(POP_FRONT relative_path_components)
+  endforeach()
+
+  list(APPEND final_path_components ${remote_path_components} ${relative_path_components})
+  list(JOIN final_path_components "/" path)
+
+  set("${output_variable}" "${protocol}${auth}${host}${separator}${path}" PARENT_SCOPE)
+endfunction()
+
+# The output_variable will be set to the original git_repository if it
+# could not be resolved (no error message is output). The original value is
+# also returned if it doesn't need to be resolved.
+function(_ep_resolve_git_remote
+  output_variable
+  git_repository
+  cmp0150
+  cmp0150_old_base_dir
+)
+  if(git_repository STREQUAL "")
+    set("${output_variable}" "" PARENT_SCOPE)
+    return()
+  endif()
+
+  _ep_is_relative_git_remote(_git_repository_is_relative "${git_repository}")
+
+  if(NOT _git_repository_is_relative)
+    set("${output_variable}" "${git_repository}" PARENT_SCOPE)
+    return()
+  endif()
+
+  if(cmp0150 STREQUAL "NEW")
+    _ep_get_git_remote_url(_parent_git_remote_url "${CMAKE_CURRENT_SOURCE_DIR}")
+    _ep_resolve_relative_git_remote(_resolved_git_remote_url "${_parent_git_remote_url}" "${git_repository}")
+
+    if(_resolved_git_remote_url STREQUAL "")
+      message(FATAL_ERROR
+        "Failed to resolve relative git remote URL:\n"
+        "  Relative URL: ${git_repository}\n"
+        "  Parent URL:   ${_parent_git_remote_url}"
+      )
+    endif()
+    set("${output_variable}" "${_resolved_git_remote_url}" PARENT_SCOPE)
+    return()
+  elseif(cmp0150 STREQUAL "")
+    cmake_policy(GET_WARNING CMP0150 _cmp0150_warning)
+    message(AUTHOR_WARNING
+      "${_cmp0150_warning}\n"
+      "A relative GIT_REPOSITORY path was detected. "
+      "This will be interpreted as a local path to where the project is being cloned. "
+      "Set GIT_REPOSITORY to an absolute path or set policy CMP0150 to NEW to avoid "
+      "this warning."
+    )
+  endif()
+
+  set("${output_variable}" "${cmp0150_old_base_dir}/${git_repository}" PARENT_SCOPE)
+endfunction()
diff --git a/Modules/FetchContent.cmake b/Modules/FetchContent.cmake
index 8afb9bc..56fc0ed 100644
--- a/Modules/FetchContent.cmake
+++ b/Modules/FetchContent.cmake
@@ -665,10 +665,16 @@
   This is a less severe download/update control compared to
   :variable:`FETCHCONTENT_FULLY_DISCONNECTED`.  Instead of bypassing all
   download and update logic, ``FETCHCONTENT_UPDATES_DISCONNECTED`` only
-  disables the update stage.  Therefore, if content has not been downloaded
-  previously, it will still be downloaded when this option is enabled.
-  This can speed up the configure stage, but not as much as
-  :variable:`FETCHCONTENT_FULLY_DISCONNECTED`.  It is ``OFF`` by default.
+  prevents the update step from making connections to remote servers
+  when using the git or hg download methods.  Updates still occur if details
+  about the update step change, but the update is attempted with only the
+  information already available locally (so switching to a different tag or
+  commit that is already fetched locally will succeed, but switching to an
+  unknown commit hash will fail).  The download step is not affected, so if
+  content has not been downloaded previously, it will still be downloaded
+  when this option is enabled.  This can speed up the configure step, but
+  not as much as :variable:`FETCHCONTENT_FULLY_DISCONNECTED`.
+  ``FETCHCONTENT_UPDATES_DISCONNECTED`` is ``OFF`` by default.
 
 .. variable:: FETCHCONTENT_TRY_FIND_PACKAGE_MODE
 
@@ -735,10 +741,11 @@
 
   This is the per-content equivalent of
   :variable:`FETCHCONTENT_UPDATES_DISCONNECTED`.  If the global option or
-  this option is ``ON``, then updates will be disabled for the named content.
-  Disabling updates for individual content can be useful for content whose
-  details rarely change, while still leaving other frequently changing content
-  with updates enabled.
+  this option is ``ON``, then updates for the git and hg methods will not
+  contact any remote for the named content.  They will only use information
+  already available locally.  Disabling updates for individual content can
+  be useful for content whose details rarely change, while still leaving
+  other frequently changing content with updates enabled.
 
 .. _`fetch-content-examples`:
 
@@ -1076,6 +1083,8 @@
 
 #]=======================================================================]
 
+include(${CMAKE_CURRENT_LIST_DIR}/ExternalProject/shared_internal_commands.cmake)
+
 #=======================================================================
 # Recording and retrieving content details for later population
 #=======================================================================
@@ -1223,6 +1232,7 @@
   # cannot check for multi-value arguments with this method. We will have to
   # handle the URL keyword differently.
   set(oneValueArgs
+    GIT_REPOSITORY
     SVN_REPOSITORY
     DOWNLOAD_NO_EXTRACT
     DOWNLOAD_EXTRACT_TIMESTAMP
@@ -1242,6 +1252,30 @@
     set(ARG_SOURCE_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-src")
   endif()
 
+  if(ARG_GIT_REPOSITORY)
+    # We resolve the GIT_REPOSITORY here so that we get the right parent in the
+    # remote selection logic. In the sub-build, ExternalProject_Add() would see
+    # the private sub-build directory as the parent project, but the parent
+    # project should be the one that called FetchContent_Declare(). We resolve
+    # a relative repo here so that the sub-build's ExternalProject_Add() only
+    # ever sees a non-relative repo.
+    # Since these checks may be non-trivial on some platforms (notably Windows),
+    # don't perform them if we won't be using these details. This also allows
+    # projects to override calls with relative URLs when they have checked out
+    # the parent project in an unexpected way, such as from a mirror or fork.
+    set(savedDetailsPropertyName "_FetchContent_${contentNameLower}_savedDetails")
+    get_property(alreadyDefined GLOBAL PROPERTY ${savedDetailsPropertyName} DEFINED)
+    if(NOT alreadyDefined)
+      cmake_policy(GET CMP0150 cmp0150
+        PARENT_SCOPE # undocumented, do not use outside of CMake
+      )
+      _ep_resolve_git_remote(_resolved_git_repository
+        "${ARG_GIT_REPOSITORY}" "${cmp0150}" "${FETCHCONTENT_BASE_DIR}"
+      )
+      set(ARG_GIT_REPOSITORY "${_resolved_git_repository}")
+    endif()
+  endif()
+
   if(ARG_SVN_REPOSITORY)
     # Add a hash of the svn repository URL to the source dir. This works
     # around the problem where if the URL changes, the download would
@@ -1536,7 +1570,9 @@
     if(CMAKE_GENERATOR_TOOLSET)
       list(APPEND subCMakeOpts "-T${CMAKE_GENERATOR_TOOLSET}")
     endif()
-
+    if(CMAKE_GENERATOR_INSTANCE)
+      list(APPEND subCMakeOpts "-DCMAKE_GENERATOR_INSTANCE:INTERNAL=${CMAKE_GENERATOR_INSTANCE}")
+    endif()
     if(CMAKE_MAKE_PROGRAM)
       list(APPEND subCMakeOpts "-DCMAKE_MAKE_PROGRAM:FILEPATH=${CMAKE_MAKE_PROGRAM}")
     endif()
@@ -1596,7 +1632,9 @@
   # has this set to something not findable on the PATH. We also ensured above
   # that the Debug config will be defined for multi-config generators.
   configure_file("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/FetchContent/CMakeLists.cmake.in"
-                 "${ARG_SUBBUILD_DIR}/CMakeLists.txt")
+                 "${ARG_SUBBUILD_DIR}/CMakeLists.txt"
+                 @ONLY
+  )
   execute_process(
     COMMAND ${CMAKE_COMMAND} ${subCMakeOpts} .
     RESULT_VARIABLE result
diff --git a/Modules/FetchContent/CMakeLists.cmake.in b/Modules/FetchContent/CMakeLists.cmake.in
index d94b0f4..8adb533 100644
--- a/Modules/FetchContent/CMakeLists.cmake.in
+++ b/Modules/FetchContent/CMakeLists.cmake.in
@@ -1,21 +1,27 @@
 # Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
 # file Copyright.txt or https://cmake.org/licensing for details.
 
-cmake_minimum_required(VERSION ${CMAKE_VERSION})
+cmake_minimum_required(VERSION @CMAKE_VERSION@)
+
+# Reject any attempt to use a toolchain file. We must not use one because
+# we could be downloading it here. If the CMAKE_TOOLCHAIN_FILE environment
+# variable is set, the cache variable will have been initialized from it.
+unset(CMAKE_TOOLCHAIN_FILE CACHE)
+unset(ENV{CMAKE_TOOLCHAIN_FILE})
 
 # We name the project and the target for the ExternalProject_Add() call
 # to something that will highlight to the user what we are working on if
 # something goes wrong and an error message is produced.
 
-project(${contentName}-populate NONE)
+project(@contentName@-populate NONE)
 
 @__FETCHCONTENT_CACHED_INFO@
 
 include(ExternalProject)
-ExternalProject_Add(${contentName}-populate
-                    ${ARG_EXTRA}
-                    SOURCE_DIR          "${ARG_SOURCE_DIR}"
-                    BINARY_DIR          "${ARG_BINARY_DIR}"
+ExternalProject_Add(@contentName@-populate
+                    @ARG_EXTRA@
+                    SOURCE_DIR          "@ARG_SOURCE_DIR@"
+                    BINARY_DIR          "@ARG_BINARY_DIR@"
                     CONFIGURE_COMMAND   ""
                     BUILD_COMMAND       ""
                     INSTALL_COMMAND     ""
diff --git a/Modules/FindBLAS.cmake b/Modules/FindBLAS.cmake
index 7af1017..39a1163 100644
--- a/Modules/FindBLAS.cmake
+++ b/Modules/FindBLAS.cmake
@@ -93,6 +93,11 @@
 ``ACML``, ``ACML_MP``, ``ACML_GPU``
   AMD Core Math Library
 
+``AOCL``, ``AOCL_mt``
+  .. versionadded:: 3.27
+
+  AMD Optimizing CPU Libraries
+
 ``Apple``, ``NAS``
   Apple BLAS (Accelerate), and Apple NAS (vecLib)
 
@@ -386,10 +391,10 @@
 set(BLAS_LIBRARIES)
 set(BLAS95_LIBRARIES)
 set(_blas_fphsa_req_var BLAS_LIBRARIES)
-if(NOT $ENV{BLA_VENDOR} STREQUAL "")
-  set(BLA_VENDOR $ENV{BLA_VENDOR})
-else()
-  if(NOT BLA_VENDOR)
+if(NOT BLA_VENDOR)
+  if(NOT "$ENV{BLA_VENDOR}" STREQUAL "")
+    set(BLA_VENDOR "$ENV{BLA_VENDOR}")
+  else()
     set(BLA_VENDOR "All")
   endif()
 endif()
@@ -848,6 +853,38 @@
   unset(_blas_flame_lib)
 endif()
 
+# AOCL's blis library? (https://developer.amd.com/amd-aocl/)
+if(BLA_VENDOR MATCHES "AOCL" OR BLA_VENDOR STREQUAL "All")
+  set(_blas_aocl_lib "blis")
+
+  if(_blas_sizeof_integer EQUAL 8)
+    set(_blas_aocl_subdir "ILP64")
+  else()
+    set(_blas_aocl_subdir "LP64")
+  endif()
+
+  # Check for multi-threaded support
+  if(BLA_VENDOR MATCHES "_mt")
+    string(APPEND _blas_aocl_lib "-mt")
+  endif()
+
+  if(NOT BLAS_LIBRARIES)
+    check_blas_libraries(
+      BLAS_LIBRARIES
+      BLAS
+      sgemm
+      ""
+      "${_blas_aocl_lib}"
+      ""
+      ""
+      "${_blas_aocl_subdir}"
+      )
+  endif()
+
+  unset(_blas_aocl_lib)
+  unset(_blas_aocl_subdir)
+endif()
+
 # BLAS in the ATLAS library? (http://math-atlas.sourceforge.net/)
 if(BLA_VENDOR STREQUAL "ATLAS" OR BLA_VENDOR STREQUAL "All")
   if(NOT BLAS_LIBRARIES)
diff --git a/Modules/FindCUDA.cmake b/Modules/FindCUDA.cmake
index c928157..0d7f1a4 100644
--- a/Modules/FindCUDA.cmake
+++ b/Modules/FindCUDA.cmake
@@ -2,7 +2,12 @@
 FindCUDA
 --------
 
-.. warning:: *Deprecated since version 3.10.*
+.. versionchanged:: 3.27
+  This module is available only if policy :policy:`CMP0146` is not set to ``NEW``.
+  Port projects to CMake's first-class ``CUDA`` language support.
+
+.. deprecated:: 3.10
+  Do not use this module in new code.
 
 It is no longer necessary to use this module or call ``find_package(CUDA)``
 for compiling CUDA code. Instead, list ``CUDA`` among the languages named
@@ -555,6 +560,23 @@
 #
 ###############################################################################
 
+cmake_policy(GET CMP0146 _FindCUDA_CMP0146)
+if(_FindCUDA_CMP0146 STREQUAL "NEW")
+  message(FATAL_ERROR "The FindCUDA module has been removed by policy CMP0146.")
+endif()
+
+if(CMAKE_GENERATOR MATCHES "Visual Studio")
+  cmake_policy(GET CMP0147 _FindCUDA_CMP0147)
+  if(_FindCUDA_CMP0147 STREQUAL "NEW")
+    message(FATAL_ERROR "The FindCUDA module does not work in Visual Studio with policy CMP0147.")
+  endif()
+endif()
+
+if(_FindCUDA_testing)
+  set(_FindCUDA_included TRUE)
+  return()
+endif()
+
 # FindCUDA.cmake
 
 # This macro helps us find the location of helper files we will need the full path to
@@ -1052,6 +1074,7 @@
     if(NOT APPLE AND NOT (CMAKE_SYSTEM_NAME STREQUAL "QNX"))
       #On Linux, you must link against librt when using the static cuda runtime.
       find_library(CUDA_rt_LIBRARY rt)
+      mark_as_advanced(CUDA_rt_LIBRARY)
       if (NOT CUDA_rt_LIBRARY)
         message(WARNING "Expecting to find librt for libcudart_static, but didn't find it.")
       endif()
@@ -1919,7 +1942,7 @@
       list(APPEND flags -Xcompiler ${f})
     endforeach()
 
-    # Add our general CUDA_NVCC_FLAGS with the configuration specifig flags
+    # Add our general CUDA_NVCC_FLAGS with the configuration specific flags
     set(nvcc_flags ${CUDA_NVCC_FLAGS} ${config_specific_flags} ${nvcc_flags})
 
     file(RELATIVE_PATH output_file_relative_path "${CMAKE_BINARY_DIR}" "${output_file}")
diff --git a/Modules/FindCUDAToolkit.cmake b/Modules/FindCUDAToolkit.cmake
index 724fdf1..107ced7 100644
--- a/Modules/FindCUDAToolkit.cmake
+++ b/Modules/FindCUDAToolkit.cmake
@@ -109,6 +109,7 @@
 - :ref:`CUDA Runtime Library<cuda_toolkit_rt_lib>`
 - :ref:`CUDA Driver Library<cuda_toolkit_driver_lib>`
 - :ref:`cuBLAS<cuda_toolkit_cuBLAS>`
+- :ref:`cuDLA<cuda_toolkit_cuDLA>`
 - :ref:`cuFile<cuda_toolkit_cuFile>`
 - :ref:`cuFFT<cuda_toolkit_cuFFT>`
 - :ref:`cuRAND<cuda_toolkit_cuRAND>`
@@ -166,6 +167,19 @@
 - ``CUDA::cublasLt`` starting in CUDA 10.1
 - ``CUDA::cublasLt_static`` starting in CUDA 10.1
 
+.. _`cuda_toolkit_cuDLA`:
+
+cuDLA
+""""""
+
+.. versionadded:: 3.27
+
+The NVIDIA Tegra Deep Learning Accelerator `cuDLA <https://docs.nvidia.com/cuda/cublas/index.html>`_ library.
+
+Targets Created:
+
+- ``CUDA::cudla`` starting in CUDA 11.6
+
 .. _`cuda_toolkit_cuFile`:
 
 cuFile
@@ -173,7 +187,7 @@
 
 .. versionadded:: 3.25
 
-The NVIDIA GPUDirect Storage `cuFile <https://docs.nvidia.com/cuda/cufile-api/index.html>`_ library.
+The NVIDIA GPUDirect Storage `cuFile <https://docs.nvidia.com/gpudirect-storage/api-reference-guide/index.html>`_ library.
 
 Targets Created:
 
@@ -236,7 +250,7 @@
 cupti
 """""
 
-The `NVIDIA CUDA Profiling Tools Interface <https://developer.nvidia.com/CUPTI>`_.
+The `NVIDIA CUDA Profiling Tools Interface <https://developer.nvidia.com/cupti>`_.
 
 Targets Created:
 
@@ -330,7 +344,7 @@
 nvGRAPH
 """""""
 
-The `nvGRAPH <https://docs.nvidia.com/cuda/nvgraph/index.html>`_ library.
+The `nvGRAPH <https://web.archive.org/web/20201111171403/https://docs.nvidia.com/cuda/nvgraph/index.html>`_ library.
 Removed starting in CUDA 11.0
 
 Targets Created:
@@ -374,7 +388,6 @@
 """""
 
 The `nvRTC <https://docs.nvidia.com/cuda/nvrtc/index.html>`_ (Runtime Compilation) library.
-This is a shared library only.
 
 Targets Created:
 
@@ -417,7 +430,7 @@
 
 .. deprecated:: 3.25 With CUDA 10.0+, use :ref:`nvtx3 <cuda_toolkit_nvtx3>`.
 
-The `NVIDIA Tools Extension <https://docs.nvidia.com/gameworks/content/gameworkslibrary/nvtx/nvidia_tools_extension_library_nvtx.htm>`_.
+The `NVIDIA Tools Extension <https://docs.nvidia.com/nvtx/>`_.
 This is a shared library only.
 
 Targets Created:
@@ -609,8 +622,8 @@
         endif()
         unset(_CUDA_NVCC_OUT)
 
-        mark_as_advanced(CUDAToolkit_BIN_DIR)
         set(CUDAToolkit_BIN_DIR "${CUDAToolkit_BIN_DIR}" CACHE PATH "" FORCE)
+        mark_as_advanced(CUDAToolkit_BIN_DIR)
       endif()
 
       if(CUDAToolkit_SENTINEL_FILE)
@@ -1092,6 +1105,11 @@
     _CUDAToolkit_find_and_add_import_lib(cuFile_rdma_static DEPS cuFile_static culibos)
   endif()
 
+    if(CUDAToolkit_VERSION VERSION_GREATER_EQUAL 11.6)
+    _CUDAToolkit_find_and_add_import_lib(cudla)
+  endif()
+
+
   # cuFFTW depends on cuFFT
   _CUDAToolkit_find_and_add_import_lib(cufftw DEPS cufft)
   _CUDAToolkit_find_and_add_import_lib(cufftw_static DEPS cufft_static)
@@ -1135,6 +1153,7 @@
       "${CUDAToolkit_INCLUDE_DIR}/../extras/CUPTI/include"
       "${CUDAToolkit_INCLUDE_DIR}"
       NO_DEFAULT_PATH)
+  mark_as_advanced(CUDAToolkit_CUPTI_INCLUDE_DIR)
 
   if(CUDAToolkit_CUPTI_INCLUDE_DIR)
     _CUDAToolkit_find_and_add_import_lib(cupti
diff --git a/Modules/FindDart.cmake b/Modules/FindDart.cmake
index 0492578..fed50e1 100644
--- a/Modules/FindDart.cmake
+++ b/Modules/FindDart.cmake
@@ -5,12 +5,20 @@
 FindDart
 --------
 
+.. deprecated:: 3.27
+  This module is available only if policy :policy:`CMP0145` is not set to ``NEW``.
+
 Find DART
 
 This module looks for the dart testing software and sets DART_ROOT to
 point to where it found it.
 #]=======================================================================]
 
+if(_FindDart_testing)
+  set(_FindDart_included TRUE)
+  return()
+endif()
+
 find_path(DART_ROOT README.INSTALL
     HINTS
       ENV DART_ROOT
diff --git a/Modules/FindDoxygen.cmake b/Modules/FindDoxygen.cmake
index ef9801e..76f4759 100644
--- a/Modules/FindDoxygen.cmake
+++ b/Modules/FindDoxygen.cmake
@@ -76,7 +76,8 @@
         [ALL]
         [USE_STAMP_FILE]
         [WORKING_DIRECTORY dir]
-        [COMMENT comment])
+        [COMMENT comment]
+        [CONFIG_FILE filename])
 
   The function constructs a ``Doxyfile`` and defines a custom target that runs
   Doxygen on that generated file. The listed files and directories are used as
@@ -97,6 +98,10 @@
   the :command:`add_custom_target` command used to create the custom target
   internally.
 
+  .. versionadded:: 3.27
+    If ``CONFIG_FILE`` is set, the given file provided with full-path
+    will be used as doxygen configuration file
+
   .. versionadded:: 3.12
     If ``ALL`` is set, the target will be added to the default build target.
 
@@ -864,7 +869,7 @@
 
 function(doxygen_add_docs targetName)
     set(_options ALL USE_STAMP_FILE)
-    set(_one_value_args WORKING_DIRECTORY COMMENT)
+    set(_one_value_args WORKING_DIRECTORY COMMENT CONFIG_FILE)
     set(_multi_value_args)
     cmake_parse_arguments(_args
                           "${_options}"
@@ -1166,8 +1171,15 @@
 
     # Prepare doxygen configuration file
     set(_doxyfile_template "${CMAKE_BINARY_DIR}/CMakeDoxyfile.in")
-    set(_target_doxyfile "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.${targetName}")
-    configure_file("${_doxyfile_template}" "${_target_doxyfile}")
+    if(_args_CONFIG_FILE)
+        if(NOT EXISTS "${_args_CONFIG_FILE}")
+            message(FATAL_ERROR "Option CONFIG_FILE specifies file:\n ${_args_CONFIG_FILE}\nbut it does not exist.")
+        endif()
+        set(_target_doxyfile "${_args_CONFIG_FILE}")
+    else()
+        set(_target_doxyfile "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.${targetName}")
+        configure_file("${_doxyfile_template}" "${_target_doxyfile}")
+    endif()
 
     unset(_all)
     if(${_args_ALL})
diff --git a/Modules/FindEXPAT.cmake b/Modules/FindEXPAT.cmake
index f9cb432..3bedc73 100644
--- a/Modules/FindEXPAT.cmake
+++ b/Modules/FindEXPAT.cmake
@@ -39,27 +39,62 @@
 # Look for the header file.
 find_path(EXPAT_INCLUDE_DIR NAMES expat.h HINTS ${PC_EXPAT_INCLUDE_DIRS})
 
-# Look for the library.
-find_library(EXPAT_LIBRARY NAMES expat libexpat NAMES_PER_DIR HINTS ${PC_EXPAT_LIBRARY_DIRS})
+set(EXPAT_NAMES expat expatw)
+set(EXPAT_NAMES_DEBUG expatd expatwd)
 
-if (EXPAT_INCLUDE_DIR AND EXISTS "${EXPAT_INCLUDE_DIR}/expat.h")
-    file(STRINGS "${EXPAT_INCLUDE_DIR}/expat.h" expat_version_str
-         REGEX "^#[\t ]*define[\t ]+XML_(MAJOR|MINOR|MICRO)_VERSION[\t ]+[0-9]+$")
+if(WIN32)
+  list(APPEND EXPAT_NAMES expatMT expatMD expatwMT expatwMD)
+  list(APPEND EXPAT_NAMES_DEBUG expatdMT expatdMD expatwdMT expatwdMD)
+endif()
 
-    unset(EXPAT_VERSION_STRING)
-    foreach(VPART MAJOR MINOR MICRO)
-        foreach(VLINE ${expat_version_str})
-            if(VLINE MATCHES "^#[\t ]*define[\t ]+XML_${VPART}_VERSION[\t ]+([0-9]+)$")
-                set(EXPAT_VERSION_PART "${CMAKE_MATCH_1}")
-                if(EXPAT_VERSION_STRING)
-                    string(APPEND EXPAT_VERSION_STRING ".${EXPAT_VERSION_PART}")
-                else()
-                    set(EXPAT_VERSION_STRING "${EXPAT_VERSION_PART}")
-                endif()
-            endif()
-        endforeach()
+# Allow EXPAT_LIBRARY to be set manually, as the location of the expat library
+if(NOT EXPAT_LIBRARY)
+  if(DEFINED CMAKE_FIND_LIBRARY_PREFIXES)
+    set(_expat_ORIG_CMAKE_FIND_LIBRARY_PREFIXES "${CMAKE_FIND_LIBRARY_PREFIXES}")
+  else()
+    set(_expat_ORIG_CMAKE_FIND_LIBRARY_PREFIXES)
+  endif()
+
+  if(WIN32)
+    list(APPEND CMAKE_FIND_LIBRARY_PREFIXES "" "lib")
+  endif()
+
+  # Look for the library.
+  find_library(EXPAT_LIBRARY_RELEASE NAMES ${EXPAT_NAMES} NAMES_PER_DIR HINTS ${PC_EXPAT_LIBRARY_DIRS} PATH_SUFFIXES lib)
+  find_library(EXPAT_LIBRARY_DEBUG NAMES ${EXPAT_NAMES_DEBUG} NAMES_PER_DIR HINTS ${PC_EXPAT_LIBRARY_DIRS} PATH_SUFFIXES lib)
+
+  # Restore the original find library ordering
+  if(DEFINED _expat_ORIG_CMAKE_FIND_LIBRARY_PREFIXES)
+    set(CMAKE_FIND_LIBRARY_PREFIXES "${_expat_ORIG_CMAKE_FIND_LIBRARY_PREFIXES}")
+  else()
+    set(CMAKE_FIND_LIBRARY_PREFIXES)
+  endif()
+
+  include(${CMAKE_CURRENT_LIST_DIR}/SelectLibraryConfigurations.cmake)
+  select_library_configurations(EXPAT)
+endif()
+
+unset(EXPAT_NAMES)
+unset(EXPAT_NAMES_DEBUG)
+
+if(EXPAT_INCLUDE_DIR AND EXISTS "${EXPAT_INCLUDE_DIR}/expat.h")
+  file(STRINGS "${EXPAT_INCLUDE_DIR}/expat.h" expat_version_str
+    REGEX "^#[\t ]*define[\t ]+XML_(MAJOR|MINOR|MICRO)_VERSION[\t ]+[0-9]+$")
+
+  unset(EXPAT_VERSION_STRING)
+  foreach(VPART MAJOR MINOR MICRO)
+    foreach(VLINE ${expat_version_str})
+      if(VLINE MATCHES "^#[\t ]*define[\t ]+XML_${VPART}_VERSION[\t ]+([0-9]+)$")
+        set(EXPAT_VERSION_PART "${CMAKE_MATCH_1}")
+        if(EXPAT_VERSION_STRING)
+          string(APPEND EXPAT_VERSION_STRING ".${EXPAT_VERSION_PART}")
+        else()
+          set(EXPAT_VERSION_STRING "${EXPAT_VERSION_PART}")
+        endif()
+      endif()
     endforeach()
-endif ()
+  endforeach()
+endif()
 
 include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake)
 FIND_PACKAGE_HANDLE_STANDARD_ARGS(EXPAT
@@ -68,15 +103,36 @@
 
 # Copy the results to the output variables and target.
 if(EXPAT_FOUND)
-  set(EXPAT_LIBRARIES ${EXPAT_LIBRARY})
   set(EXPAT_INCLUDE_DIRS ${EXPAT_INCLUDE_DIR})
 
+  if(NOT EXPAT_LIBRARIES)
+    set(EXPAT_LIBRARIES ${EXPAT_LIBRARY})
+  endif()
+
   if(NOT TARGET EXPAT::EXPAT)
     add_library(EXPAT::EXPAT UNKNOWN IMPORTED)
     set_target_properties(EXPAT::EXPAT PROPERTIES
       IMPORTED_LINK_INTERFACE_LANGUAGES "C"
-      IMPORTED_LOCATION "${EXPAT_LIBRARY}"
       INTERFACE_INCLUDE_DIRECTORIES "${EXPAT_INCLUDE_DIRS}")
+
+    if(EXPAT_LIBRARY_RELEASE)
+      set_property(TARGET EXPAT::EXPAT APPEND PROPERTY
+        IMPORTED_CONFIGURATIONS RELEASE)
+      set_target_properties(EXPAT::EXPAT PROPERTIES
+        IMPORTED_LOCATION_RELEASE "${EXPAT_LIBRARY_RELEASE}")
+    endif()
+
+    if(EXPAT_LIBRARY_DEBUG)
+      set_property(TARGET EXPAT::EXPAT APPEND PROPERTY
+        IMPORTED_CONFIGURATIONS DEBUG)
+      set_target_properties(EXPAT::EXPAT PROPERTIES
+        IMPORTED_LOCATION_DEBUG "${EXPAT_LIBRARY_DEBUG}")
+    endif()
+
+    if(NOT EXPAT_LIBRARY_RELEASE AND NOT EXPAT_LIBRARY_DEBUG)
+      set_property(TARGET EXPAT::EXPAT APPEND PROPERTY
+        IMPORTED_LOCATION "${EXPAT_LIBRARY}")
+    endif()
   endif()
 endif()
 
diff --git a/Modules/FindHDF5.cmake b/Modules/FindHDF5.cmake
index 6fab08f..a44c6f9 100644
--- a/Modules/FindHDF5.cmake
+++ b/Modules/FindHDF5.cmake
@@ -219,7 +219,7 @@
 function(_HDF5_test_regular_compiler_C success version is_parallel)
   if(NOT ${success} OR
      NOT EXISTS ${_HDF5_TEST_DIR}/compiler_has_h5_c)
-    file(WRITE "${_HDF5_TEST_SRC}"
+    file(WRITE "${_HDF5_TEST_DIR}/${_HDF5_TEST_SRC}"
       "#include <hdf5.h>\n"
       "const char* info_ver = \"INFO\" \":\" H5_VERSION;\n"
       "#ifdef H5_HAVE_PARALLEL\n"
@@ -235,7 +235,7 @@
       "  fid = H5Fcreate(\"foo.h5\",H5F_ACC_TRUNC,H5P_DEFAULT,H5P_DEFAULT);\n"
       "  return 0;\n"
       "}")
-    try_compile(${success} SOURCES "${_HDF5_TEST_SRC}"
+    try_compile(${success} SOURCES "${_HDF5_TEST_DIR}/${_HDF5_TEST_SRC}"
       COPY_FILE ${_HDF5_TEST_DIR}/compiler_has_h5_c
     )
   endif()
@@ -263,7 +263,7 @@
 function(_HDF5_test_regular_compiler_CXX success version is_parallel)
   if(NOT ${success} OR
      NOT EXISTS ${_HDF5_TEST_DIR}/compiler_has_h5_cxx)
-    file(WRITE "${_HDF5_TEST_SRC}"
+    file(WRITE "${_HDF5_TEST_DIR}/${_HDF5_TEST_SRC}"
       "#include <H5Cpp.h>\n"
       "#ifndef H5_NO_NAMESPACE\n"
       "using namespace H5;\n"
@@ -281,7 +281,7 @@
       "  H5File file(\"foo.h5\", H5F_ACC_TRUNC);\n"
       "  return 0;\n"
       "}")
-    try_compile(${success} SOURCES "${_HDF5_TEST_SRC}"
+    try_compile(${success} SOURCES "${_HDF5_TEST_DIR}/${_HDF5_TEST_SRC}"
       COPY_FILE ${_HDF5_TEST_DIR}/compiler_has_h5_cxx
     )
   endif()
@@ -308,22 +308,29 @@
 
 function(_HDF5_test_regular_compiler_Fortran success is_parallel)
   if(NOT ${success})
-    file(WRITE "${_HDF5_TEST_SRC}"
+    file(WRITE "${_HDF5_TEST_DIR}/${_HDF5_TEST_SRC}"
       "program hdf5_hello\n"
       "  use hdf5\n"
       "  integer error\n"
       "  call h5open_f(error)\n"
       "  call h5close_f(error)\n"
       "end\n")
-    try_compile(${success} SOURCES "${_HDF5_TEST_SRC}")
+    try_compile(${success} SOURCES "${_HDF5_TEST_DIR}/${_HDF5_TEST_SRC}")
     if(${success})
       execute_process(COMMAND ${CMAKE_Fortran_COMPILER} -showconfig
         OUTPUT_VARIABLE config_output
         ERROR_VARIABLE config_error
         RESULT_VARIABLE config_result
         )
-      if(config_output MATCHES "Parallel HDF5: yes")
-        set(${is_parallel} TRUE PARENT_SCOPE)
+      if(config_output MATCHES "Parallel HDF5: ([A-Za-z0-9]+)")
+        # The value may be anything used when HDF5 was configured,
+        # so see if CMake interprets it as "true".
+        set(parallelHDF5 "${CMAKE_MATCH_1}")
+        if(parallelHDF5)
+          set(${is_parallel} TRUE PARENT_SCOPE)
+        else()
+          set(${is_parallel} FALSE PARENT_SCOPE)
+        endif()
       else()
         set(${is_parallel} FALSE PARENT_SCOPE)
       endif()
@@ -349,9 +356,13 @@
     ERROR_VARIABLE output
     RESULT_VARIABLE return_value
     )
-  if(return_value AND NOT HDF5_FIND_QUIETLY)
-    message(STATUS
-      "HDF5 ${language} compiler wrapper is unable to compile a minimal HDF5 program.")
+  if(NOT return_value EQUAL 0)
+    message(CONFIGURE_LOG
+      "HDF5 ${language} compiler wrapper is unable to compile a minimal HDF5 program.\n\n${output}")
+    if(NOT HDF5_FIND_QUIETLY)
+      message(STATUS
+        "HDF5 ${language} compiler wrapper is unable to compile a minimal HDF5 program.")
+    endif()
   else()
     execute_process(
       COMMAND ${HDF5_${language}_COMPILER_EXECUTABLE} -show ${lib_type_args} "${_HDF5_TEST_SRC}"
@@ -361,9 +372,13 @@
       RESULT_VARIABLE return_value
       OUTPUT_STRIP_TRAILING_WHITESPACE
       )
-    if(return_value AND NOT HDF5_FIND_QUIETLY)
-      message(STATUS
-        "Unable to determine HDF5 ${language} flags from HDF5 wrapper.")
+    if(NOT return_value EQUAL 0)
+      message(CONFIGURE_LOG
+        "Unable to determine HDF5 ${language} flags from HDF5 wrapper.\n\n${output}")
+      if(NOT HDF5_FIND_QUIETLY)
+        message(STATUS
+          "Unable to determine HDF5 ${language} flags from HDF5 wrapper.")
+      endif()
     endif()
     execute_process(
       COMMAND ${HDF5_${language}_COMPILER_EXECUTABLE} -showconfig
@@ -372,17 +387,26 @@
       RESULT_VARIABLE return_value
       OUTPUT_STRIP_TRAILING_WHITESPACE
       )
-    if(return_value AND NOT HDF5_FIND_QUIETLY)
-      message(STATUS
-        "Unable to determine HDF5 ${language} version_var from HDF5 wrapper.")
+    if(NOT return_value EQUAL 0)
+      message(CONFIGURE_LOG
+        "Unable to determine HDF5 ${language} version_var from HDF5 wrapper.\n\n${output}")
+      if(NOT HDF5_FIND_QUIETLY)
+        message(STATUS
+          "Unable to determine HDF5 ${language} version_var from HDF5 wrapper.")
+      endif()
     endif()
     string(REGEX MATCH "HDF5 Version: ([a-zA-Z0-9\\.\\-]*)" version "${config_output}")
     if(version)
       string(REPLACE "HDF5 Version: " "" version "${version}")
       string(REPLACE "-patch" "." version "${version}")
     endif()
-    if(config_output MATCHES "Parallel HDF5: yes")
-      set(is_parallel TRUE)
+    if(config_output MATCHES "Parallel HDF5: ([A-Za-z0-9]+)")
+      # The value may be anything used when HDF5 was configured,
+      # so see if CMake interprets it as "true".
+      set(parallelHDF5 "${CMAKE_MATCH_1}")
+      if(parallelHDF5)
+        set(is_parallel TRUE)
+      endif()
     endif()
   endif()
   foreach(var output return_value version is_parallel)
@@ -576,23 +600,23 @@
 
     # First check to see if our regular compiler is one of wrappers
     if(_lang STREQUAL "C")
-      set(_HDF5_TEST_SRC ${_HDF5_TEST_DIR}/cmake_hdf5_test.c)
+      set(_HDF5_TEST_SRC cmake_hdf5_test.c)
       if(CMAKE_CXX_COMPILER_LOADED AND NOT CMAKE_C_COMPILER_LOADED)
         # CXX project without C enabled
-        set(_HDF5_TEST_SRC ${_HDF5_TEST_DIR}/cmake_hdf5_test.cxx)
+        set(_HDF5_TEST_SRC cmake_hdf5_test.cxx)
       endif()
       _HDF5_test_regular_compiler_C(
         HDF5_${_lang}_COMPILER_NO_INTERROGATE
         HDF5_${_lang}_VERSION
         HDF5_${_lang}_IS_PARALLEL)
     elseif(_lang STREQUAL "CXX")
-      set(_HDF5_TEST_SRC ${_HDF5_TEST_DIR}/cmake_hdf5_test.cxx)
+      set(_HDF5_TEST_SRC cmake_hdf5_test.cxx)
       _HDF5_test_regular_compiler_CXX(
         HDF5_${_lang}_COMPILER_NO_INTERROGATE
         HDF5_${_lang}_VERSION
         HDF5_${_lang}_IS_PARALLEL)
     elseif(_lang STREQUAL "Fortran")
-      set(_HDF5_TEST_SRC ${_HDF5_TEST_DIR}/cmake_hdf5_test.f90)
+      set(_HDF5_TEST_SRC cmake_hdf5_test.f90)
       _HDF5_test_regular_compiler_Fortran(
         HDF5_${_lang}_COMPILER_NO_INTERROGATE
         HDF5_${_lang}_IS_PARALLEL)
diff --git a/Modules/FindLAPACK.cmake b/Modules/FindLAPACK.cmake
index 1eecb1c..4d3ab5a 100644
--- a/Modules/FindLAPACK.cmake
+++ b/Modules/FindLAPACK.cmake
@@ -304,10 +304,12 @@
 # Search for different LAPACK distributions if BLAS is found
 if(NOT LAPACK_NOT_FOUND_MESSAGE)
   set(LAPACK_LINKER_FLAGS ${BLAS_LINKER_FLAGS})
-  if(NOT $ENV{BLA_VENDOR} STREQUAL "")
-    set(BLA_VENDOR $ENV{BLA_VENDOR})
-  elseif(NOT BLA_VENDOR)
-    set(BLA_VENDOR "All")
+  if(NOT BLA_VENDOR)
+    if(NOT "$ENV{BLA_VENDOR}" STREQUAL "")
+      set(BLA_VENDOR "$ENV{BLA_VENDOR}")
+    else()
+      set(BLA_VENDOR "All")
+    endif()
   endif()
 
   # LAPACK in the Intel MKL 10+ library?
@@ -559,6 +561,29 @@
     endif()
   endif()
 
+  # AOCL? (https://developer.amd.com/amd-aocl/)
+  if(NOT LAPACK_LIBRARIES
+      AND (BLA_VENDOR MATCHES "AOCL" OR BLA_VENDOR STREQUAL "All"))
+    if(_lapack_sizeof_integer EQUAL 8)
+      set(_lapack_aocl_subdir "ILP64")
+    else()
+      set(_lapack_aocl_subdir "LP64")
+    endif()
+
+    check_lapack_libraries(
+      LAPACK_LIBRARIES
+      LAPACK
+      cheev
+      ""
+      "flame"
+      "-fopenmp"
+      ""
+      "${_lapack_aocl_subdir}"
+      "${BLAS_LIBRARIES}"
+    )
+    unset(_lapack_aocl_subdir)
+  endif()
+
   # LAPACK in SCSL library? (SGI/Cray Scientific Library)
   if(NOT LAPACK_LIBRARIES
       AND (BLA_VENDOR MATCHES "SCSL" OR BLA_VENDOR STREQUAL "All"))
diff --git a/Modules/FindMPI.cmake b/Modules/FindMPI.cmake
index 1fbb4f9..e3246c6 100644
--- a/Modules/FindMPI.cmake
+++ b/Modules/FindMPI.cmake
@@ -1554,7 +1554,7 @@
           endif()
         endif()
 
-        # We are on a Cray, environment identfier: PE_ENV is set (CRAY), and
+        # We are on a Cray, environment identifier: PE_ENV is set (CRAY), and
         # have NOT found an mpic++-like compiler wrapper (previous block),
         # and we do NOT use the Cray cc/CC compiler wrappers as CC/CXX CMake
         # compiler.
diff --git a/Modules/FindMatlab.cmake b/Modules/FindMatlab.cmake
index 34b1c5b..3ab6bc1 100644
--- a/Modules/FindMatlab.cmake
+++ b/Modules/FindMatlab.cmake
@@ -137,6 +137,12 @@
 ``Matlab_FOUND``
   ``TRUE`` if the Matlab installation is found, ``FALSE``
   otherwise. All variable below are defined if Matlab is found.
+``Matlab_VERSION``
+  .. versionadded:: 3.27
+
+  the numerical version (e.g. 9.13) of Matlab found. Not to be confused with
+  Matlab release name (e.g. R2022b) that can be obtained with
+  :command:`matlab_get_release_name_from_version`.
 ``Matlab_ROOT_DIR``
   the final root of the Matlab installation determined by the FindMatlab
   module.
@@ -337,7 +343,14 @@
 #[=======================================================================[.rst:
 .. command:: matlab_get_version_from_release_name
 
-  Returns the version of Matlab (17.58) from a release name (R2017k)
+  .. code-block:: cmake
+
+    matlab_get_version_from_release_name(release version)
+
+  * Input: ``release`` is the release name (R2022b)
+  * Output: ``version`` is the version of Matlab (9.13)
+
+  Returns the version of Matlab from a release name
 #]=======================================================================]
 macro(matlab_get_version_from_release_name release_name version_name)
 
@@ -354,13 +367,17 @@
 endmacro()
 
 
-
-
-
 #[=======================================================================[.rst:
 .. command:: matlab_get_release_name_from_version
 
-  Returns the release name (R2017k) from the version of Matlab (17.58)
+  .. code-block:: cmake
+
+    matlab_get_release_name_from_version(version release_name)
+
+  * Input: ``version`` is the version of Matlab (9.13)
+  * Output: ``release_name`` is the release name (R2022b)
+
+  Returns the release name from the version of Matlab
 #]=======================================================================]
 macro(matlab_get_release_name_from_version version release_name)
 
@@ -371,7 +388,7 @@
       set(${release_name} ${CMAKE_MATCH_1})
       break()
     endif()
-  endforeach(_var)
+  endforeach()
 
   unset(_var)
   unset(_matched)
@@ -382,10 +399,7 @@
 endmacro()
 
 
-
-
-
-# extracts all the supported release names (R2017k...) of Matlab
+# extracts all the supported release names (R2022b...) of Matlab
 # internal use
 macro(matlab_get_supported_releases list_releases)
   set(${list_releases})
@@ -396,7 +410,7 @@
     endif()
     unset(_matched)
     unset(CMAKE_MATCH_1)
-  endforeach(_var)
+  endforeach()
   unset(_var)
 endmacro()
 
@@ -413,7 +427,7 @@
     endif()
     unset(_matched)
     unset(CMAKE_MATCH_1)
-  endforeach(_var)
+  endforeach()
   unset(_var)
 endmacro()
 
@@ -421,8 +435,15 @@
 #[=======================================================================[.rst:
 .. command:: matlab_extract_all_installed_versions_from_registry
 
-  This function parses the registry and founds the Matlab versions that are
-  installed. The found versions are returned in `matlab_versions`.
+  .. code-block:: cmake
+
+    matlab_extract_all_installed_versions_from_registry(win64 matlab_versions)
+
+  * Input: ``win64`` is a boolean to search for the 64 bit version of Matlab
+  * Output: ``matlab_versions`` is a list of all the versions of Matlab found
+
+  This function parses the Windows registry and founds the Matlab versions that
+  are installed. The found versions are returned in `matlab_versions`.
   Set `win64` to `TRUE` if the 64 bit version of Matlab should be looked for
   The returned list contains all versions under
   ``HKLM\\SOFTWARE\\Mathworks\\MATLAB`` and
@@ -505,31 +526,6 @@
   set(matlab_supported_versions)
   matlab_get_supported_versions(matlab_supported_versions)
 
-
-  # this is a manual population of the versions we want to look for
-  # this can be done as is, but preferably with the call to
-  # matlab_get_supported_versions and variable
-
-  # populating the versions we want to look for
-  # set(matlab_supported_versions)
-
-  # # Matlab 7
-  # set(matlab_major 7)
-  # foreach(current_matlab_minor RANGE 4 20)
-    # list(APPEND matlab_supported_versions "${matlab_major}.${current_matlab_minor}")
-  # endforeach(current_matlab_minor)
-
-  # # Matlab 8
-  # set(matlab_major 8)
-  # foreach(current_matlab_minor RANGE 0 5)
-    # list(APPEND matlab_supported_versions "${matlab_major}.${current_matlab_minor}")
-  # endforeach(current_matlab_minor)
-
-  # # taking into account the possible additional versions provided by the user
-  # if(DEFINED MATLAB_ADDITIONAL_VERSIONS)
-    # list(APPEND matlab_supported_versions MATLAB_ADDITIONAL_VERSIONS)
-  # endif()
-
   # we order from more recent to older
   if(matlab_supported_versions)
     list(REMOVE_DUPLICATES matlab_supported_versions)
@@ -541,8 +537,6 @@
 endmacro()
 
 
-
-
 #[=======================================================================[.rst:
 .. command:: matlab_get_all_valid_matlab_roots_from_registry
 
@@ -552,16 +546,12 @@
   ``(type,version_number,matlab_root_path)``, where ``type``
   indicates either ``MATLAB`` or ``MCR``.
 
-  ::
+  .. code-block:: cmake
 
-    matlab_get_all_valid_matlab_roots_from_registry(
-        matlab_versions
-        matlab_roots)
+    matlab_get_all_valid_matlab_roots_from_registry(matlab_versions matlab_roots)
 
-  ``matlab_versions``
-    the versions of each of the Matlab or MCR installations
-  ``matlab_roots``
-    the location of each of the Matlab or MCR installations
+  * Input: ``matlab_versions`` of each of the Matlab or MCR installations
+  * Output: ``matlab_roots`` location of each of the Matlab or MCR installations
 #]=======================================================================]
 function(matlab_get_all_valid_matlab_roots_from_registry matlab_versions matlab_roots)
 
@@ -571,7 +561,7 @@
 
   set(_matlab_roots_list )
   # check for Matlab installations
-  foreach(_matlab_current_version ${matlab_versions})
+  foreach(_matlab_current_version IN LISTS matlab_versions)
     get_filename_component(
       current_MATLAB_ROOT
       "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MathWorks\\MATLAB\\${_matlab_current_version};MATLABROOT]"
@@ -584,7 +574,7 @@
   endforeach()
 
   # Check for MCR installations
-  foreach(_matlab_current_version ${matlab_versions})
+  foreach(_matlab_current_version IN LISTS matlab_versions)
     get_filename_component(
       current_MATLAB_ROOT
       "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MathWorks\\MATLAB Runtime\\${_matlab_current_version};MATLABROOT]"
@@ -600,7 +590,7 @@
   endforeach()
 
   # Check for old MCR installations
-  foreach(_matlab_current_version ${matlab_versions})
+  foreach(_matlab_current_version IN LISTS matlab_versions)
     get_filename_component(
       current_MATLAB_ROOT
       "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MathWorks\\MATLAB Compiler Runtime\\${_matlab_current_version};MATLABROOT]"
@@ -624,16 +614,12 @@
   This function should not be called before the appropriate Matlab root has
   been found.
 
-  ::
+  .. code-block:: cmake
 
-    matlab_get_mex_suffix(
-        matlab_root
-        mex_suffix)
+    matlab_get_mex_suffix(matlab_root mex_suffix)
 
-  ``matlab_root``
-    the root of the Matlab/MCR installation
-  ``mex_suffix``
-    the variable name in which the suffix will be returned.
+  * Input: ``matlab_root`` root of Matlab/MCR install e.g. ``Matlab_ROOT_DIR``
+  * Output: ``mex_suffix`` variable name in which the suffix will be returned.
 #]=======================================================================]
 function(matlab_get_mex_suffix matlab_root mex_suffix)
 
@@ -711,8 +697,6 @@
 endfunction()
 
 
-
-
 #[=======================================================================[.rst:
 .. command:: matlab_get_version_from_matlab_run
 
@@ -720,16 +704,12 @@
   version. If the path provided for the Matlab installation points to an MCR
   installation, the version is extracted from the installed files.
 
-  ::
+  .. code-block:: cmake
 
-    matlab_get_version_from_matlab_run(
-        matlab_binary_path
-        matlab_list_versions)
+    matlab_get_version_from_matlab_run(matlab_binary_path matlab_list_versions)
 
-  ``matlab_binary_path``
-    the location of the `matlab` binary executable
-  ``matlab_list_versions``
-    the version extracted from Matlab
+  * Input: ``matlab_binary_path`` path of the `matlab` binary executable
+  * Output: ``matlab_list_versions`` the version extracted from Matlab
 #]=======================================================================]
 function(matlab_get_version_from_matlab_run matlab_binary_program matlab_list_versions)
 
@@ -899,7 +879,7 @@
   non 0 failure). Additional arguments accepted by :command:`add_test` can be
   passed through ``TEST_ARGS`` (eg. ``CONFIGURATION <config> ...``).
 
-  ::
+  .. code-block:: cmake
 
     matlab_add_unit_test(
         NAME <name>
@@ -913,7 +893,7 @@
         [NO_UNITTEST_FRAMEWORK]
         )
 
-  The function arguments are:
+  Function Parameters:
 
   ``NAME``
     name of the unittest in ctest.
@@ -971,7 +951,7 @@
   endif()
 
   # The option to run a batch program with MATLAB changes depending on the MATLAB version
-  # For MATLAB before R2019a (9.6), the only supported option is -r, afterwords the suggested option
+  # For MATLAB before R2019a (9.6), the only supported option is -r, afterwards the suggested option
   # is -batch as -r is deprecated
   set(maut_BATCH_OPTION "-r")
   if(NOT (Matlab_VERSION_STRING STREQUAL ""))
@@ -1011,7 +991,7 @@
   for the MEX file. Remaining arguments of the call are passed to the
   :command:`add_library` or :command:`add_executable` command.
 
-  ::
+  .. code-block:: cmake
 
      matlab_add_mex(
          NAME <name>
@@ -1026,6 +1006,8 @@
          [...]
      )
 
+  Function Parameters:
+
   ``NAME``
     name of the target.
   ``SRC``
@@ -1210,18 +1192,17 @@
 
     if (MSVC)
 
-      set(_link_flags "${_link_flags} /EXPORT:mexFunction")
+      string(APPEND _link_flags " /EXPORT:mexFunction")
       if(NOT Matlab_VERSION_STRING VERSION_LESS "9.1") # For 9.1 (R2016b) and newer, export version
-        set(_link_flags "${_link_flags} /EXPORT:mexfilerequiredapiversion")
+        string(APPEND _link_flags " /EXPORT:mexfilerequiredapiversion")
       endif()
 
       set_property(TARGET ${${prefix}_NAME} APPEND PROPERTY LINK_FLAGS ${_link_flags})
 
     endif() # No other compiler currently supported on Windows.
 
-    set_target_properties(${${prefix}_NAME}
-      PROPERTIES
-        DEFINE_SYMBOL "DLL_EXPORT_SYM=__declspec(dllexport)")
+    set_property(TARGET ${${prefix}_NAME} PROPERTY
+      DEFINE_SYMBOL "DLL_EXPORT_SYM=__declspec(dllexport)")
 
   else()
 
@@ -1247,7 +1228,7 @@
 
       if(Matlab_HAS_CPP_API)
         list(APPEND _ver_map_files ${Matlab_EXTERN_LIBRARY_DIR}/cppMexFunction.map) # This one doesn't exist on Linux
-        set(_link_flags "${_link_flags} -Wl,-U,_mexCreateMexFunction -Wl,-U,_mexDestroyMexFunction -Wl,-U,_mexFunctionAdapter")
+        string(APPEND _link_flags " -Wl,-U,_mexCreateMexFunction -Wl,-U,_mexDestroyMexFunction -Wl,-U,_mexFunctionAdapter")
         # On MacOS, the MEX command adds the above, without it the link breaks
         # because we indiscriminately use "cppMexFunction.map" even for C API MEX-files.
       endif()
@@ -1262,14 +1243,14 @@
         target_compile_options(${${prefix}_NAME} PRIVATE "-pthread")
       endif()
 
-      set(_link_flags "${_link_flags} -Wl,--as-needed")
+      string(APPEND _link_flags " -Wl,--as-needed")
 
       set(_export_flag_name --version-script)
 
     endif()
 
-    foreach(_file ${_ver_map_files})
-      set(_link_flags "${_link_flags} -Wl,${_export_flag_name},${_file}")
+    foreach(_file IN LISTS _ver_map_files)
+      string(APPEND _link_flags " -Wl,${_export_flag_name},${_file}")
     endforeach()
 
     # The `mex` command doesn't add this define. It is specified here in order
@@ -2021,11 +2002,13 @@
 _Matlab_add_imported_target(ENGINE MatlabEngine)
 _Matlab_add_imported_target(DATAARRAY MatlabDataArray)
 
+set(Matlab_VERSION ${Matlab_VERSION_STRING})
+
 find_package_handle_standard_args(
   Matlab
   FOUND_VAR Matlab_FOUND
   REQUIRED_VARS ${_matlab_required_variables}
-  VERSION_VAR Matlab_VERSION_STRING
+  VERSION_VAR Matlab_VERSION
   HANDLE_COMPONENTS)
 
 unset(_matlab_required_variables)
diff --git a/Modules/FindOpenAL.cmake b/Modules/FindOpenAL.cmake
index 53aafdc..3d58569 100644
--- a/Modules/FindOpenAL.cmake
+++ b/Modules/FindOpenAL.cmake
@@ -105,18 +105,16 @@
 
 mark_as_advanced(OPENAL_LIBRARY OPENAL_INCLUDE_DIR)
 
-if(OPENAL_INCLUDE_DIR AND OPENAL_LIBRARY)
-  if(NOT TARGET OpenAL::OpenAL)
-    if(EXISTS "${OPENAL_LIBRARY}")
-      add_library(OpenAL::OpenAL UNKNOWN IMPORTED)
-      set_target_properties(OpenAL::OpenAL PROPERTIES
-        IMPORTED_LOCATION "${OPENAL_LIBRARY}")
-    else()
-      add_library(OpenAL::OpenAL INTERFACE IMPORTED)
-      set_target_properties(OpenAL::OpenAL PROPERTIES
-        IMPORTED_LIBNAME "${OPENAL_LIBRARY}")
-    endif()
+if(OPENAL_FOUND AND NOT TARGET OpenAL::OpenAL)
+  if(OPENAL_LIBRARY MATCHES "/([^/]+)\\.framework$")
+    add_library(OpenAL::OpenAL INTERFACE IMPORTED)
     set_target_properties(OpenAL::OpenAL PROPERTIES
-      INTERFACE_INCLUDE_DIRECTORIES "${OPENAL_INCLUDE_DIR}")
+      INTERFACE_LINK_LIBRARIES "${OPENAL_LIBRARY}")
+  else()
+    add_library(OpenAL::OpenAL UNKNOWN IMPORTED)
+    set_target_properties(OpenAL::OpenAL PROPERTIES
+      IMPORTED_LOCATION "${OPENAL_LIBRARY}")
   endif()
+  set_target_properties(OpenAL::OpenAL PROPERTIES
+    INTERFACE_INCLUDE_DIRECTORIES "${OPENAL_INCLUDE_DIR}")
 endif()
diff --git a/Modules/FindOpenCL.cmake b/Modules/FindOpenCL.cmake
index 2b700ff..55be667 100644
--- a/Modules/FindOpenCL.cmake
+++ b/Modules/FindOpenCL.cmake
@@ -39,6 +39,8 @@
 
 #]=======================================================================]
 
+set(_OPENCL_x86 "(x86)")
+
 function(_FIND_OPENCL_VERSION)
   include(CheckSymbolExists)
   include(CMakePushCheckState)
@@ -79,6 +81,9 @@
     CL/cl.h OpenCL/cl.h
   PATHS
     ENV "PROGRAMFILES(X86)"
+    ENV "PROGRAMFILES"
+    $ENV{PROGRAMFILES${_OPENCL_x86}}/OpenCLHeaders
+    $ENV{PROGRAMFILES}/OpenCLHeaders
     ENV AMDAPPSDKROOT
     ENV INTELOCLSDKROOT
     ENV NVSDKCOMPUTE_ROOT
@@ -100,6 +105,9 @@
       NAMES OpenCL
       PATHS
         ENV "PROGRAMFILES(X86)"
+        ENV "PROGRAMFILES"
+        $ENV{PROGRAMFILES${_OPENCL_x86}}/OpenCL-ICD-Loader
+        $ENV{PROGRAMFILES}/OpenCL-ICD-Loader
         ENV AMDAPPSDKROOT
         ENV INTELOCLSDKROOT
         ENV CUDA_PATH
@@ -116,6 +124,9 @@
       NAMES OpenCL
       PATHS
         ENV "PROGRAMFILES(X86)"
+        ENV "PROGRAMFILES"
+        $ENV{PROGRAMFILES${_OPENCL_x86}}/OpenCL-ICD-Loader
+        $ENV{PROGRAMFILES}/OpenCL-ICD-Loader
         ENV AMDAPPSDKROOT
         ENV INTELOCLSDKROOT
         ENV CUDA_PATH
@@ -126,6 +137,7 @@
         "AMD APP/lib/x86_64"
         lib/x86_64
         lib/x64
+        lib
         OpenCL/common/lib/x64)
   endif()
 else()
@@ -156,6 +168,8 @@
   endif()
 endif()
 
+unset(_OPENCL_x86)
+
 set(OpenCL_LIBRARIES ${OpenCL_LIBRARY})
 set(OpenCL_INCLUDE_DIRS ${OpenCL_INCLUDE_DIR})
 
diff --git a/Modules/FindOpenGL.cmake b/Modules/FindOpenGL.cmake
index a9a1b2a..843f787 100644
--- a/Modules/FindOpenGL.cmake
+++ b/Modules/FindOpenGL.cmake
@@ -18,8 +18,26 @@
 
 .. versionadded:: 3.10
 
-This module respects several optional COMPONENTS: ``EGL``, ``GLX``, and
-``OpenGL``.  There are corresponding import targets for each of these flags.
+This module respects several optional COMPONENTS:
+
+``EGL``
+  The EGL interface between OpenGL, OpenGL ES and the underlying windowing system.
+
+``GLX``
+  An extension to X that interfaces OpenGL, OpenGL ES with X window system.
+
+``OpenGL``
+  The cross platform API for 3D graphics.
+
+``GLES2``
+  .. versionadded:: 3.27
+
+  A subset of OpenGL API for embedded systems with limited capabilities.
+
+``GLES3``
+  .. versionadded:: 3.27
+
+  A subset of OpenGL API for embedded systems with more capabilities.
 
 IMPORTED Targets
 ^^^^^^^^^^^^^^^^
@@ -42,6 +60,14 @@
   Defined if the system has OpenGL Extension to the X Window System (GLX).
 ``OpenGL::EGL``
   Defined if the system has EGL.
+``OpenGL::GLES2``
+  .. versionadded:: 3.27
+
+  Defined if the system has GLES2.
+``OpenGL::GLES3``
+  .. versionadded:: 3.27
+
+  Defined if the system has GLES3.
 
 Result Variables
 ^^^^^^^^^^^^^^^^
@@ -60,6 +86,10 @@
  True, if the system has GLX.
 ``OpenGL_EGL_FOUND``
  True, if the system has EGL.
+``OpenGL::GLES2``
+ Defined if the system has GLES2.
+``OpenGL::GLES3``
+ Defined if the system has GLES3.
 ``OPENGL_INCLUDE_DIR``
  Path to the OpenGL include directory.
 ``OPENGL_EGL_INCLUDE_DIRS``
@@ -88,6 +118,14 @@
 ``OPENGL_gl_LIBRARY``
  Path to the OpenGL library.  New code should prefer the ``OpenGL::*`` import
  targets.
+``OPENGL_gles2_LIBRARY``
+  .. versionadded:: 3.27
+
+  Path to the OpenGL GLES2 library.
+``OPENGL_gles3_LIBRARY``
+  .. versionadded:: 3.27
+
+  Path to the OpenGL GLES3 library.
 
 .. versionadded:: 3.10
   Variables for GLVND-specific libraries ``OpenGL``, ``EGL`` and ``GLX``.
@@ -122,7 +160,7 @@
 
  .. versionchanged:: 3.11
   This is the default, unless policy :policy:`CMP0072` is set to ``OLD``
-  and no components are requeted (since components
+  and no components are requested (since components
   correspond to GLVND libraries).
 
 ``LEGACY``
@@ -182,7 +220,10 @@
     OPENGL_glu_LIBRARY
     )
 else()
-  if (CMAKE_SYSTEM_NAME MATCHES "HP-UX")
+  if (CMAKE_ANDROID_NDK)
+    set(_OPENGL_INCLUDE_PATH ${CMAKE_ANDROID_NDK}/sysroot/usr/include)
+    set(_OPENGL_LIB_PATH ${CMAKE_ANDROID_NDK}/platforms/android-${CMAKE_SYSTEM_VERSION}/arch-${CMAKE_ANDROID_ARCH}/usr/lib)
+  elseif (CMAKE_SYSTEM_NAME MATCHES "HP-UX")
     # Handle HP-UX cases where we only want to find OpenGL in either hpux64
     # or hpux32 depending on if we're doing a 64 bit build.
     if(CMAKE_SIZEOF_VOID_P EQUAL 4)
@@ -198,6 +239,13 @@
       /boot/develop/lib/x86)
     set(_OPENGL_INCLUDE_PATH
       /boot/develop/headers/os/opengl)
+  elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux")
+    # CMake doesn't support arbitrary globs in search paths.
+    file(GLOB _OPENGL_LIB_PATH
+      # The NVidia driver installation tool on Linux installs libraries to a
+      # `nvidia-<version>` subdirectory.
+      "/usr/lib/nvidia-*"
+      "/usr/lib32/nvidia-*")
   endif()
 
   # The first line below is to make sure that the proper headers
@@ -215,15 +263,20 @@
   )
   find_path(OPENGL_GLX_INCLUDE_DIR GL/glx.h ${_OPENGL_INCLUDE_PATH})
   find_path(OPENGL_EGL_INCLUDE_DIR EGL/egl.h ${_OPENGL_INCLUDE_PATH})
+  find_path(OPENGL_GLES2_INCLUDE_DIR GLES2/gl2.h ${_OPENGL_INCLUDE_PATH})
+  find_path(OPENGL_GLES3_INCLUDE_DIR GLES3/gl3.h ${_OPENGL_INCLUDE_PATH})
   find_path(OPENGL_xmesa_INCLUDE_DIR GL/xmesa.h
     /usr/share/doc/NVIDIA_GLX-1.0/include
     /usr/openwin/share/include
     /opt/graphics/OpenGL/include
   )
+
   list(APPEND _OpenGL_CACHE_VARS
     OPENGL_INCLUDE_DIR
     OPENGL_GLX_INCLUDE_DIR
     OPENGL_EGL_INCLUDE_DIR
+    OPENGL_GLES2_INCLUDE_DIR
+    OPENGL_GLES3_INCLUDE_DIR
     OPENGL_xmesa_INCLUDE_DIR
     )
 
@@ -246,6 +299,17 @@
     PATH_SUFFIXES libglvnd
   )
 
+  find_library(OPENGL_gles2_LIBRARY
+    NAMES GLESv2
+    PATHS ${_OPENGL_LIB_PATH}
+  )
+
+  find_library(OPENGL_gles3_LIBRARY
+    NAMES GLESv3
+          GLESv2 # mesa provides only libGLESv2
+    PATHS ${_OPENGL_LIB_PATH}
+  )
+
   find_library(OPENGL_glu_LIBRARY
     NAMES GLU MesaGLU
     PATHS ${OPENGL_gl_LIBRARY}
@@ -258,6 +322,8 @@
     OPENGL_opengl_LIBRARY
     OPENGL_glx_LIBRARY
     OPENGL_egl_LIBRARY
+    OPENGL_gles2_LIBRARY
+    OPENGL_gles3_LIBRARY
     OPENGL_glu_LIBRARY
     )
 
@@ -338,12 +404,16 @@
           OPENGL_glx_LIBRARY AND
       NOT OPENGL_gl_LIBRARY) OR
      (NOT OPENGL_USE_EGL AND
+      NOT OPENGL_USE_GLES3 AND
+      NOT OPENGL_USE_GLES2 AND
       NOT OPENGL_glx_LIBRARY AND
       NOT OPENGL_gl_LIBRARY) OR
      (NOT OPENGL_USE_EGL AND
           OPENGL_opengl_LIBRARY AND
           OPENGL_glx_LIBRARY) OR
-     (    OPENGL_USE_EGL))
+     (NOT OPENGL_USE_GLES3 AND
+      NOT OPENGL_USE_GLES2 AND
+          OPENGL_USE_EGL))
     list(APPEND _OpenGL_REQUIRED_VARS OPENGL_opengl_LIBRARY)
   endif()
 
@@ -351,13 +421,19 @@
   if((NOT OPENGL_USE_OPENGL AND
       NOT OPENGL_USE_GLX AND
       NOT OPENGL_USE_EGL AND
+      NOT OPENGL_USE_GLES3 AND
+      NOT OPENGL_USE_GLES2 AND
       NOT OPENGL_glx_LIBRARY AND
       NOT OPENGL_gl_LIBRARY) OR
      (    OPENGL_USE_GLX AND
       NOT OPENGL_USE_EGL AND
+      NOT OPENGL_USE_GLES3 AND
+      NOT OPENGL_USE_GLES2 AND
       NOT OPENGL_glx_LIBRARY AND
       NOT OPENGL_gl_LIBRARY) OR
      (NOT OPENGL_USE_EGL AND
+      NOT OPENGL_USE_GLES3 AND
+      NOT OPENGL_USE_GLES2 AND
           OPENGL_opengl_LIBRARY AND
           OPENGL_glx_LIBRARY) OR
      (OPENGL_USE_GLX AND OPENGL_USE_EGL))
@@ -369,6 +445,16 @@
     list(APPEND _OpenGL_REQUIRED_VARS OPENGL_egl_LIBRARY)
   endif()
 
+  # GLVND GLES2 library.
+  if(OPENGL_USE_GLES2)
+    list(APPEND _OpenGL_REQUIRED_VARS OPENGL_gles2_LIBRARY)
+  endif()
+
+  # GLVND GLES3 library.
+  if(OPENGL_USE_GLES3)
+    list(APPEND _OpenGL_REQUIRED_VARS OPENGL_gles3_LIBRARY)
+  endif()
+
   # Old-style "libGL" library: used as a fallback when GLVND isn't available.
   if((NOT OPENGL_USE_EGL AND
       NOT OPENGL_opengl_LIBRARY AND
@@ -381,7 +467,11 @@
   endif()
 
   # We always need the 'gl.h' include dir.
-  list(APPEND _OpenGL_REQUIRED_VARS OPENGL_INCLUDE_DIR)
+  if(OPENGL_USE_EGL)
+    list(APPEND _OpenGL_REQUIRED_VARS OPENGL_EGL_INCLUDE_DIR)
+  else()
+    list(APPEND _OpenGL_REQUIRED_VARS OPENGL_INCLUDE_DIR)
+  endif()
 
   unset(_OPENGL_INCLUDE_PATH)
   unset(_OPENGL_LIB_PATH)
@@ -428,6 +518,18 @@
   set(OpenGL_EGL_FOUND FALSE)
 endif()
 
+if(OPENGL_gles2_LIBRARY AND OPENGL_GLES2_INCLUDE_DIR)
+  set(OpenGL_GLES2_FOUND TRUE)
+else()
+  set(OpenGL_GLES2_FOUND FALSE)
+endif()
+
+if(OPENGL_gles3_LIBRARY AND OPENGL_GLES3_INCLUDE_DIR)
+  set(OpenGL_GLES3_FOUND TRUE)
+else()
+  set(OpenGL_GLES3_FOUND FALSE)
+endif()
+
 # User-visible names should be plural.
 if(OPENGL_EGL_INCLUDE_DIR)
   set(OPENGL_EGL_INCLUDE_DIRS ${OPENGL_EGL_INCLUDE_DIR})
@@ -461,6 +563,7 @@
     endif()
     set_target_properties(OpenGL::OpenGL PROPERTIES INTERFACE_INCLUDE_DIRECTORIES
                           "${OPENGL_INCLUDE_DIR}")
+    set(_OpenGL_EGL_IMPL OpenGL::OpenGL)
   endif()
 
   # ::GLX is a GLVND library, and thus Linux-only: we don't bother checking
@@ -481,6 +584,73 @@
                           "${OPENGL_GLX_INCLUDE_DIR}")
   endif()
 
+  # ::GLES2 is a GLVND library, and thus Linux-only: we don't bother checking
+  # for a framework version of this library.
+  if(OpenGL_GLES2_FOUND AND NOT TARGET OpenGL::GLES2)
+
+    # Initialize target
+    if(NOT OPENGL_gles2_LIBRARY)
+      add_library(OpenGL::GLES2 INTERFACE IMPORTED)
+    else()
+      if(IS_ABSOLUTE "${OPENGL_gles2_LIBRARY}")
+        add_library(OpenGL::GLES2 UNKNOWN IMPORTED)
+        set_target_properties(OpenGL::GLES2 PROPERTIES
+          IMPORTED_LOCATION "${OPENGL_gles2_LIBRARY}"
+        )
+      else()
+        add_library(OpenGL::GLES2 INTERFACE IMPORTED)
+        set_target_properties(OpenGL::GLES2 PROPERTIES
+          IMPORTED_LIBNAME "${OPENGL_gles2_LIBRARY}"
+        )
+      endif()
+    endif()
+
+    # Attach target properties
+    set_target_properties(OpenGL::GLES2
+      PROPERTIES
+        INTERFACE_INCLUDE_DIRECTORIES
+          "${OPENGL_GLES2_INCLUDE_DIR}"
+    )
+
+    if (OPENGL_USE_GLES2)
+      set(_OpenGL_EGL_IMPL OpenGL::GLES2)
+    endif ()
+
+  endif()
+
+  # ::GLES3 is a GLVND library, and thus Linux-only: we don't bother checking
+  # for a framework version of this library.
+  if(OpenGL_GLES3_FOUND AND NOT TARGET OpenGL::GLES3)
+
+    # Initialize target
+    if(NOT OPENGL_gles3_LIBRARY)
+      add_library(OpenGL::GLES3 INTERFACE IMPORTED)
+    else()
+      if(IS_ABSOLUTE "${OPENGL_gles3_LIBRARY}")
+        add_library(OpenGL::GLES3 UNKNOWN IMPORTED)
+        set_target_properties(OpenGL::GLES3 PROPERTIES
+          IMPORTED_LOCATION "${OPENGL_gles3_LIBRARY}"
+        )
+      else()
+        add_library(OpenGL::GLES3 INTERFACE IMPORTED)
+        set_target_properties(OpenGL::GLES3 PROPERTIES
+          IMPORTED_LIBNAME "${OPENGL_gles3_LIBRARY}"
+        )
+      endif()
+    endif()
+
+    # Attach target properties
+    set_target_properties(OpenGL::GLES3 PROPERTIES
+      INTERFACE_INCLUDE_DIRECTORIES
+        "${OPENGL_GLES3_INCLUDE_DIR}"
+    )
+
+    if (OPENGL_USE_GLES3)
+      set(_OpenGL_EGL_IMPL OpenGL::GLES3)
+    endif ()
+
+  endif()
+
   if(OPENGL_gl_LIBRARY AND NOT TARGET OpenGL::GL)
     # A legacy GL library is available, so use it for the legacy GL target.
     if(IS_ABSOLUTE "${OPENGL_gl_LIBRARY}")
@@ -517,10 +687,9 @@
 
   # ::EGL is a GLVND library, and thus Linux-only: we don't bother checking
   # for a framework version of this library.
-  # Note we test for OpenGL::OpenGL as a target.  When this module is updated to
-  # support GLES, we would additionally want to check for the hypothetical GLES
-  # target and enable EGL if either ::GLES or ::OpenGL is created.
-  if(TARGET OpenGL::OpenGL AND OpenGL_EGL_FOUND AND NOT TARGET OpenGL::EGL)
+  # Note we test whether _OpenGL_EGL_IMPL is set. Based on the OpenGL implementation,
+  # _OpenGL_EGL_IMPL will be one of OpenGL::OpenGL, OpenGL::GLES2, OpenGL::GLES3
+  if(_OpenGL_EGL_IMPL AND OpenGL_EGL_FOUND AND NOT TARGET OpenGL::EGL)
     if(IS_ABSOLUTE "${OPENGL_egl_LIBRARY}")
       add_library(OpenGL::EGL UNKNOWN IMPORTED)
       set_target_properties(OpenGL::EGL PROPERTIES IMPORTED_LOCATION
@@ -531,7 +700,7 @@
                             "${OPENGL_egl_LIBRARY}")
     endif()
     set_target_properties(OpenGL::EGL PROPERTIES INTERFACE_LINK_LIBRARIES
-                          OpenGL::OpenGL)
+                          "${_OpenGL_EGL_IMPL}")
     # Note that EGL's include directory is different from OpenGL/GLX's!
     set_target_properties(OpenGL::EGL PROPERTIES INTERFACE_INCLUDE_DIRECTORIES
                           "${OPENGL_EGL_INCLUDE_DIR}")
diff --git a/Modules/FindOpenSSL.cmake b/Modules/FindOpenSSL.cmake
index 4e8374c..426d00d 100644
--- a/Modules/FindOpenSSL.cmake
+++ b/Modules/FindOpenSSL.cmake
@@ -107,13 +107,13 @@
 
 ``ENV{PKG_CONFIG_PATH}``
   On UNIX-like systems, ``pkg-config`` is used to locate the system OpenSSL.
-  Set the ``PKG_CONFIG_PATH`` environment varialbe to look in alternate
+  Set the ``PKG_CONFIG_PATH`` environment variable to look in alternate
   locations.  Useful on multi-lib systems.
 #]=======================================================================]
 
 macro(_OpenSSL_test_and_find_dependencies ssl_library crypto_library)
   unset(_OpenSSL_extra_static_deps)
-  if((CMAKE_SYSTEM_NAME STREQUAL "Linux") AND
+  if(UNIX AND
      (("${ssl_library}" MATCHES "\\${CMAKE_STATIC_LIBRARY_SUFFIX}$") OR
       ("${crypto_library}" MATCHES "\\${CMAKE_STATIC_LIBRARY_SUFFIX}$")))
     set(_OpenSSL_has_dependencies TRUE)
@@ -140,7 +140,7 @@
         endif()
       endforeach()
       unset(_OPENSSL_DEP_LIB)
-    else()
+    elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
       set(_OpenSSL_has_dependency_dl TRUE)
     endif()
     if(_OpenSSL_ldflags_other)
@@ -152,7 +152,7 @@
         endif()
       endforeach()
       unset(_OPENSSL_DEP_LDFLAG)
-    else()
+    elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
       set(_OpenSSL_has_dependency_threads TRUE)
       find_package(Threads)
     endif()
@@ -210,7 +210,7 @@
 # Support preference of static libs by adjusting CMAKE_FIND_LIBRARY_SUFFIXES
 if(OPENSSL_USE_STATIC_LIBS)
   set(_openssl_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
-  if(WIN32)
+  if(MSVC)
     set(CMAKE_FIND_LIBRARY_SUFFIXES .lib .a ${CMAKE_FIND_LIBRARY_SUFFIXES})
   else()
     set(CMAKE_FIND_LIBRARY_SUFFIXES .a )
@@ -230,13 +230,15 @@
   set(_OPENSSL_FIND_PATH_SUFFIX "include")
 endif()
 
-if (WIN32)
+if ((DEFINED OPENSSL_ROOT_DIR) OR (DEFINED ENV{OPENSSL_ROOT_DIR}))
+  set(_OPENSSL_ROOT_HINTS HINTS ${OPENSSL_ROOT_DIR} ENV OPENSSL_ROOT_DIR)
+  set(_OPENSSL_ROOT_PATHS NO_DEFAULT_PATH)
+elseif (MSVC)
   # http://www.slproweb.com/products/Win32OpenSSL.html
   set(_OPENSSL_ROOT_HINTS
-    ${OPENSSL_ROOT_DIR}
+    HINTS
     "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (32-bit)_is1;Inno Setup: App Path]"
     "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (64-bit)_is1;Inno Setup: App Path]"
-    ENV OPENSSL_ROOT_DIR
     )
 
   if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")
@@ -255,6 +257,7 @@
   endif()
 
   set(_OPENSSL_ROOT_PATHS
+    PATHS
     "${_programfiles}/OpenSSL"
     "${_programfiles}/OpenSSL-${_arch}"
     "C:/OpenSSL/"
@@ -262,16 +265,11 @@
     )
   unset(_programfiles)
   unset(_arch)
-else ()
-  set(_OPENSSL_ROOT_HINTS
-    ${OPENSSL_ROOT_DIR}
-    ENV OPENSSL_ROOT_DIR
-    )
 endif ()
 
 set(_OPENSSL_ROOT_HINTS_AND_PATHS
-    HINTS ${_OPENSSL_ROOT_HINTS}
-    PATHS ${_OPENSSL_ROOT_PATHS}
+    ${_OPENSSL_ROOT_HINTS}
+    ${_OPENSSL_ROOT_PATHS}
     )
 
 find_path(OPENSSL_INCLUDE_DIR
diff --git a/Modules/FindPNG.cmake b/Modules/FindPNG.cmake
index 94d15db..043b69c 100644
--- a/Modules/FindPNG.cmake
+++ b/Modules/FindPNG.cmake
@@ -48,18 +48,35 @@
 will be defined unless ZLib can be found.
 #]=======================================================================]
 
+# Default install location on windows when installing from included cmake build
+# From FindZLIB.cmake
+set(_PNG_x86 "(x86)")
+set(_PNG_INCLUDE_SEARCH_NORMAL
+  "$ENV{ProgramFiles}/libpng"
+  "$ENV{ProgramFiles${_PNG_x86}}/libpng")
+set(_PNG_LIB_SEARCH_NORMAL
+  "$ENV{ProgramFiles}/libpng/lib"
+  "$ENV{ProgramFiles${_PNG_x86}}/libpng/lib")
+unset(_PNG_x86)
+
 if(PNG_FIND_QUIETLY)
   set(_FIND_ZLIB_ARG QUIET)
 endif()
 find_package(ZLIB ${_FIND_ZLIB_ARG})
 
 if(ZLIB_FOUND)
-  find_path(PNG_PNG_INCLUDE_DIR png.h PATH_SUFFIXES include/libpng)
+  set(_PNG_VERSION_SUFFIXES 17 16 15 14 12)
+
+  list(APPEND _PNG_INCLUDE_PATH_SUFFIXES include/libpng)
+  foreach(v IN LISTS _PNG_VERSION_SUFFIXES)
+    list(APPEND _PNG_INCLUDE_PATH_SUFFIXES include/libpng${v})
+  endforeach()
+
+  find_path(PNG_PNG_INCLUDE_DIR png.h PATH_SUFFIXES ${_PNG_INCLUDE_PATH_SUFFIXES} PATHS ${_PNG_INCLUDE_SEARCH_NORMAL} )
   mark_as_advanced(PNG_PNG_INCLUDE_DIR)
 
   list(APPEND PNG_NAMES png libpng)
   unset(PNG_NAMES_DEBUG)
-  set(_PNG_VERSION_SUFFIXES 17 16 15 14 12)
   if (PNG_FIND_VERSION MATCHES "^([0-9]+)\\.([0-9]+)(\\..*)?$")
     set(_PNG_VERSION_SUFFIX_MIN "${CMAKE_MATCH_1}${CMAKE_MATCH_2}")
     if (PNG_FIND_VERSION_EXACT)
@@ -79,14 +96,15 @@
   # For compatibility with versions prior to this multi-config search, honor
   # any PNG_LIBRARY that is already specified and skip the search.
   if(NOT PNG_LIBRARY)
-    find_library(PNG_LIBRARY_RELEASE NAMES ${PNG_NAMES} NAMES_PER_DIR)
-    find_library(PNG_LIBRARY_DEBUG NAMES ${PNG_NAMES_DEBUG} NAMES_PER_DIR)
+    find_library(PNG_LIBRARY_RELEASE NAMES ${PNG_NAMES} NAMES_PER_DIR PATHS ${_PNG_LIB_SEARCH_NORMAL})
+    find_library(PNG_LIBRARY_DEBUG NAMES ${PNG_NAMES_DEBUG} NAMES_PER_DIR PATHS ${_PNG_LIB_SEARCH_NORMAL})
     include(${CMAKE_CURRENT_LIST_DIR}/SelectLibraryConfigurations.cmake)
     select_library_configurations(PNG)
     mark_as_advanced(PNG_LIBRARY_RELEASE PNG_LIBRARY_DEBUG)
   endif()
   unset(PNG_NAMES)
   unset(PNG_NAMES_DEBUG)
+  unset(_PNG_INCLUDE_PATH_SUFFIXES)
 
   # Set by select_library_configurations(), but we want the one from
   # find_package_handle_standard_args() below.
diff --git a/Modules/FindPython.cmake b/Modules/FindPython.cmake
index 31ef1c7..e6f44e0 100644
--- a/Modules/FindPython.cmake
+++ b/Modules/FindPython.cmake
@@ -335,6 +335,8 @@
     constraints is founded.
     This is the default if policy :policy:`CMP0094` is set to ``NEW``.
 
+  See also ``Python_FIND_UNVERSIONED_NAMES``.
+
 ``Python_FIND_REGISTRY``
   .. versionadded:: 3.13
 
@@ -442,6 +444,8 @@
     This is the default.
   * ``NEVER``: The generic name are not searched at all.
 
+  See also ``Python_FIND_STRATEGY``.
+
 Artifacts Specification
 ^^^^^^^^^^^^^^^^^^^^^^^
 
diff --git a/Modules/FindPython/Support.cmake b/Modules/FindPython/Support.cmake
index f842068..7f35e07 100644
--- a/Modules/FindPython/Support.cmake
+++ b/Modules/FindPython/Support.cmake
@@ -192,28 +192,25 @@
     if (implementation STREQUAL "CPython")
       foreach (version IN LISTS _PGR_VERSION)
         string (REPLACE "." "" version_no_dots ${version})
-        list (APPEND registries
-                     [HKEY_CURRENT_USER/SOFTWARE/Python/PythonCore/${version}-${_${_PYTHON_PREFIX}_ARCH}/InstallPath]
-                     [HKEY_CURRENT_USER/SOFTWARE/Python/PythonCore/${version}-${_${_PYTHON_PREFIX}_ARCH2}/InstallPath])
+        list (TRANSFORM _${_PYTHON_PREFIX}_ARCH REPLACE "^(.+)$" "[HKEY_CURRENT_USER/SOFTWARE/Python/PythonCore/${version}-\\1/InstallPath]" OUTPUT_VARIABLE reg_paths)
+        list (APPEND registries ${reg_paths})
         if (version VERSION_GREATER_EQUAL "3.5")
           # cmake_host_system_information is not usable in bootstrap
           get_filename_component (arch "[HKEY_CURRENT_USER\\Software\\Python\\PythonCore\\${version};SysArchitecture]" NAME)
-          if (arch MATCHES "(${_${_PYTHON_PREFIX}_ARCH}|${_${_PYTHON_PREFIX}_ARCH2})bit")
-            list (APPEND registries
-                         [HKEY_CURRENT_USER/SOFTWARE/Python/PythonCore/${version}/InstallPath])
+          string (REPLACE "bit" "" arch "${arch}")
+          if (arch IN_LIST _${_PYTHON_PREFIX}_ARCH)
+            list (APPEND registries [HKEY_CURRENT_USER/SOFTWARE/Python/PythonCore/${version}/InstallPath])
           endif()
         else()
-          list (APPEND registries
-                       [HKEY_CURRENT_USER/SOFTWARE/Python/PythonCore/${version}/InstallPath])
+          list (APPEND registries [HKEY_CURRENT_USER/SOFTWARE/Python/PythonCore/${version}/InstallPath])
         endif()
-        list (APPEND registries
-                     [HKEY_CURRENT_USER/SOFTWARE/Python/ContinuumAnalytics/Anaconda${version_no_dots}-${_${_PYTHON_PREFIX}_ARCH}/InstallPath]
-                     [HKEY_CURRENT_USER/SOFTWARE/Python/ContinuumAnalytics/Anaconda${version_no_dots}-${_${_PYTHON_PREFIX}_ARCH2}/InstallPath]
-                     [HKEY_LOCAL_MACHINE/SOFTWARE/Python/PythonCore/${version}-${_${_PYTHON_PREFIX}_ARCH}/InstallPath]
-                     [HKEY_LOCAL_MACHINE/SOFTWARE/Python/PythonCore/${version}-${_${_PYTHON_PREFIX}_ARCH2}/InstallPath]
-                     [HKEY_LOCAL_MACHINE/SOFTWARE/Python/PythonCore/${version}/InstallPath]
-                     [HKEY_LOCAL_MACHINE/SOFTWARE/Python/ContinuumAnalytics/Anaconda${version_no_dots}-${_${_PYTHON_PREFIX}_ARCH}/InstallPath]
-                     [HKEY_LOCAL_MACHINE/SOFTWARE/Python/ContinuumAnalytics/Anaconda${version_no_dots}-${_${_PYTHON_PREFIX}_ARCH2}/InstallPath])
+        list (TRANSFORM _${_PYTHON_PREFIX}_ARCH REPLACE "^(.+)$" "[HKEY_CURRENT_USER/SOFTWARE/Python/ContinuumAnalytics/Anaconda${version_no_dots}-\\1/InstallPath]" OUTPUT_VARIABLE reg_paths)
+        list (APPEND registries ${reg_paths})
+        list (TRANSFORM _${_PYTHON_PREFIX}_ARCH REPLACE "^(.+)$" "[HKEY_CURRENT_USER/SOFTWARE/Python/PythonCore/${version}-\\1/InstallPath]" OUTPUT_VARIABLE reg_paths)
+        list (APPEND registries ${reg_paths})
+        list (APPEND registries [HKEY_LOCAL_MACHINE/SOFTWARE/Python/PythonCore/${version}/InstallPath])
+        list (TRANSFORM _${_PYTHON_PREFIX}_ARCH REPLACE "^(.+)$" "[HKEY_LOCAL_MACHINE/SOFTWARE/Python/ContinuumAnalytics/Anaconda${version_no_dots}-\\1/InstallPath]" OUTPUT_VARIABLE reg_paths)
+        list (APPEND registries ${reg_paths})
       endforeach()
     elseif (implementation STREQUAL "IronPython")
       foreach (version  IN LISTS _PGR_VERSION)
@@ -927,6 +924,33 @@
       set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND")
       return()
     endif()
+
+    if (WIN32)
+      # In this case, check if the interpreter is compatible with the target processor architecture
+      if (NOT CMAKE_GENERATOR_PLATFORM AND CMAKE_SYSTEM_PROCESSOR MATCHES "ARM" OR CMAKE_GENERATOR_PLATFORM MATCHES "ARM")
+        set(target_arm TRUE)
+      else()
+        set(target_arm FALSE)
+      endif()
+      execute_process (COMMAND ${launcher} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c
+        "import sys, sysconfig; sys.stdout.write(sysconfig.get_platform())"
+        RESULT_VARIABLE result
+        OUTPUT_VARIABLE platform
+        ERROR_QUIET
+        OUTPUT_STRIP_TRAILING_WHITESPACE)
+      string(TOUPPER "${platform}" platform)
+      if (result OR ((target_arm AND NOT platform MATCHES "ARM") OR
+                     (NOT target_arm AND platform MATCHES "ARM")))
+        # interpreter not usable or has wrong architecture
+        if (result)
+          set_property (CACHE _${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE PROPERTY VALUE "Cannot use the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"")
+        else()
+          set_property (CACHE _${_PYTHON_PREFIX}_Interpreter_REASON_FAILURE PROPERTY VALUE "Wrong architecture for the interpreter \"${_${_PYTHON_PREFIX}_EXECUTABLE}\"")
+        endif()
+        set_property (CACHE _${_PYTHON_PREFIX}_EXECUTABLE PROPERTY VALUE "${_PYTHON_PREFIX}_EXECUTABLE-NOTFOUND")
+        return()
+      endif()
+    endif()
   endif()
 endfunction()
 
@@ -1419,19 +1443,37 @@
       OR "Development.SABIModule" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS
       OR "Development.Embed" IN_LIST ${_PYTHON_PREFIX}_FIND_COMPONENTS)
     # In this case, search only for 64bit or 32bit
-    set (_${_PYTHON_PREFIX}_ARCH2 ${_${_PYTHON_PREFIX}_ARCH})
     set (_${_PYTHON_PREFIX}_REGISTRY_VIEW REGISTRY_VIEW ${_${_PYTHON_PREFIX}_ARCH})
+    if (WIN32 AND (NOT CMAKE_GENERATOR_PLATFORM AND CMAKE_SYSTEM_PROCESSOR MATCHES "ARM"
+                   OR CMAKE_GENERATOR_PLATFORM MATCHES "ARM"))
+      # search exclusively ARM architecture: 64bit or 32bit
+      if (_${_PYTHON_PREFIX}_ARCH EQUAL 64)
+        set (_${_PYTHON_PREFIX}_ARCH ARM64)
+      else()
+        set (_${_PYTHON_PREFIX}_ARCH ARM)
+      endif()
+    endif()
   else()
     if (_${_PYTHON_PREFIX}_ARCH EQUAL "32")
-      set (_${_PYTHON_PREFIX}_ARCH2 64)
+      if (CMAKE_SYSTEM_PROCESSOR MATCHES "ARM")
+        # search first ARM architectures: 32bit and then 64bit
+        list (PREPEND _${_PYTHON_PREFIX}_ARCH ARM ARM64)
+      endif()
+      list (APPEND _${_PYTHON_PREFIX}_ARCH 64)
     else()
-      set (_${_PYTHON_PREFIX}_ARCH2 32)
+      if (CMAKE_SYSTEM_PROCESSOR MATCHES "ARM")
+        # search first ARM architectures: 64bit and then 32bit
+        list (PREPEND _${_PYTHON_PREFIX}_ARCH ARM64 ARM)
+      endif()
+      list (APPEND _${_PYTHON_PREFIX}_ARCH 32)
     endif()
   endif()
 else()
   # architecture unknown, search for both 64bit and 32bit
-  set (_${_PYTHON_PREFIX}_ARCH 64)
-  set (_${_PYTHON_PREFIX}_ARCH2 32)
+  set (_${_PYTHON_PREFIX}_ARCH 64 32)
+  if (CMAKE_SYSTEM_PROCESSOR MATCHES "ARM")
+    list (PREPEND _${_PYTHON_PREFIX}_ARCH ARM64 ARM)
+  endif()
 endif()
 
 # IronPython support
@@ -1439,7 +1481,7 @@
 unset (_${_PYTHON_PREFIX}_IRON_PYTHON_COMPILER_NAMES)
 unset (_${_PYTHON_PREFIX}_IRON_PYTHON_COMPILER_ARCH_FLAGS)
 if (CMAKE_SIZEOF_VOID_P)
-  if (_${_PYTHON_PREFIX}_ARCH EQUAL "32")
+  if (CMAKE_SIZEOF_VOID_P EQUAL "4")
     set (_${_PYTHON_PREFIX}_IRON_PYTHON_COMPILER_ARCH_FLAGS "/platform:x86")
   else()
     set (_${_PYTHON_PREFIX}_IRON_PYTHON_COMPILER_ARCH_FLAGS "/platform:x64")
@@ -2048,7 +2090,6 @@
       list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 3 ${_PYTHON_PREFIX}_VERSION_PATCH)
 
       list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 4 _${_PYTHON_PREFIX}_ARCH)
-      set (_${_PYTHON_PREFIX}_ARCH2 ${_${_PYTHON_PREFIX}_ARCH})
 
       list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 5 _${_PYTHON_PREFIX}_ABIFLAGS)
       list (GET _${_PYTHON_PREFIX}_INTERPRETER_PROPERTIES 6 ${_PYTHON_PREFIX}_SOABI)
@@ -2098,10 +2139,27 @@
           if (NOT _${_PYTHON_PREFIX}_RESULT)
             if (${_PYTHON_PREFIX}_IS64BIT)
               set (_${_PYTHON_PREFIX}_ARCH 64)
-              set (_${_PYTHON_PREFIX}_ARCH2 64)
             else()
               set (_${_PYTHON_PREFIX}_ARCH 32)
-              set (_${_PYTHON_PREFIX}_ARCH2 32)
+            endif()
+          endif()
+
+          if (WIN32)
+            # check if architecture is Intel or ARM
+            execute_process (COMMAND ${_${_PYTHON_PREFIX}_INTERPRETER_LAUNCHER} "${_${_PYTHON_PREFIX}_EXECUTABLE}" -c
+                                     "import sys; import sysconfig; sys.stdout.write(sysconfig.get_platform())"
+                             RESULT_VARIABLE _${_PYTHON_PREFIX}_RESULT
+                             OUTPUT_VARIABLE _${_PYTHON_PREFIX}_PLATFORM
+                             ERROR_VARIABLE ${_PYTHON_PREFIX}_PLATFORM)
+            if (NOT _${_PYTHON_PREFIX}_RESULT)
+              string(TOUPPER "${_${_PYTHON_PREFIX}_PLATFORM}" _${_PYTHON_PREFIX}_PLATFORM)
+              if (_${_PYTHON_PREFIX}_PLATFORM MATCHES "ARM")
+                if (${_PYTHON_PREFIX}_IS64BIT)
+                  set (_${_PYTHON_PREFIX}_ARCH ARM64)
+                else()
+                  set (_${_PYTHON_PREFIX}_ARCH ARM)
+                endif()
+              endif()
             endif()
           endif()
         endif()
diff --git a/Modules/FindPython2.cmake b/Modules/FindPython2.cmake
index 41d9b68..0575ea5 100644
--- a/Modules/FindPython2.cmake
+++ b/Modules/FindPython2.cmake
@@ -234,6 +234,8 @@
     constraints is founded.
     This is the default if policy :policy:`CMP0094` is set to ``NEW``.
 
+  See also ``Python2_FIND_UNVERSIONED_NAMES``.
+
 ``Python2_FIND_REGISTRY``
   .. versionadded:: 3.13
 
@@ -341,6 +343,8 @@
     This is the default.
   * ``NEVER``: The generic name are not searched at all.
 
+  See also ``Python2_FIND_STRATEGY``.
+
 Artifacts Specification
 ^^^^^^^^^^^^^^^^^^^^^^^
 
diff --git a/Modules/FindPython3.cmake b/Modules/FindPython3.cmake
index ae086e8..18929b2 100644
--- a/Modules/FindPython3.cmake
+++ b/Modules/FindPython3.cmake
@@ -333,6 +333,8 @@
     constraints is founded.
     This is the default if policy :policy:`CMP0094` is set to ``NEW``.
 
+  See also ``Python3_FIND_UNVERSIONED_NAMES``.
+
 ``Python3_FIND_REGISTRY``
   .. versionadded:: 3.13
 
@@ -440,6 +442,8 @@
     This is the default.
   * ``NEVER``: The generic name are not searched at all.
 
+  See also ``Python3_FIND_STRATEGY``.
+
 Artifacts Specification
 ^^^^^^^^^^^^^^^^^^^^^^^
 
diff --git a/Modules/FindPythonInterp.cmake b/Modules/FindPythonInterp.cmake
index 7ad3587..b5e759d 100644
--- a/Modules/FindPythonInterp.cmake
+++ b/Modules/FindPythonInterp.cmake
@@ -5,6 +5,9 @@
 FindPythonInterp
 ----------------
 
+.. versionchanged:: 3.27
+  This module is available only if policy :policy:`CMP0148` is not set to ``NEW``.
+
 .. deprecated:: 3.12
 
   Use :module:`FindPython3`, :module:`FindPython2` or :module:`FindPython` instead.
@@ -50,6 +53,16 @@
 
 #]=======================================================================]
 
+cmake_policy(GET CMP0148 _FindPythonInterp_CMP0148)
+if(_FindPythonInterp_CMP0148 STREQUAL "NEW")
+  message(FATAL_ERROR "The FindPythonInterp module has been removed by policy CMP0148.")
+endif()
+
+if(_FindPythonInterp_testing)
+  set(_FindPythonInterp_included TRUE)
+  return()
+endif()
+
 unset(_Python_NAMES)
 
 set(_PYTHON1_VERSIONS 1.6 1.5)
diff --git a/Modules/FindPythonLibs.cmake b/Modules/FindPythonLibs.cmake
index 43a84dd..06004ee 100644
--- a/Modules/FindPythonLibs.cmake
+++ b/Modules/FindPythonLibs.cmake
@@ -5,6 +5,9 @@
 FindPythonLibs
 --------------
 
+.. versionchanged:: 3.27
+  This module is available only if policy :policy:`CMP0148` is not set to ``NEW``.
+
 .. deprecated:: 3.12
 
   Use :module:`FindPython3`, :module:`FindPython2` or :module:`FindPython` instead.
@@ -45,6 +48,16 @@
 of PYTHON_LIBRARIES.
 #]=======================================================================]
 
+cmake_policy(GET CMP0148 _FindPythonLibs_CMP0148)
+if(_FindPythonLibs_CMP0148 STREQUAL "NEW")
+  message(FATAL_ERROR "The FindPythonLibs module has been removed by policy CMP0148.")
+endif()
+
+if(_FindPythonLibs_testing)
+  set(_FindPythonLibs_included TRUE)
+  return()
+endif()
+
 # Use the executable's path as a hint
 set(_Python_LIBRARY_PATH_HINT)
 if(IS_ABSOLUTE "${PYTHON_EXECUTABLE}")
diff --git a/Modules/FindVulkan.cmake b/Modules/FindVulkan.cmake
index 3817987..581763d 100644
--- a/Modules/FindVulkan.cmake
+++ b/Modules/FindVulkan.cmake
@@ -244,23 +244,26 @@
 if(WIN32)
   set(_Vulkan_library_name vulkan-1)
   set(_Vulkan_hint_include_search_paths
-    "$ENV{VULKAN_SDK}/Include"
+    "$ENV{VULKAN_SDK}/include"
   )
   if(CMAKE_SIZEOF_VOID_P EQUAL 8)
     set(_Vulkan_hint_executable_search_paths
-      "$ENV{VULKAN_SDK}/Bin"
+      "$ENV{VULKAN_SDK}/bin"
     )
     set(_Vulkan_hint_library_search_paths
-      "$ENV{VULKAN_SDK}/Lib"
-      "$ENV{VULKAN_SDK}/Bin"
+      "$ENV{VULKAN_SDK}/lib"
+      "$ENV{VULKAN_SDK}/bin"
     )
   else()
     set(_Vulkan_hint_executable_search_paths
-      "$ENV{VULKAN_SDK}/Bin32"
+      "$ENV{VULKAN_SDK}/bin32"
+      "$ENV{VULKAN_SDK}/bin"
     )
     set(_Vulkan_hint_library_search_paths
-      "$ENV{VULKAN_SDK}/Lib32"
-      "$ENV{VULKAN_SDK}/Bin32"
+      "$ENV{VULKAN_SDK}/lib32"
+      "$ENV{VULKAN_SDK}/bin32"
+      "$ENV{VULKAN_SDK}/lib"
+      "$ENV{VULKAN_SDK}/bin"
     )
   endif()
 else()
diff --git a/Modules/FindX11.cmake b/Modules/FindX11.cmake
index 8e5a5f1..1047e4f 100644
--- a/Modules/FindX11.cmake
+++ b/Modules/FindX11.cmake
@@ -22,52 +22,77 @@
 
 ::
 
-  X11_ICE_INCLUDE_PATH,          X11_ICE_LIB,        X11_ICE_FOUND,        X11::ICE
-  X11_SM_INCLUDE_PATH,           X11_SM_LIB,         X11_SM_FOUND,         X11::SM
-  X11_X11_INCLUDE_PATH,          X11_X11_LIB,                              X11::X11
+  X11_ICE_INCLUDE_PATH,            X11_ICE_LIB,            X11_ICE_FOUND,            X11::ICE
+  X11_SM_INCLUDE_PATH,             X11_SM_LIB,             X11_SM_FOUND,             X11::SM
+  X11_X11_INCLUDE_PATH,            X11_X11_LIB,                                      X11::X11
   X11_Xaccessrules_INCLUDE_PATH,
-  X11_Xaccessstr_INCLUDE_PATH,                       X11_Xaccess_FOUND
-  X11_Xau_INCLUDE_PATH,          X11_Xau_LIB,        X11_Xau_FOUND,        X11::Xau
-  X11_xcb_INCLUDE_PATH,          X11_xcb_LIB,        X11_xcb_FOUND,        X11::xcb
-  X11_X11_xcb_INCLUDE_PATH,      X11_X11_xcb_LIB,    X11_X11_xcb_FOUND,    X11::X11_xcb
-  X11_xcb_icccm_INCLUDE_PATH,    X11_xcb_icccm_LIB,  X11_xcb_icccm_FOUND,  X11::xcb_icccm
-  X11_xcb_randr_INCLUDE_PATH,    X11_xcb_randr_LIB,  X11_xcb_randr_FOUND,  X11::xcb_randr
-  X11_xcb_util_INCLUDE_PATH,     X11_xcb_util_LIB,   X11_xcb_util_FOUND,   X11::xcb_util
-  X11_xcb_xfixes_INCLUDE_PATH,   X11_xcb_xfixes_LIB, X11_xcb_xfixes_FOUND, X11::xcb_xfixes
-  X11_xcb_xtest_INCLUDE_PATH,    X11_xcb_xtest_LIB,  X11_xcb_xtest_FOUND,  X11::xcb_xtest
-  X11_xcb_keysyms_INCLUDE_PATH,  X11_xcb_keysyms_LIB,X11_xcb_keysyms_FOUND,X11::xcb_keysyms
-  X11_xcb_xkb_INCLUDE_PATH,      X11_xcb_xkb_LIB,    X11_xcb_xkb_FOUND,    X11::xcb_xkb
-  X11_Xcomposite_INCLUDE_PATH,   X11_Xcomposite_LIB, X11_Xcomposite_FOUND, X11::Xcomposite
-  X11_Xcursor_INCLUDE_PATH,      X11_Xcursor_LIB,    X11_Xcursor_FOUND,    X11::Xcursor
-  X11_Xdamage_INCLUDE_PATH,      X11_Xdamage_LIB,    X11_Xdamage_FOUND,    X11::Xdamage
-  X11_Xdmcp_INCLUDE_PATH,        X11_Xdmcp_LIB,      X11_Xdmcp_FOUND,      X11::Xdmcp
-  X11_Xext_INCLUDE_PATH,         X11_Xext_LIB,       X11_Xext_FOUND,       X11::Xext
-  X11_Xxf86misc_INCLUDE_PATH,    X11_Xxf86misc_LIB,  X11_Xxf86misc_FOUND,  X11::Xxf86misc
-  X11_Xxf86vm_INCLUDE_PATH,      X11_Xxf86vm_LIB     X11_Xxf86vm_FOUND,    X11::Xxf86vm
-  X11_Xfixes_INCLUDE_PATH,       X11_Xfixes_LIB,     X11_Xfixes_FOUND,     X11::Xfixes
-  X11_Xft_INCLUDE_PATH,          X11_Xft_LIB,        X11_Xft_FOUND,        X11::Xft
-  X11_Xi_INCLUDE_PATH,           X11_Xi_LIB,         X11_Xi_FOUND,         X11::Xi
-  X11_Xinerama_INCLUDE_PATH,     X11_Xinerama_LIB,   X11_Xinerama_FOUND,   X11::Xinerama
+  X11_Xaccessstr_INCLUDE_PATH,                             X11_Xaccess_FOUND
+  X11_Xau_INCLUDE_PATH,            X11_Xau_LIB,            X11_Xau_FOUND,            X11::Xau
+  X11_xcb_INCLUDE_PATH,            X11_xcb_LIB,            X11_xcb_FOUND,            X11::xcb
+  X11_X11_xcb_INCLUDE_PATH,        X11_X11_xcb_LIB,        X11_X11_xcb_FOUND,        X11::X11_xcb
+  X11_xcb_composite_INCLUDE_PATH,  X11_xcb_composite_LIB,  X11_xcb_composite_FOUND,  X11::xcb_composite
+  X11_xcb_cursor_INCLUDE_PATH,     X11_xcb_cursor_LIB,     X11_xcb_cursor_FOUND,     X11::xcb_cursor
+  X11_xcb_damage_INCLUDE_PATH,     X11_xcb_damage_LIB,     X11_xcb_damage_FOUND,     X11::xcb_damage
+  X11_xcb_dpms_INCLUDE_PATH,       X11_xcb_dpms_LIB,       X11_xcb_dpms_FOUND,       X11::xcb_dpms
+  X11_xcb_dri2_INCLUDE_PATH,       X11_xcb_dri2_LIB,       X11_xcb_dri2_FOUND,       X11::xcb_dri2
+  X11_xcb_dri3_INCLUDE_PATH,       X11_xcb_dri3_LIB,       X11_xcb_dri3_FOUND,       X11::xcb_dri3
+  X11_xcb_errors_INCLUDE_PATH,     X11_xcb_errors_LIB,     X11_xcb_errors_FOUND,     X11::xcb_errors
+  X11_xcb_ewmh_INCLUDE_PATH,       X11_xcb_ewmh_LIB,       X11_xcb_ewmh_FOUND,       X11::xcb_ewmh
+  X11_xcb_glx_INCLUDE_PATH,        X11_xcb_glx_LIB,        X11_xcb_glx_FOUND,        X11::xcb_glx
+  X11_xcb_icccm_INCLUDE_PATH,      X11_xcb_icccm_LIB,      X11_xcb_icccm_FOUND,      X11::xcb_icccm
+  X11_xcb_image_INCLUDE_PATH,      X11_xcb_image_LIB,      X11_xcb_image_FOUND,      X11::xcb_image
+  X11_xcb_keysyms_INCLUDE_PATH,    X11_xcb_keysyms_LIB,    X11_xcb_keysyms_FOUND,    X11::xcb_keysyms
+  X11_xcb_present_INCLUDE_PATH,    X11_xcb_present_LIB,    X11_xcb_present_FOUND,    X11::xcb_present
+  X11_xcb_randr_INCLUDE_PATH,      X11_xcb_randr_LIB,      X11_xcb_randr_FOUND,      X11::xcb_randr
+  X11_xcb_record_INCLUDE_PATH,     X11_xcb_record_LIB,     X11_xcb_record_FOUND,     X11::xcb_record
+  X11_xcb_render_INCLUDE_PATH,     X11_xcb_render_LIB,     X11_xcb_render_FOUND,     X11::xcb_render
+  X11_xcb_render_util_INCLUDE_PATH,X11_xcb_render_util_LIB,X11_xcb_render_util_FOUND,X11::xcb_render_util
+  X11_xcb_res_INCLUDE_PATH,        X11_xcb_res_LIB,        X11_xcb_res_FOUND,        X11::xcb_res
+  X11_xcb_screensaver_INCLUDE_PATH,X11_xcb_screensaver_LIB,X11_xcb_screensaver_FOUND,X11::xcb_screensaver
+  X11_xcb_shape_INCLUDE_PATH,      X11_xcb_shape_LIB,      X11_xcb_shape_FOUND,      X11::xcb_shape
+  X11_xcb_shm_INCLUDE_PATH,        X11_xcb_shm_LIB,        X11_xcb_shm_FOUND,        X11::xcb_shm
+  X11_xcb_sync_INCLUDE_PATH,       X11_xcb_sync_LIB,       X11_xcb_sync_FOUND,       X11::xcb_sync
+  X11_xcb_util_INCLUDE_PATH,       X11_xcb_util_LIB,       X11_xcb_util_FOUND,       X11::xcb_util
+  X11_xcb_xf86dri_INCLUDE_PATH,    X11_xcb_xf86dri_LIB,    X11_xcb_xf86dri_FOUND,    X11::xcb_xf86dri
+  X11_xcb_xfixes_INCLUDE_PATH,     X11_xcb_xfixes_LIB,     X11_xcb_xfixes_FOUND,     X11::xcb_xfixes
+  X11_xcb_xinerama_INCLUDE_PATH,   X11_xcb_xinerama_LIB,   X11_xcb_xinerama_FOUND,   X11::xcb_xinerama
+  X11_xcb_xinput_INCLUDE_PATH,     X11_xcb_xinput_LIB,     X11_xcb_xinput_FOUND,     X11::xcb_xinput
+  X11_xcb_xkb_INCLUDE_PATH,        X11_xcb_xkb_LIB,        X11_xcb_xkb_FOUND,        X11::xcb_xkb
+  X11_xcb_xrm_INCLUDE_PATH,        X11_xcb_xrm_LIB,        X11_xcb_xrm_FOUND,        X11::xcb_xrm
+  X11_xcb_xtest_INCLUDE_PATH,      X11_xcb_xtest_LIB,      X11_xcb_xtest_FOUND,      X11::xcb_xtest
+  X11_xcb_xvmc_INCLUDE_PATH,       X11_xcb_xvmc_LIB,       X11_xcb_xvmc_FOUND,       X11::xcb_xvmc
+  X11_xcb_xv_INCLUDE_PATH,         X11_xcb_xv_LIB,         X11_xcb_xv_FOUND          X11::xcb_xv
+  X11_Xcomposite_INCLUDE_PATH,     X11_Xcomposite_LIB,     X11_Xcomposite_FOUND,     X11::Xcomposite
+  X11_Xcursor_INCLUDE_PATH,        X11_Xcursor_LIB,        X11_Xcursor_FOUND,        X11::Xcursor
+  X11_Xdamage_INCLUDE_PATH,        X11_Xdamage_LIB,        X11_Xdamage_FOUND,        X11::Xdamage
+  X11_Xdmcp_INCLUDE_PATH,          X11_Xdmcp_LIB,          X11_Xdmcp_FOUND,          X11::Xdmcp
+  X11_Xext_INCLUDE_PATH,           X11_Xext_LIB,           X11_Xext_FOUND,           X11::Xext
+  X11_Xxf86misc_INCLUDE_PATH,      X11_Xxf86misc_LIB,      X11_Xxf86misc_FOUND,      X11::Xxf86misc
+  X11_Xxf86vm_INCLUDE_PATH,        X11_Xxf86vm_LIB         X11_Xxf86vm_FOUND,        X11::Xxf86vm
+  X11_Xfixes_INCLUDE_PATH,         X11_Xfixes_LIB,         X11_Xfixes_FOUND,         X11::Xfixes
+  X11_Xft_INCLUDE_PATH,            X11_Xft_LIB,            X11_Xft_FOUND,            X11::Xft
+  X11_Xi_INCLUDE_PATH,             X11_Xi_LIB,             X11_Xi_FOUND,             X11::Xi
+  X11_Xinerama_INCLUDE_PATH,       X11_Xinerama_LIB,       X11_Xinerama_FOUND,       X11::Xinerama
   X11_Xkb_INCLUDE_PATH,
-  X11_Xkblib_INCLUDE_PATH,                           X11_Xkb_FOUND,        X11::Xkb
-  X11_xkbcommon_INCLUDE_PATH,    X11_xkbcommon_LIB,  X11_xkbcommon_FOUND,  X11::xkbcommon
-  X11_xkbcommon_X11_INCLUDE_PATH,X11_xkbcommon_X11_LIB,X11_xkbcommon_X11_FOUND,X11::xkbcommon_X11
-  X11_xkbfile_INCLUDE_PATH,      X11_xkbfile_LIB,    X11_xkbfile_FOUND,    X11::xkbfile
-  X11_Xmu_INCLUDE_PATH,          X11_Xmu_LIB,        X11_Xmu_FOUND,        X11::Xmu
-  X11_Xpm_INCLUDE_PATH,          X11_Xpm_LIB,        X11_Xpm_FOUND,        X11::Xpm
-  X11_Xtst_INCLUDE_PATH,         X11_Xtst_LIB,       X11_Xtst_FOUND,       X11::Xtst
-  X11_Xrandr_INCLUDE_PATH,       X11_Xrandr_LIB,     X11_Xrandr_FOUND,     X11::Xrandr
-  X11_Xrender_INCLUDE_PATH,      X11_Xrender_LIB,    X11_Xrender_FOUND,    X11::Xrender
-  X11_XRes_INCLUDE_PATH,         X11_XRes_LIB,       X11_XRes_FOUND,       X11::XRes
-  X11_Xss_INCLUDE_PATH,          X11_Xss_LIB,        X11_Xss_FOUND,        X11::Xss
-  X11_Xt_INCLUDE_PATH,           X11_Xt_LIB,         X11_Xt_FOUND,         X11::Xt
-  X11_Xutil_INCLUDE_PATH,                            X11_Xutil_FOUND,      X11::Xutil
-  X11_Xv_INCLUDE_PATH,           X11_Xv_LIB,         X11_Xv_FOUND,         X11::Xv
-  X11_dpms_INCLUDE_PATH,         (in X11_Xext_LIB),  X11_dpms_FOUND
-  X11_XShm_INCLUDE_PATH,         (in X11_Xext_LIB),  X11_XShm_FOUND
-  X11_Xshape_INCLUDE_PATH,       (in X11_Xext_LIB),  X11_Xshape_FOUND
-  X11_XSync_INCLUDE_PATH,        (in X11_Xext_LIB),  X11_XSync_FOUND
-  X11_Xaw_INCLUDE_PATH,          X11_Xaw_LIB         X11_Xaw_FOUND         X11::Xaw
+  X11_Xkblib_INCLUDE_PATH,                                 X11_Xkb_FOUND,            X11::Xkb
+  X11_xkbcommon_INCLUDE_PATH,      X11_xkbcommon_LIB,      X11_xkbcommon_FOUND,      X11::xkbcommon
+  X11_xkbcommon_X11_INCLUDE_PATH,  X11_xkbcommon_X11_LIB,  X11_xkbcommon_X11_FOUND,  X11::xkbcommon_X11
+  X11_xkbfile_INCLUDE_PATH,        X11_xkbfile_LIB,        X11_xkbfile_FOUND,        X11::xkbfile
+  X11_Xmu_INCLUDE_PATH,            X11_Xmu_LIB,            X11_Xmu_FOUND,            X11::Xmu
+  X11_Xpm_INCLUDE_PATH,            X11_Xpm_LIB,            X11_Xpm_FOUND,            X11::Xpm
+  X11_Xtst_INCLUDE_PATH,           X11_Xtst_LIB,           X11_Xtst_FOUND,           X11::Xtst
+  X11_Xrandr_INCLUDE_PATH,         X11_Xrandr_LIB,         X11_Xrandr_FOUND,         X11::Xrandr
+  X11_Xrender_INCLUDE_PATH,        X11_Xrender_LIB,        X11_Xrender_FOUND,        X11::Xrender
+  X11_XRes_INCLUDE_PATH,           X11_XRes_LIB,           X11_XRes_FOUND,           X11::XRes
+  X11_Xss_INCLUDE_PATH,            X11_Xss_LIB,            X11_Xss_FOUND,            X11::Xss
+  X11_Xt_INCLUDE_PATH,             X11_Xt_LIB,             X11_Xt_FOUND,             X11::Xt
+  X11_Xutil_INCLUDE_PATH,                                  X11_Xutil_FOUND,          X11::Xutil
+  X11_Xv_INCLUDE_PATH,             X11_Xv_LIB,             X11_Xv_FOUND,             X11::Xv
+  X11_dpms_INCLUDE_PATH,           (in X11_Xext_LIB),      X11_dpms_FOUND
+  X11_XShm_INCLUDE_PATH,           (in X11_Xext_LIB),      X11_XShm_FOUND
+  X11_Xshape_INCLUDE_PATH,         (in X11_Xext_LIB),      X11_Xshape_FOUND
+  X11_XSync_INCLUDE_PATH,          (in X11_Xext_LIB),      X11_XSync_FOUND
+  X11_Xaw_INCLUDE_PATH,            X11_Xaw_LIB             X11_Xaw_FOUND             X11::Xaw
 
 .. versionadded:: 3.14
   Renamed ``Xxf86misc``, ``X11_Xxf86misc``, ``X11_Xxf86vm``, ``X11_xkbfile``,
@@ -88,6 +113,14 @@
 .. versionadded:: 3.24
   Added the ``xcb_randr``, ``xcb_xtext``, and ``xcb_keysyms`` libraries.
 
+.. versionadded:: 3.27
+  Added the ``xcb_composite``, ``xcb_cursor``, ``xcb_damage``, ``xcb_dpms``,
+  ``xcb_dri2``, ``xcb_dri3``, ``xcb_errors``, ``xcb_ewmh``, ``xcb_glx``,
+  ``xcb_image``, ``xcb_present``, ``xcb_record``, ``xcb_render``,
+  ``xcb_render_util``, ``xcb_res``, ``xcb_screensaver``, ``xcb_shape``,
+  ``xcb_shm``, ``xcb_sync``, ``xcb_xf86dri``, ``xcb_xinerama``, ``xcb_xinput``,
+  ``xcb_xrm``, ``xcb_xvmc``, and ``xcb_xv`` libraries.
+
 #]=======================================================================]
 
 if (UNIX)
@@ -129,15 +162,41 @@
   find_path(X11_Xaccessrules_INCLUDE_PATH X11/extensions/XKBrules.h  ${X11_INC_SEARCH_PATH})
   find_path(X11_Xaccessstr_INCLUDE_PATH X11/extensions/XKBstr.h      ${X11_INC_SEARCH_PATH})
   find_path(X11_Xau_INCLUDE_PATH X11/Xauth.h                         ${X11_INC_SEARCH_PATH})
-  find_path(X11_Xaw_INCLUDE_PATH X11/Xaw/Intrinsic.h                 ${X11_INC_SEARCH_PATH})
+  find_path(X11_Xaw_INCLUDE_PATH X11/Xaw/Box.h                       ${X11_INC_SEARCH_PATH})
   find_path(X11_xcb_INCLUDE_PATH xcb/xcb.h                           ${X11_INC_SEARCH_PATH})
   find_path(X11_X11_xcb_INCLUDE_PATH X11/Xlib-xcb.h                  ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_composite_INCLUDE_PATH xcb/composite.h           ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_cursor_INCLUDE_PATH xcb/xcb_cursor.h             ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_damage_INCLUDE_PATH xcb/damage.h                 ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_dpms_INCLUDE_PATH xcb/dpms.h                     ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_dri2_INCLUDE_PATH xcb/dri2.h                     ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_dri3_INCLUDE_PATH xcb/dri3.h                     ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_errors_INCLUDE_PATH xcb/xcb_errors.h             ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_ewmh_INCLUDE_PATH xcb/xcb_ewmh.h                 ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_glx_INCLUDE_PATH xcb/glx.h                       ${X11_INC_SEARCH_PATH})
   find_path(X11_xcb_icccm_INCLUDE_PATH xcb/xcb_icccm.h               ${X11_INC_SEARCH_PATH})
-  find_path(X11_xcb_randr_INCLUDE_PATH xcb/randr.h                   ${X11_INC_SEARCH_PATH})
-  find_path(X11_xcb_util_INCLUDE_PATH xcb/xcb_aux.h                  ${X11_INC_SEARCH_PATH})
-  find_path(X11_xcb_xfixes_INCLUDE_PATH xcb/xfixes.h                 ${X11_INC_SEARCH_PATH})
-  find_path(X11_xcb_xtest_INCLUDE_PATH xcb/xtest.h                   ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_image_INCLUDE_PATH xcb/xcb_image.h               ${X11_INC_SEARCH_PATH})
   find_path(X11_xcb_keysyms_INCLUDE_PATH xcb/xcb_keysyms.h           ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_present_INCLUDE_PATH xcb/present.h               ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_randr_INCLUDE_PATH xcb/randr.h                   ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_record_INCLUDE_PATH xcb/record.h                 ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_render_INCLUDE_PATH xcb/render.h                 ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_render_util_INCLUDE_PATH xcb/xcb_renderutil.h    ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_res_INCLUDE_PATH xcb/res.h                       ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_screensaver_INCLUDE_PATH xcb/screensaver.h       ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_shape_INCLUDE_PATH xcb/shape.h                   ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_shm_INCLUDE_PATH xcb/shm.h                       ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_sync_INCLUDE_PATH xcb/sync.h                     ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_util_INCLUDE_PATH xcb/xcb_aux.h                  ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_xf86dri_INCLUDE_PATH xcb/xf86dri.h               ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_xfixes_INCLUDE_PATH xcb/xfixes.h                 ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_xinerama_INCLUDE_PATH xcb/xinerama.h             ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_xinput_INCLUDE_PATH xcb/xinput.h                 ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_xkb_INCLUDE_PATH xcb/xkb.h                       ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_xrm_INCLUDE_PATH xcb/xcb_xrm.h                   ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_xtest_INCLUDE_PATH xcb/xtest.h                   ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_xvmc_INCLUDE_PATH xcb/xvmc.h                     ${X11_INC_SEARCH_PATH})
+  find_path(X11_xcb_xv_INCLUDE_PATH xcb/xv.h                         ${X11_INC_SEARCH_PATH})
   find_path(X11_Xcomposite_INCLUDE_PATH X11/extensions/Xcomposite.h  ${X11_INC_SEARCH_PATH})
   find_path(X11_Xcursor_INCLUDE_PATH X11/Xcursor/Xcursor.h           ${X11_INC_SEARCH_PATH})
   find_path(X11_Xdamage_INCLUDE_PATH X11/extensions/Xdamage.h        ${X11_INC_SEARCH_PATH})
@@ -182,42 +241,67 @@
   find_library(X11_X11_LIB X11               ${X11_LIB_SEARCH_PATH})
 
   # Find additional X libraries. Keep list sorted by library name.
-  find_library(X11_ICE_LIB ICE               ${X11_LIB_SEARCH_PATH})
-  find_library(X11_SM_LIB SM                 ${X11_LIB_SEARCH_PATH})
-  find_library(X11_Xau_LIB Xau               ${X11_LIB_SEARCH_PATH})
-  find_library(X11_Xaw_LIB Xaw               ${X11_LIB_SEARCH_PATH})
-  find_library(X11_xcb_LIB xcb               ${X11_LIB_SEARCH_PATH})
-  find_library(X11_X11_xcb_LIB X11-xcb       ${X11_LIB_SEARCH_PATH})
-  find_library(X11_xcb_icccm_LIB xcb-icccm   ${X11_LIB_SEARCH_PATH})
-  find_library(X11_xcb_randr_LIB xcb-randr   ${X11_LIB_SEARCH_PATH})
-  find_library(X11_xcb_util_LIB xcb-util     ${X11_LIB_SEARCH_PATH})
-  find_library(X11_xcb_xfixes_LIB xcb-xfixes ${X11_LIB_SEARCH_PATH})
-  find_library(X11_xcb_xtest_LIB xcb-xtest   ${X11_LIB_SEARCH_PATH})
-  find_library(X11_xcb_keysyms_LIB xcb-keysyms ${X11_LIB_SEARCH_PATH})
-  find_library(X11_xcb_xkb_LIB xcb-xkb       ${X11_LIB_SEARCH_PATH})
-  find_library(X11_Xcomposite_LIB Xcomposite ${X11_LIB_SEARCH_PATH})
-  find_library(X11_Xcursor_LIB Xcursor       ${X11_LIB_SEARCH_PATH})
-  find_library(X11_Xdamage_LIB Xdamage       ${X11_LIB_SEARCH_PATH})
-  find_library(X11_Xdmcp_LIB Xdmcp           ${X11_LIB_SEARCH_PATH})
-  find_library(X11_Xext_LIB Xext             ${X11_LIB_SEARCH_PATH})
-  find_library(X11_Xfixes_LIB Xfixes         ${X11_LIB_SEARCH_PATH})
-  find_library(X11_Xft_LIB Xft               ${X11_LIB_SEARCH_PATH})
-  find_library(X11_Xi_LIB Xi                 ${X11_LIB_SEARCH_PATH})
-  find_library(X11_Xinerama_LIB Xinerama     ${X11_LIB_SEARCH_PATH})
-  find_library(X11_xkbcommon_LIB xkbcommon   ${X11_LIB_SEARCH_PATH})
-  find_library(X11_xkbcommon_X11_LIB xkbcommon-x11   ${X11_LIB_SEARCH_PATH})
-  find_library(X11_xkbfile_LIB xkbfile       ${X11_LIB_SEARCH_PATH})
-  find_library(X11_Xmu_LIB Xmu               ${X11_LIB_SEARCH_PATH})
-  find_library(X11_Xpm_LIB Xpm               ${X11_LIB_SEARCH_PATH})
-  find_library(X11_Xrandr_LIB Xrandr         ${X11_LIB_SEARCH_PATH})
-  find_library(X11_Xrender_LIB Xrender       ${X11_LIB_SEARCH_PATH})
-  find_library(X11_XRes_LIB XRes             ${X11_LIB_SEARCH_PATH})
-  find_library(X11_Xss_LIB Xss               ${X11_LIB_SEARCH_PATH})
-  find_library(X11_Xt_LIB Xt                 ${X11_LIB_SEARCH_PATH})
-  find_library(X11_Xtst_LIB Xtst             ${X11_LIB_SEARCH_PATH})
-  find_library(X11_Xv_LIB Xv                 ${X11_LIB_SEARCH_PATH})
-  find_library(X11_Xxf86misc_LIB Xxf86misc   ${X11_LIB_SEARCH_PATH})
-  find_library(X11_Xxf86vm_LIB Xxf86vm       ${X11_LIB_SEARCH_PATH})
+  find_library(X11_ICE_LIB ICE                         ${X11_LIB_SEARCH_PATH})
+  find_library(X11_SM_LIB SM                           ${X11_LIB_SEARCH_PATH})
+  find_library(X11_Xau_LIB Xau                         ${X11_LIB_SEARCH_PATH})
+  find_library(X11_Xaw_LIB Xaw                         ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_LIB xcb                         ${X11_LIB_SEARCH_PATH})
+  find_library(X11_X11_xcb_LIB X11-xcb                 ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_composite_LIB xcb-composite     ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_cursor_LIB xcb-cursor           ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_damage_LIB xcb-damage           ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_dpms_LIB xcb-dpms               ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_dri2_LIB xcb-dri2               ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_dri3_LIB xcb-dri3               ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_errors_LIB xcb-errors           ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_ewmh_LIB xcb-ewmh               ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_glx_LIB xcb-glx                 ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_icccm_LIB xcb-icccm             ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_image_LIB xcb-image             ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_keysyms_LIB xcb-keysyms         ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_present_LIB xcb-present         ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_randr_LIB xcb-randr             ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_record_LIB xcb-record           ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_render_LIB xcb-render           ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_render_util_LIB xcb-render-util ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_res_LIB xcb-res                 ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_screensaver_LIB xcb-screensaver ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_shape_LIB xcb-shape             ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_shm_LIB xcb-shm                 ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_sync_LIB xcb-sync               ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_util_LIB xcb-util               ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_xf86dri_LIB xcb-xf86dri         ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_xfixes_LIB xcb-xfixes           ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_xinerama_LIB xcb-xinerama       ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_xinput_LIB xcb-xinput           ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_xkb_LIB xcb-xkb                 ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_xrm_LIB xcb-xrm                 ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_xtest_LIB xcb-xtest             ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_xvmc_LIB xcb-xvmc               ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xcb_xv_LIB xcb-xv                   ${X11_LIB_SEARCH_PATH})
+  find_library(X11_Xcomposite_LIB Xcomposite           ${X11_LIB_SEARCH_PATH})
+  find_library(X11_Xcursor_LIB Xcursor                 ${X11_LIB_SEARCH_PATH})
+  find_library(X11_Xdamage_LIB Xdamage                 ${X11_LIB_SEARCH_PATH})
+  find_library(X11_Xdmcp_LIB Xdmcp                     ${X11_LIB_SEARCH_PATH})
+  find_library(X11_Xext_LIB Xext                       ${X11_LIB_SEARCH_PATH})
+  find_library(X11_Xfixes_LIB Xfixes                   ${X11_LIB_SEARCH_PATH})
+  find_library(X11_Xft_LIB Xft                         ${X11_LIB_SEARCH_PATH})
+  find_library(X11_Xi_LIB Xi                           ${X11_LIB_SEARCH_PATH})
+  find_library(X11_Xinerama_LIB Xinerama               ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xkbcommon_LIB xkbcommon             ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xkbcommon_X11_LIB xkbcommon-x11     ${X11_LIB_SEARCH_PATH})
+  find_library(X11_xkbfile_LIB xkbfile                 ${X11_LIB_SEARCH_PATH})
+  find_library(X11_Xmu_LIB Xmu                         ${X11_LIB_SEARCH_PATH})
+  find_library(X11_Xpm_LIB Xpm                         ${X11_LIB_SEARCH_PATH})
+  find_library(X11_Xrandr_LIB Xrandr                   ${X11_LIB_SEARCH_PATH})
+  find_library(X11_Xrender_LIB Xrender                 ${X11_LIB_SEARCH_PATH})
+  find_library(X11_XRes_LIB XRes                       ${X11_LIB_SEARCH_PATH})
+  find_library(X11_Xss_LIB Xss                         ${X11_LIB_SEARCH_PATH})
+  find_library(X11_Xt_LIB Xt                           ${X11_LIB_SEARCH_PATH})
+  find_library(X11_Xtst_LIB Xtst                       ${X11_LIB_SEARCH_PATH})
+  find_library(X11_Xv_LIB Xv                           ${X11_LIB_SEARCH_PATH})
+  find_library(X11_Xxf86misc_LIB Xxf86misc             ${X11_LIB_SEARCH_PATH})
+  find_library(X11_Xxf86vm_LIB Xxf86vm                 ${X11_LIB_SEARCH_PATH})
 
   # Backwards compatibility.
   set(X11_Xinput_LIB "${X11_Xi_LIB}")
@@ -289,32 +373,132 @@
     set(X11_X11_xcb_FOUND TRUE)
   endif ()
 
+  if (X11_xcb_composite_LIB AND X11_xcb_composite_INCLUDE_PATH)
+    set(X11_xcb_composite_FOUND TRUE)
+  endif ()
+
+  if (X11_xcb_cursor_LIB AND X11_xcb_cursor_INCLUDE_PATH)
+    set(X11_xcb_cursor_FOUND TRUE)
+  endif ()
+
+  if (X11_xcb_damage_LIB AND X11_xcb_damage_INCLUDE_PATH)
+    set(X11_xcb_damage_FOUND TRUE)
+  endif ()
+
+  if (X11_xcb_dpms_LIB AND X11_xcb_dpms_INCLUDE_PATH)
+    set(X11_xcb_dpms_FOUND TRUE)
+  endif ()
+
+  if (X11_xcb_dri2_LIB AND X11_xcb_dri2_INCLUDE_PATH)
+    set(X11_xcb_dri2_FOUND TRUE)
+  endif ()
+
+  if (X11_xcb_dri3_LIB AND X11_xcb_dri3_INCLUDE_PATH)
+    set(X11_xcb_dri3_FOUND TRUE)
+  endif ()
+
+  if (X11_xcb_errors_LIB AND X11_xcb_errors_INCLUDE_PATH)
+    set(X11_xcb_errors_FOUND TRUE)
+  endif ()
+
+  if (X11_xcb_ewmh_LIB AND X11_xcb_ewmh_INCLUDE_PATH)
+    set(X11_xcb_ewmh_FOUND TRUE)
+  endif ()
+
+  if (X11_xcb_glx_LIB AND X11_xcb_glx_INCLUDE_PATH)
+    set(X11_xcb_glx_FOUND TRUE)
+  endif ()
+
   if (X11_xcb_icccm_LIB AND X11_xcb_icccm_INCLUDE_PATH)
     set(X11_xcb_icccm_FOUND TRUE)
   endif ()
 
+  if (X11_xcb_image_LIB AND X11_xcb_image_INCLUDE_PATH)
+    set(X11_xcb_image_FOUND TRUE)
+  endif ()
+
+  if (X11_xcb_keysyms_LIB AND X11_xcb_keysyms_INCLUDE_PATH)
+    set(X11_xcb_keysyms_FOUND TRUE)
+  endif ()
+
+  if (X11_xcb_present_LIB AND X11_xcb_present_INCLUDE_PATH)
+    set(X11_xcb_present_FOUND TRUE)
+  endif ()
+
   if (X11_xcb_randr_LIB AND X11_xcb_randr_INCLUDE_PATH)
     set(X11_xcb_randr_FOUND TRUE)
   endif ()
 
+  if (X11_xcb_record_LIB AND X11_xcb_record_INCLUDE_PATH)
+    set(X11_xcb_record_FOUND TRUE)
+  endif ()
+
+  if (X11_xcb_render_LIB AND X11_xcb_render_INCLUDE_PATH)
+    set(X11_xcb_render_FOUND TRUE)
+  endif ()
+
+  if (X11_xcb_render_util_LIB AND X11_xcb_render_util_INCLUDE_PATH)
+    set(X11_xcb_render_util_FOUND TRUE)
+  endif ()
+
+  if (X11_xcb_res_LIB AND X11_xcb_res_INCLUDE_PATH)
+    set(X11_xcb_res_FOUND TRUE)
+  endif ()
+
+  if (X11_xcb_screensaver_LIB AND X11_xcb_screensaver_INCLUDE_PATH)
+    set(X11_xcb_screensaver_FOUND TRUE)
+  endif ()
+
+  if (X11_xcb_shape_LIB AND X11_xcb_shape_INCLUDE_PATH)
+    set(X11_xcb_shape_FOUND TRUE)
+  endif ()
+
+  if (X11_xcb_shm_LIB AND X11_xcb_shm_INCLUDE_PATH)
+    set(X11_xcb_shm_FOUND TRUE)
+  endif ()
+
+  if (X11_xcb_sync_LIB AND X11_xcb_sync_INCLUDE_PATH)
+    set(X11_xcb_sync_FOUND TRUE)
+  endif ()
+
   if (X11_xcb_util_LIB AND X11_xcb_util_INCLUDE_PATH)
     set(X11_xcb_util_FOUND TRUE)
   endif ()
 
-  if (X11_xcb_xfixes_LIB)
+  if (X11_xcb_xf86dri_LIB AND X11_xcb_xf86dri_INCLUDE_PATH)
+    set(X11_xcb_xf86dri_FOUND TRUE)
+  endif ()
+
+  if (X11_xcb_xfixes_LIB AND X11_xcb_xfixes_INCLUDE_PATH)
     set(X11_xcb_xfixes_FOUND TRUE)
   endif ()
 
-  if (X11_xcb_xtest_LIB)
+  if (X11_xcb_xinerama_LIB AND X11_xcb_xinerama_INCLUDE_PATH)
+    set(X11_xcb_xinerama_FOUND TRUE)
+  endif ()
+
+  if (X11_xcb_xinput_LIB AND X11_xcb_xinput_INCLUDE_PATH)
+    set(X11_xcb_xinput_FOUND TRUE)
+  endif ()
+
+  if (X11_xcb_xkb_LIB AND X11_xcb_xkb_INCLUDE_PATH)
+    set(X11_xcb_xkb_FOUND TRUE)
+  endif ()
+
+  if (X11_xcb_xrm_LIB AND X11_xcb_xrm_INCLUDE_PATH)
+    set(X11_xcb_xrm_FOUND TRUE)
+  endif ()
+
+  if (X11_xcb_xtest_LIB AND X11_xcb_xtest_INCLUDE_PATH)
     set(X11_xcb_xtest_FOUND TRUE)
   endif ()
 
-  if (X11_xcb_keysyms_LIB)
-    set(X11_xcb_keysyms_FOUND TRUE)
+  if (X11_xcb_xvmc_LIB AND X11_xcb_xvmc_INCLUDE_PATH)
+    set(X11_xcb_xvmc_FOUND TRUE)
   endif ()
 
-  if (X11_xcb_xkb_LIB)
-    set(X11_xcb_xkb_FOUND TRUE)
+  if (X11_xcb_xv_LIB AND X11_xcb_xv_INCLUDE_PATH)
+    set(X11_xcb_xv_FOUND TRUE)
   endif ()
 
   if (X11_Xdmcp_INCLUDE_PATH AND X11_Xdmcp_LIB)
@@ -617,6 +801,69 @@
       INTERFACE_LINK_LIBRARIES "X11::xcb;X11::X11")
   endif ()
 
+  if (X11_xcb_composite_FOUND AND NOT TARGET X11::xcb_composite)
+    add_library(X11::xcb_composite UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_composite PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_composite_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
+  if (X11_xcb_cursor_FOUND AND NOT TARGET X11::xcb_cursor)
+    add_library(X11::xcb_cursor UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_cursor PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_cursor_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
+  if (X11_xcb_damage_FOUND AND NOT TARGET X11::xcb_damage)
+    add_library(X11::xcb_damage UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_damage PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_damage_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
+  if (X11_xcb_dpms_FOUND AND NOT TARGET X11::xcb_dpms)
+    add_library(X11::xcb_dpms UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_dpms PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_dpms_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
+  if (X11_xcb_dri2_FOUND AND NOT TARGET X11::xcb_dri2)
+    add_library(X11::xcb_dri2 UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_dri2 PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_dri2_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
+  if (X11_xcb_dri3_FOUND AND NOT TARGET X11::xcb_dri3)
+    add_library(X11::xcb_dri3 UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_dri3 PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_dri3_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
+  if (X11_xcb_errors_FOUND AND NOT TARGET X11::xcb_errors)
+    add_library(X11::xcb_errors UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_errors PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_errors_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
+  if (X11_xcb_ewmh_FOUND AND NOT TARGET X11::xcb_ewmh)
+    add_library(X11::xcb_ewmh UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_ewmh PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_ewmh_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
+  if (X11_xcb_glx_FOUND AND NOT TARGET X11::xcb_glx)
+    add_library(X11::xcb_glx UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_glx PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_glx_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
   if (X11_xcb_icccm_FOUND AND NOT TARGET X11::xcb_icccm)
     add_library(X11::xcb_icccm UNKNOWN IMPORTED)
     set_target_properties(X11::xcb_icccm PROPERTIES
@@ -624,6 +871,27 @@
       INTERFACE_LINK_LIBRARIES "X11::xcb")
   endif ()
 
+  if (X11_xcb_image_FOUND AND NOT TARGET X11::xcb_image)
+    add_library(X11::xcb_image UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_image PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_image_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
+  if (X11_xcb_keysyms_FOUND AND NOT TARGET X11::xcb_keysyms)
+    add_library(X11::xcb_keysyms UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_keysyms PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_keysyms_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
+  if (X11_xcb_present_FOUND AND NOT TARGET X11::xcb_present)
+    add_library(X11::xcb_present UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_present PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_present_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
   if (X11_xcb_randr_FOUND AND NOT TARGET X11::xcb_randr)
     add_library(X11::xcb_randr UNKNOWN IMPORTED)
     set_target_properties(X11::xcb_randr PROPERTIES
@@ -631,6 +899,62 @@
       INTERFACE_LINK_LIBRARIES "X11::xcb")
   endif ()
 
+  if (X11_xcb_record_FOUND AND NOT TARGET X11::xcb_record)
+    add_library(X11::xcb_record UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_record PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_record_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
+  if (X11_xcb_render_FOUND AND NOT TARGET X11::xcb_render)
+    add_library(X11::xcb_render UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_render PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_render_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
+  if (X11_xcb_render_util_FOUND AND NOT TARGET X11::xcb_render_util)
+    add_library(X11::xcb_render_util UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_render_util PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_render_util_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
+  if (X11_xcb_res_FOUND AND NOT TARGET X11::xcb_res)
+    add_library(X11::xcb_res UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_res PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_res_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
+  if (X11_xcb_screensaver_FOUND AND NOT TARGET X11::xcb_screensaver)
+    add_library(X11::xcb_screensaver UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_screensaver PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_screensaver_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
+  if (X11_xcb_shape_FOUND AND NOT TARGET X11::xcb_shape)
+    add_library(X11::xcb_shape UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_shape PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_shape_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
+  if (X11_xcb_shm_FOUND AND NOT TARGET X11::xcb_shm)
+    add_library(X11::xcb_shm UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_shm PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_shm_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
+  if (X11_xcb_sync_FOUND AND NOT TARGET X11::xcb_sync)
+    add_library(X11::xcb_sync UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_sync PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_sync_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
   if (X11_xcb_util_FOUND AND NOT TARGET X11::xcb_util)
     add_library(X11::xcb_util UNKNOWN IMPORTED)
     set_target_properties(X11::xcb_util PROPERTIES
@@ -638,6 +962,13 @@
       INTERFACE_LINK_LIBRARIES "X11::xcb")
   endif ()
 
+  if (X11_xcb_xf86dri_FOUND AND NOT TARGET X11::xcb_xf86dri)
+    add_library(X11::xcb_xf86dri UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_xf86dri PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_xf86dri_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
   if (X11_xcb_xfixes_FOUND AND NOT TARGET X11::xcb_xfixes)
     add_library(X11::xcb_xfixes UNKNOWN IMPORTED)
     set_target_properties(X11::xcb_xfixes PROPERTIES
@@ -645,17 +976,17 @@
       INTERFACE_LINK_LIBRARIES "X11::xcb")
   endif ()
 
-  if (X11_xcb_xtest_FOUND AND NOT TARGET X11::xcb_xtest)
-  add_library(X11::xcb_xtest UNKNOWN IMPORTED)
-  set_target_properties(X11::xcb_xtest PROPERTIES
-    IMPORTED_LOCATION "${X11_xcb_xtest_LIB}"
-    INTERFACE_LINK_LIBRARIES "X11::xcb")
+  if (X11_xcb_xinerama_FOUND AND NOT TARGET X11::xcb_xinerama)
+    add_library(X11::xcb_xinerama UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_xinerama PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_xinerama_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
   endif ()
 
-  if (X11_xcb_keysyms_FOUND AND NOT TARGET X11::xcb_keysyms)
-    add_library(X11::xcb_keysyms UNKNOWN IMPORTED)
-    set_target_properties(X11::xcb_keysyms PROPERTIES
-      IMPORTED_LOCATION "${X11_xcb_keysyms_LIB}"
+  if (X11_xcb_xinput_FOUND AND NOT TARGET X11::xcb_xinput)
+    add_library(X11::xcb_xinput UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_xinput PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_xinput_LIB}"
       INTERFACE_LINK_LIBRARIES "X11::xcb")
   endif ()
 
@@ -666,6 +997,34 @@
       INTERFACE_LINK_LIBRARIES "X11::xcb")
   endif ()
 
+  if (X11_xcb_xrm_FOUND AND NOT TARGET X11::xcb_xrm)
+    add_library(X11::xcb_xrm UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_xrm PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_xrm_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
+  if (X11_xcb_xtest_FOUND AND NOT TARGET X11::xcb_xtest)
+  add_library(X11::xcb_xtest UNKNOWN IMPORTED)
+  set_target_properties(X11::xcb_xtest PROPERTIES
+    IMPORTED_LOCATION "${X11_xcb_xtest_LIB}"
+    INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
+  if (X11_xcb_xvmc_FOUND AND NOT TARGET X11::xcb_xvmc)
+    add_library(X11::xcb_xvmc UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_xvmc PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_xvmc_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
+  if (X11_xcb_xv_FOUND AND NOT TARGET X11::xcb_xv)
+    add_library(X11::xcb_xv UNKNOWN IMPORTED)
+    set_target_properties(X11::xcb_xv PROPERTIES
+      IMPORTED_LOCATION "${X11_xcb_xv_LIB}"
+      INTERFACE_LINK_LIBRARIES "X11::xcb")
+  endif ()
+
   if (X11_Xcomposite_FOUND AND NOT TARGET X11::Xcomposite)
     add_library(X11::Xcomposite UNKNOWN IMPORTED)
     set_target_properties(X11::Xcomposite PROPERTIES
@@ -873,20 +1232,70 @@
     X11_Xau_INCLUDE_PATH
     X11_xcb_LIB
     X11_xcb_INCLUDE_PATH
+    X11_xcb_composite_LIB
+    X11_xcb_composite_INCLUDE_PATH
+    X11_xcb_cursor_LIB
+    X11_xcb_cursor_INCLUDE_PATH
+    X11_xcb_damage_LIB
+    X11_xcb_damage_INCLUDE_PATH
+    X11_xcb_dpms_LIB
+    X11_xcb_dpms_INCLUDE_PATH
+    X11_xcb_dri2_LIB
+    X11_xcb_dri2_INCLUDE_PATH
+    X11_xcb_dri3_LIB
+    X11_xcb_dri3_INCLUDE_PATH
+    X11_xcb_errors_LIB
+    X11_xcb_errors_INCLUDE_PATH
+    X11_xcb_ewmh_LIB
+    X11_xcb_ewmh_INCLUDE_PATH
+    X11_xcb_glx_LIB
+    X11_xcb_glx_INCLUDE_PATH
     X11_xcb_icccm_LIB
     X11_xcb_icccm_INCLUDE_PATH
-    X11_xcb_randr_LIB
-    X11_xcb_randr_INCLUDE_PATH
-    X11_xcb_util_LIB
-    X11_xcb_util_INCLUDE_PATH
-    X11_xcb_xfixes_LIB
-    X11_xcb_xfixes_INCLUDE_PATH
-    X11_xcb_xtest_LIB
-    X11_xcb_xtest_INCLUDE_PATH
+    X11_xcb_image_LIB
+    X11_xcb_image_INCLUDE_PATH
     X11_xcb_keysyms_LIB
     X11_xcb_keysyms_INCLUDE_PATH
+    X11_xcb_present_LIB
+    X11_xcb_present_INCLUDE_PATH
+    X11_xcb_randr_LIB
+    X11_xcb_randr_INCLUDE_PATH
+    X11_xcb_record_LIB
+    X11_xcb_record_INCLUDE_PATH
+    X11_xcb_render_LIB
+    X11_xcb_render_INCLUDE_PATH
+    X11_xcb_render_util_LIB
+    X11_xcb_render_util_INCLUDE_PATH
+    X11_xcb_res_LIB
+    X11_xcb_res_INCLUDE_PATH
+    X11_xcb_screensaver_LIB
+    X11_xcb_screensaver_INCLUDE_PATH
+    X11_xcb_shape_LIB
+    X11_xcb_shape_INCLUDE_PATH
+    X11_xcb_shm_LIB
+    X11_xcb_shm_INCLUDE_PATH
+    X11_xcb_sync_LIB
+    X11_xcb_sync_INCLUDE_PATH
+    X11_xcb_util_LIB
+    X11_xcb_util_INCLUDE_PATH
+    X11_xcb_xf86dri_LIB
+    X11_xcb_xf86dri_INCLUDE_PATH
+    X11_xcb_xfixes_LIB
+    X11_xcb_xfixes_INCLUDE_PATH
+    X11_xcb_xinerama_LIB
+    X11_xcb_xinerama_INCLUDE_PATH
+    X11_xcb_xinput_LIB
+    X11_xcb_xinput_INCLUDE_PATH
     X11_xcb_xkb_LIB
     X11_X11_xcb_LIB
+    X11_xcb_xrm_LIB
+    X11_xcb_xrm_INCLUDE_PATH
+    X11_xcb_xtest_LIB
+    X11_xcb_xtest_INCLUDE_PATH
+    X11_xcb_xvmc_LIB
+    X11_xcb_xvmc_INCLUDE_PATH
+    X11_xcb_xv_LIB
+    X11_xcb_xv_INCLUDE_PATH
     X11_X11_xcb_INCLUDE_PATH
     X11_Xlib_INCLUDE_PATH
     X11_Xutil_INCLUDE_PATH
diff --git a/Modules/FindXCTest.cmake b/Modules/FindXCTest.cmake
index 7118df2..40e767b 100644
--- a/Modules/FindXCTest.cmake
+++ b/Modules/FindXCTest.cmake
@@ -13,7 +13,7 @@
 and bundle extension. The Mac Developer Library provides more
 information in the `Testing with Xcode`_ document.
 
-.. _Testing with Xcode: https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/testing_with_xcode/
+.. _Testing with Xcode: https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/testing_with_xcode/
 
 Module Functions
 ^^^^^^^^^^^^^^^^
diff --git a/Modules/FindwxWidgets.cmake b/Modules/FindwxWidgets.cmake
index 3159ff7..cc76b35 100644
--- a/Modules/FindwxWidgets.cmake
+++ b/Modules/FindwxWidgets.cmake
@@ -113,6 +113,16 @@
    include(${wxWidgets_USE_FILE})
    # and for each of your dependent executable/library targets:
    target_link_libraries(<YourTarget> ${wxWidgets_LIBRARIES})
+
+Imported targets
+^^^^^^^^^^^^^^^^
+
+.. versionadded:: 3.27
+
+This module defines the following :prop_tgt:`IMPORTED` targets:
+
+``wxWidgets::wxWidgets``
+  An interface library providing usage requirements for the found components.
 #]=======================================================================]
 
 #
@@ -981,6 +991,17 @@
   )
 unset(wxWidgets_HANDLE_COMPONENTS)
 
+if(wxWidgets_FOUND AND NOT TARGET wxWidgets::wxWidgets)
+  add_library(wxWidgets::wxWidgets INTERFACE IMPORTED)
+  target_link_libraries(wxWidgets::wxWidgets INTERFACE ${wxWidgets_LIBRARIES})
+  target_link_directories(wxWidgets::wxWidgets INTERFACE ${wxWidgets_LIBRARY_DIRS})
+  target_include_directories(wxWidgets::wxWidgets INTERFACE ${wxWidgets_INCLUDE_DIRS})
+  target_compile_options(wxWidgets::wxWidgets INTERFACE ${wxWidgets_CXX_FLAGS})
+  target_compile_definitions(wxWidgets::wxWidgets INTERFACE ${wxWidgets_DEFINITIONS})
+  # FIXME: Add "$<$<CONFIG:Debug>:${wxWidgets_DEFINITIONS_DEBUG}>"
+  # if the debug library variant is available.
+endif()
+
 #=====================================================================
 # Macros for use in wxWidgets apps.
 # - This module will not fail to find wxWidgets based on the code
diff --git a/Modules/FindwxWindows.cmake b/Modules/FindwxWindows.cmake
index 15dacbb..6e4be91 100644
--- a/Modules/FindwxWindows.cmake
+++ b/Modules/FindwxWindows.cmake
@@ -613,7 +613,7 @@
     option(WXWINDOWS_USE_SHARED_LIBS "Use shared versions (.so) of wxWindows libraries" ON)
     mark_as_advanced(WXWINDOWS_USE_SHARED_LIBS)
 
-    # JW removed option and force the develper th SET it.
+    # JW removed option and force the developer to SET it.
     # option(WXWINDOWS_USE_GL "use wxWindows with GL support (use additional
     # --gl-libs for wx-config)?" OFF)
 
diff --git a/Modules/FortranCInterface.cmake b/Modules/FortranCInterface.cmake
index ed8830e..2c85029 100644
--- a/Modules/FortranCInterface.cmake
+++ b/Modules/FortranCInterface.cmake
@@ -373,6 +373,7 @@
                  "-DCMAKE_C_FLAGS_RELEASE:STRING=${CMAKE_C_FLAGS_RELEASE}"
                  "-DCMAKE_CXX_FLAGS_RELEASE:STRING=${CMAKE_CXX_FLAGS_RELEASE}"
                  "-DCMAKE_Fortran_FLAGS_RELEASE:STRING=${CMAKE_Fortran_FLAGS_RELEASE}"
+                 "-DFortranCInterface_BINARY_DIR=${FortranCInterface_BINARY_DIR}"
                  ${_FortranCInterface_OSX_ARCH}
                  ${_FortranCInterface_EXE_LINKER_FLAGS}
       OUTPUT_VARIABLE _output)
diff --git a/Modules/FortranCInterface/CMakeLists.txt b/Modules/FortranCInterface/CMakeLists.txt
index 4bd7006..a0f1862 100644
--- a/Modules/FortranCInterface/CMakeLists.txt
+++ b/Modules/FortranCInterface/CMakeLists.txt
@@ -6,11 +6,11 @@
 include(${FortranCInterface_BINARY_DIR}/Input.cmake OPTIONAL)
 
 # Check if the C compiler supports '$' in identifiers.
-include(CheckCSourceCompiles)
-check_c_source_compiles("
-extern int dollar$(void);
-int main() { return 0; }
-" C_SUPPORTS_DOLLAR)
+include(CheckSourceCompiles)
+check_source_compiles(C
+"extern int dollar$(void);
+int main() { return 0; }"
+C_SUPPORTS_DOLLAR)
 
 # List manglings of global symbol names to try.
 set(global_symbols
diff --git a/Modules/GenerateExportHeader.cmake b/Modules/GenerateExportHeader.cmake
index 7461a3e..01a6e4f 100644
--- a/Modules/GenerateExportHeader.cmake
+++ b/Modules/GenerateExportHeader.cmake
@@ -198,19 +198,19 @@
 support for the compiler/architecture in use.
 #]=======================================================================]
 
-include(CheckCCompilerFlag)
-include(CheckCXXCompilerFlag)
+include(CheckCompilerFlag)
+include(CheckSourceCompiles)
 
 # TODO: Install this macro separately?
 macro(_check_cxx_compiler_attribute _ATTRIBUTE _RESULT)
-  check_cxx_source_compiles("${_ATTRIBUTE} int somefunc() { return 0; }
+  check_source_compiles(CXX "${_ATTRIBUTE} int somefunc() { return 0; }
     int main() { return somefunc();}" ${_RESULT}
   )
 endmacro()
 
 # TODO: Install this macro separately?
 macro(_check_c_compiler_attribute _ATTRIBUTE _RESULT)
-  check_c_source_compiles("${_ATTRIBUTE} int somefunc() { return 0; }
+  check_source_compiles(C "${_ATTRIBUTE} int somefunc() { return 0; }
     int main() { return somefunc();}" ${_RESULT}
   )
 endmacro()
@@ -226,7 +226,7 @@
   endif()
 
   # Exclude XL here because it misinterprets -fvisibility=hidden even though
-  # the check_cxx_compiler_flag passes
+  # the check_compiler_flag passes
   if(NOT GCC_TOO_OLD
       AND NOT _INTEL_TOO_OLD
       AND NOT WIN32
@@ -235,12 +235,12 @@
       AND NOT CMAKE_CXX_COMPILER_ID MATCHES "^(PGI|NVHPC)$"
       AND NOT CMAKE_CXX_COMPILER_ID MATCHES Watcom)
     if (CMAKE_CXX_COMPILER_LOADED)
-      check_cxx_compiler_flag(-fvisibility=hidden COMPILER_HAS_HIDDEN_VISIBILITY)
-      check_cxx_compiler_flag(-fvisibility-inlines-hidden
+      check_compiler_flag(CXX -fvisibility=hidden COMPILER_HAS_HIDDEN_VISIBILITY)
+      check_compiler_flag(CXX -fvisibility-inlines-hidden
         COMPILER_HAS_HIDDEN_INLINE_VISIBILITY)
     else()
-      check_c_compiler_flag(-fvisibility=hidden COMPILER_HAS_HIDDEN_VISIBILITY)
-      check_c_compiler_flag(-fvisibility-inlines-hidden
+      check_compiler_flag(C -fvisibility=hidden COMPILER_HAS_HIDDEN_VISIBILITY)
+      check_compiler_flag(C -fvisibility-inlines-hidden
         COMPILER_HAS_HIDDEN_INLINE_VISIBILITY)
     endif()
   endif()
@@ -293,7 +293,7 @@
   set(DEFINE_IMPORT)
   set(DEFINE_NO_EXPORT)
 
-  if (COMPILER_HAS_DEPRECATED_ATTR)
+  if (COMPILER_HAS_DEPRECATED_ATTR AND NOT WIN32)
     set(DEFINE_DEPRECATED "__attribute__ ((__deprecated__))")
   elseif(COMPILER_HAS_DEPRECATED)
     set(DEFINE_DEPRECATED "__declspec(deprecated)")
diff --git a/Modules/GetPrerequisites.cmake b/Modules/GetPrerequisites.cmake
index 0ba35b6..0cd49ab 100644
--- a/Modules/GetPrerequisites.cmake
+++ b/Modules/GetPrerequisites.cmake
@@ -730,7 +730,7 @@
 
   if(gp_tool MATCHES "ldd$")
     set(gp_cmd_args "")
-    set(gp_regex "^[\t ]*[^\t ]+ =>[\t ]+([^\t\(]+)( \(.+\))?${eol_char}$")
+    set(gp_regex "^[\t ]*[^\t ]+ =>[\t ]+(/[^\t\(]+)( \(.+\))?${eol_char}$")
     set(gp_regex_error "not found${eol_char}$")
     set(gp_regex_fallback "^[\t ]*([^\t ]+) => ([^\t ]+).*${eol_char}$")
     set(gp_regex_cmp_count 1)
diff --git a/Modules/Internal/CPack/CPackRPM.cmake b/Modules/Internal/CPack/CPackRPM.cmake
index 8ac1f6b..36c0a3f 100644
--- a/Modules/Internal/CPack/CPackRPM.cmake
+++ b/Modules/Internal/CPack/CPackRPM.cmake
@@ -1150,7 +1150,7 @@
   endforeach()
 
   # CPACK_RPM_SPEC_INSTALL_POST
-  # May be used to define a RPM post intallation script
+  # May be used to define a RPM post installation script
   # for example setting it to "/bin/true" may prevent
   # rpmbuild from stripping binaries.
   if(CPACK_RPM_SPEC_INSTALL_POST)
diff --git a/Modules/Internal/CPack/ISComponents.pas b/Modules/Internal/CPack/ISComponents.pas
new file mode 100644
index 0000000..8b5c8b4
--- /dev/null
+++ b/Modules/Internal/CPack/ISComponents.pas
@@ -0,0 +1,88 @@
+{ Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+file Copyright.txt or https://cmake.org/licensing for details. }
+
+function CPackGetCustomInstallationMessage(Param: String): String;
+begin
+  Result := SetupMessage(msgCustomInstallation);
+end;
+
+{ Downloaded components }
+#ifdef CPackDownloadCount
+const
+  NO_PROGRESS_BOX = 4;
+  RESPOND_YES_TO_ALL = 16;
+var
+  CPackDownloadPage: TDownloadWizardPage;
+  CPackShell: Variant;
+
+<event('InitializeWizard')>
+procedure CPackInitializeWizard();
+begin
+  CPackDownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), nil);
+  CPackShell := CreateOleObject('Shell.Application');
+end;
+
+<event('NextButtonClick')>
+function CPackNextButtonClick(CurPageID: Integer): Boolean;
+begin
+  if CurPageID = wpReady then
+  begin
+    CPackDownloadPage.Clear;
+    CPackDownloadPage.Show;
+
+#sub AddDownload
+  if WizardIsComponentSelected('{#CPackDownloadComponents[i]}') then
+    #emit "CPackDownloadPage.Add('" + CPackDownloadUrls[i] + "', '" + CPackDownloadArchives[i] + ".zip', '" + CPackDownloadHashes[i] + "');"
+#endsub
+#define i
+#for {i = 0; i < CPackDownloadCount; i++} AddDownload
+#undef i
+
+    try
+      try
+        CPackDownloadPage.Download;
+        Result := True;
+      except
+        if not CPackDownloadPage.AbortedByUser then
+          SuppressibleMsgBox(AddPeriod(GetExceptionMessage), mbCriticalError, MB_OK, IDOK);
+
+        Result := False;
+      end;
+    finally
+      CPackDownloadPage.Hide;
+    end;
+  end else
+    Result := True;
+end;
+
+procedure CPackExtractFile(ArchiveName, FileName: String);
+var
+  ZipFileName: String;
+  ZipFile: Variant;
+  Item: Variant;
+  TargetFolderName: String;
+  TargetFolder: Variant;
+begin
+  TargetFolderName := RemoveBackslashUnlessRoot(ExpandConstant('{tmp}\' + ArchiveName + '\' + ExtractFileDir(FileName)));
+  ZipFileName := ExpandConstant('{tmp}\' + ArchiveName + '.zip');
+
+  if not DirExists(TargetFolderName) then
+    if not ForceDirectories(TargetFolderName) then
+      RaiseException(Format('Target path "%s" cannot be created', [TargetFolderName]));
+
+  ZipFile := CPackShell.NameSpace(ZipFileName);
+  if VarIsClear(ZipFile) then
+    RaiseException(Format('Cannot open ZIP file "%s" or does not exist', [ZipFileName]));
+
+  Item := ZipFile.ParseName(FileName);
+  if VarIsClear(Item) then
+    RaiseException(Format('Cannot find "%s" in "%s" ZIP file', [FileName, ZipFileName]));
+
+  TargetFolder := CPackShell.NameSpace(TargetFolderName);
+  if VarIsClear(TargetFolder) then
+    RaiseException(Format('Target path "%s" does not exist', [TargetFolderName]));
+
+  TargetFolder.CopyHere(Item, NO_PROGRESS_BOX or RESPOND_YES_TO_ALL);
+end;
+
+#endif
diff --git a/Modules/Internal/CPack/ISScript.template.in b/Modules/Internal/CPack/ISScript.template.in
new file mode 100644
index 0000000..1171058
--- /dev/null
+++ b/Modules/Internal/CPack/ISScript.template.in
@@ -0,0 +1,34 @@
+; Script generated by the CPack Inno Setup generator.
+; All changes made in this file will be lost when CPack is run again.
+
+@CPACK_INNOSETUP_INCLUDES_INTERNAL@
+
+[Setup]
+@CPACK_INNOSETUP_SETUP_INTERNAL@
+
+[Languages]
+@CPACK_INNOSETUP_LANGUAGES_INTERNAL@
+
+[Dirs]
+@CPACK_INNOSETUP_DIRS_INTERNAL@
+
+[Files]
+@CPACK_INNOSETUP_FILES_INTERNAL@
+
+[Types]
+@CPACK_INNOSETUP_TYPES_INTERNAL@
+
+[Components]
+@CPACK_INNOSETUP_COMPONENTS_INTERNAL@
+
+[Tasks]
+@CPACK_INNOSETUP_TASKS_INTERNAL@
+
+[Icons]
+@CPACK_INNOSETUP_ICONS_INTERNAL@
+
+[Run]
+@CPACK_INNOSETUP_RUN_INTERNAL@
+
+[Code]
+@CPACK_INNOSETUP_CODE_INTERNAL@
diff --git a/Modules/Internal/CheckFlagCommonConfig.cmake b/Modules/Internal/CheckFlagCommonConfig.cmake
index f8481cd..8c5703d 100644
--- a/Modules/Internal/CheckFlagCommonConfig.cmake
+++ b/Modules/Internal/CheckFlagCommonConfig.cmake
@@ -7,7 +7,8 @@
 # It's content may change in any way between releases.
 
 include_guard(GLOBAL)
-cmake_policy(PUSH)
+
+block(SCOPE_FOR POLICIES)
 cmake_policy(SET CMP0054 NEW) # if() quoted variables not dereferenced
 cmake_policy(SET CMP0057 NEW) # if() supports IN_LIST
 
@@ -74,4 +75,4 @@
   endforeach()
 endmacro()
 
-cmake_policy(POP)
+endblock()
diff --git a/Modules/Internal/CheckLinkerFlag.cmake b/Modules/Internal/CheckLinkerFlag.cmake
index 7613105..b872b51 100644
--- a/Modules/Internal/CheckLinkerFlag.cmake
+++ b/Modules/Internal/CheckLinkerFlag.cmake
@@ -6,7 +6,7 @@
 include(Internal/CheckSourceCompiles)
 include(CMakeCheckCompilerFlagCommonPatterns)
 
-cmake_policy(PUSH)
+block(SCOPE_FOR POLICIES)
 cmake_policy(SET CMP0054 NEW) # if() quoted variables not dereferenced
 
 function(CMAKE_CHECK_LINKER_FLAG _lang _flag _var)
@@ -49,4 +49,4 @@
   cmake_check_flag_common_finish()
 endfunction()
 
-cmake_policy(POP)
+endblock()
diff --git a/Modules/Internal/CheckSourceCompiles.cmake b/Modules/Internal/CheckSourceCompiles.cmake
index 83d7020..14a9a61 100644
--- a/Modules/Internal/CheckSourceCompiles.cmake
+++ b/Modules/Internal/CheckSourceCompiles.cmake
@@ -3,7 +3,7 @@
 
 include_guard(GLOBAL)
 
-cmake_policy(PUSH)
+block(SCOPE_FOR POLICIES)
 cmake_policy(SET CMP0054 NEW) # if() quoted variables not dereferenced
 cmake_policy(SET CMP0057 NEW) # if() supports IN_LIST
 
@@ -131,4 +131,4 @@
   endif()
 endfunction()
 
-cmake_policy(POP)
+endblock()
diff --git a/Modules/Internal/CheckSourceRuns.cmake b/Modules/Internal/CheckSourceRuns.cmake
index 805d98d..c01081e 100644
--- a/Modules/Internal/CheckSourceRuns.cmake
+++ b/Modules/Internal/CheckSourceRuns.cmake
@@ -3,7 +3,7 @@
 
 include_guard(GLOBAL)
 
-cmake_policy(PUSH)
+block(SCOPE_FOR POLICIES)
 cmake_policy(SET CMP0054 NEW) # if() quoted variables not dereferenced
 cmake_policy(SET CMP0057 NEW) # if() supports IN_LIST
 
@@ -124,4 +124,4 @@
   endif()
 endfunction()
 
-cmake_policy(POP)
+endblock()
diff --git a/Modules/Internal/HeaderpadWorkaround.cmake b/Modules/Internal/HeaderpadWorkaround.cmake
index 9a7f9f5..fccfaae 100644
--- a/Modules/Internal/HeaderpadWorkaround.cmake
+++ b/Modules/Internal/HeaderpadWorkaround.cmake
@@ -9,7 +9,7 @@
   return()
 endif()
 
-cmake_policy(PUSH)
+block(SCOPE_FOR POLICIES)
 cmake_policy(SET CMP0054 NEW) # if() quoted variables not dereferenced
 
 function(__cmake_internal_workaround_headerpad_flag_conflict _LANG)
@@ -66,4 +66,4 @@
 unset(__lang)
 unset(__enabled_languages)
 
-cmake_policy(POP)
+endblock()
diff --git a/Modules/MatlabTestsRedirect.cmake b/Modules/MatlabTestsRedirect.cmake
index d651cdd..8973231 100644
--- a/Modules/MatlabTestsRedirect.cmake
+++ b/Modules/MatlabTestsRedirect.cmake
@@ -19,8 +19,8 @@
 #   -P FindMatlab_TestsRedirect.cmake
 
 set(Matlab_UNIT_TESTS_CMD -nosplash -nodesktop -nodisplay ${Matlab_ADDITIONAL_STARTUP_OPTIONS})
-if(WIN32)
-  set(Matlab_UNIT_TESTS_CMD ${Matlab_UNIT_TESTS_CMD} -wait)
+if(WIN32 AND maut_BATCH_OPTION STREQUAL "-r")
+  list(APPEND Matlab_UNIT_TESTS_CMD -wait)
 endif()
 
 if(NOT test_timeout)
diff --git a/Modules/Platform/Android-Determine.cmake b/Modules/Platform/Android-Determine.cmake
index 715f68b..307e4c9 100644
--- a/Modules/Platform/Android-Determine.cmake
+++ b/Modules/Platform/Android-Determine.cmake
@@ -34,18 +34,26 @@
 cmake_policy(SET CMP0057 NEW) # if IN_LIST
 
 # If using Android tools for Visual Studio, compile a sample project to get the
-# sysroot.
+# NDK path and set the processor from the generator platform.
 if(CMAKE_GENERATOR MATCHES "Visual Studio")
-  if(NOT CMAKE_SYSROOT)
-    set(vcx_platform ${CMAKE_GENERATOR_PLATFORM})
-    if(CMAKE_GENERATOR MATCHES "Visual Studio 1[45]")
-      set(vcx_sysroot_var "Sysroot")
+  if(NOT CMAKE_ANDROID_ARCH_ABI AND NOT CMAKE_SYSTEM_PROCESSOR)
+    if(CMAKE_GENERATOR_PLATFORM STREQUAL "ARM")
+      set(CMAKE_SYSTEM_PROCESSOR "armv7-a")
+    elseif(CMAKE_GENERATOR_PLATFORM STREQUAL "ARM64")
+      set(CMAKE_SYSTEM_PROCESSOR "aarch64")
+    elseif(CMAKE_GENERATOR_PLATFORM STREQUAL "x86")
+      set(CMAKE_SYSTEM_PROCESSOR "i686")
+    elseif(CMAKE_GENERATOR_PLATFORM STREQUAL "x64")
+      set(CMAKE_SYSTEM_PROCESSOR "x86_64")
     else()
-      set(vcx_sysroot_var "SysrootLink")
+      message(FATAL_ERROR "Unhandled generator platform, please choose ARM, ARM64, x86 or x86_64 using -A")
     endif()
+  endif()
+  if(NOT CMAKE_ANDROID_NDK)
+    set(vcx_platform ${CMAKE_GENERATOR_PLATFORM})
     if(CMAKE_GENERATOR MATCHES "Visual Studio 14")
       set(vcx_revision "2.0")
-    elseif(CMAKE_GENERATOR MATCHES "Visual Studio 1[56]")
+    elseif(CMAKE_GENERATOR MATCHES "Visual Studio 1[567]")
       set(vcx_revision "3.0")
     else()
       set(vcx_revision "")
@@ -62,16 +70,16 @@
       RESULT_VARIABLE VCXPROJ_INSPECT_RESULT
       )
     unset(_msbuild)
-    if(NOT CMAKE_SYSROOT AND VCXPROJ_INSPECT_OUTPUT MATCHES "CMAKE_SYSROOT=([^%\r\n]+)[\r\n]")
+    if(VCXPROJ_INSPECT_OUTPUT MATCHES "CMAKE_ANDROID_NDK=([^%\r\n]+)[\r\n]")
       # Strip VS diagnostic output from the end of the line.
-      string(REGEX REPLACE " \\(TaskId:[0-9]*\\)$" "" _sysroot "${CMAKE_MATCH_1}")
-      if(EXISTS "${_sysroot}")
-        file(TO_CMAKE_PATH "${_sysroot}" CMAKE_SYSROOT)
+      string(REGEX REPLACE " \\(TaskId:[0-9]*\\)$" "" _ndk "${CMAKE_MATCH_1}")
+      if(EXISTS "${_ndk}")
+        file(TO_CMAKE_PATH "${_ndk}" CMAKE_ANDROID_NDK)
       endif()
     endif()
     if(VCXPROJ_INSPECT_RESULT)
       message(CONFIGURE_LOG
-        "Determining the sysroot for the Android NDK failed.
+        "Determining the Android NDK failed from msbuild failed.
 The output was:
 ${VCXPROJ_INSPECT_RESULT}
 ${VCXPROJ_INSPECT_OUTPUT}
@@ -79,7 +87,7 @@
 ")
     else()
       message(CONFIGURE_LOG
-        "Determining the sysroot for the Android NDK succeeded.
+        "Determining the Android NDK succeeded.
 The output was:
 ${VCXPROJ_INSPECT_RESULT}
 ${VCXPROJ_INSPECT_OUTPUT}
diff --git a/Modules/Platform/Android/VCXProjInspect.vcxproj.in b/Modules/Platform/Android/VCXProjInspect.vcxproj.in
index 6919d2c..f87d59b 100644
--- a/Modules/Platform/Android/VCXProjInspect.vcxproj.in
+++ b/Modules/Platform/Android/VCXProjInspect.vcxproj.in
@@ -19,6 +19,7 @@
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@vcx_platform@'" Label="Configuration">
     <ConfigurationType>DynamicLibrary</ConfigurationType>
     <CharacterSet>MultiByte</CharacterSet>
+    <AndroidAPILevel>android-21</AndroidAPILevel>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
   <ImportGroup Label="ExtensionSettings">
@@ -29,7 +30,7 @@
   </PropertyGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@vcx_platform@'">
     <PostBuildEvent>
-      <Command>%40echo CMAKE_SYSROOT=$(@vcx_sysroot_var@)</Command>
+      <Command>%40echo CMAKE_ANDROID_NDK=$(VS_NdkRoot)</Command>
     </PostBuildEvent>
   </ItemDefinitionGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
diff --git a/Modules/Platform/Darwin.cmake b/Modules/Platform/Darwin.cmake
index ac2478b..a6c86f1 100644
--- a/Modules/Platform/Darwin.cmake
+++ b/Modules/Platform/Darwin.cmake
@@ -45,6 +45,8 @@
 set(CMAKE_EXTRA_SHARED_LIBRARY_SUFFIXES ".tbd" ".so")
 set(CMAKE_SHARED_MODULE_PREFIX "lib")
 set(CMAKE_SHARED_MODULE_SUFFIX ".so")
+set(CMAKE_APPLE_IMPORT_FILE_PREFIX "lib")
+set(CMAKE_APPLE_IMPORT_FILE_SUFFIX ".tbd")
 set(CMAKE_MODULE_EXISTS 1)
 set(CMAKE_DL_LIBS "")
 if(NOT "${_CURRENT_OSX_VERSION}" VERSION_LESS "10.5")
@@ -108,6 +110,9 @@
   set(CMAKE_${lang}_FRAMEWORK_SEARCH_FLAG -F)
 endforeach()
 
+# To generate text-based stubs
+set(CMAKE_CREATE_TEXT_STUBS "<CMAKE_TAPI> stubify -isysroot <CMAKE_OSX_SYSROOT> -o <TARGET_IMPLIB> <TARGET>")
+
 # Defines LINK_LIBRARY features for frameworks
 set(CMAKE_LINK_LIBRARY_USING_FRAMEWORK "LINKER:-framework,<LIBRARY>")
 set(CMAKE_LINK_LIBRARY_USING_FRAMEWORK_SUPPORTED TRUE)
diff --git a/Modules/Platform/Linux-LCC-Fortran.cmake b/Modules/Platform/Linux-LCC-Fortran.cmake
index bf2a1c2..308c771 100644
--- a/Modules/Platform/Linux-LCC-Fortran.cmake
+++ b/Modules/Platform/Linux-LCC-Fortran.cmake
@@ -1,7 +1,9 @@
 include(Platform/Linux-LCC)
 __linux_compiler_lcc(Fortran)
-if (CMAKE_Fortran_COMPILER_VERSION VERSION_LESS "1.26.03")
+if (CMAKE_Fortran_COMPILER_VERSION VERSION_GREATER_EQUAL "1.26.03")
+  set(CMAKE_SHARED_LIBRARY_LINK_Fortran_FLAGS "-lgfortran")
+elseif (CMAKE_Fortran_COMPILER_VERSION VERSION_GREATER_EQUAL "1.24.01")
   set(CMAKE_SHARED_LIBRARY_LINK_Fortran_FLAGS "-llfortran")
 else()
-  set(CMAKE_SHARED_LIBRARY_LINK_Fortran_FLAGS "-lgfortran")
+  unset(CMAKE_Fortran_CREATE_PREPROCESSED_SOURCE)
 endif()
diff --git a/Modules/Platform/Windows-Apple-Swift.cmake b/Modules/Platform/Windows-Apple-Swift.cmake
index 1177755..3f754fd 100644
--- a/Modules/Platform/Windows-Apple-Swift.cmake
+++ b/Modules/Platform/Windows-Apple-Swift.cmake
@@ -1 +1,3 @@
 set(CMAKE_Swift_IMPLIB_LINKER_FLAGS "-Xlinker -implib:<TARGET_IMPLIB>")
+set(CMAKE_Swift_FLAGS_DEBUG_LINKER_FLAGS "-Xlinker -debug")
+set(CMAKE_Swift_FLAGS_RELWITHDEBINFO_LINKER_FLAGS "-Xlinker -debug")
diff --git a/Modules/Platform/Windows-IntelLLVM.cmake b/Modules/Platform/Windows-IntelLLVM.cmake
index 43f5874..eac3f0a 100644
--- a/Modules/Platform/Windows-IntelLLVM.cmake
+++ b/Modules/Platform/Windows-IntelLLVM.cmake
@@ -54,6 +54,7 @@
     "${_CMAKE_VS_LINK_EXE}<CMAKE_${lang}_COMPILER> ${CMAKE_CL_NOLOGO} <CMAKE_${lang}_LINK_FLAGS> <OBJECTS> ${CMAKE_START_TEMP_FILE} <LINK_FLAGS> <LINK_LIBRARIES> /link /out:<TARGET> /implib:<TARGET_IMPLIB> /pdb:<TARGET_PDB> /version:<TARGET_VERSION_MAJOR>.<TARGET_VERSION_MINOR>${_PLATFORM_LINK_FLAGS} ${CMAKE_END_TEMP_FILE}")
   set(CMAKE_${lang}_CREATE_SHARED_LIBRARY
     "${_CMAKE_VS_LINK_DLL}<CMAKE_${lang}_COMPILER> ${CMAKE_CL_NOLOGO} <CMAKE_${lang}_LINK_FLAGS> <OBJECTS> ${CMAKE_START_TEMP_FILE} -LD <LINK_FLAGS> <LINK_LIBRARIES> -link /out:<TARGET> /implib:<TARGET_IMPLIB> /pdb:<TARGET_PDB> /version:<TARGET_VERSION_MAJOR>.<TARGET_VERSION_MINOR>${_PLATFORM_LINK_FLAGS} ${CMAKE_END_TEMP_FILE}")
+  set(CMAKE_${lang}_CREATE_SHARED_MODULE ${CMAKE_${lang}_CREATE_SHARED_LIBRARY})
   if (NOT "${lang}" STREQUAL "Fortran" OR CMAKE_${lang}_COMPILER_VERSION VERSION_GREATER_EQUAL 2022.1)
     # The Fortran driver does not support -fuse-ld=llvm-lib before compiler version 2022.1
     set(CMAKE_${lang}_CREATE_STATIC_LIBRARY
diff --git a/Modules/UseJava.cmake b/Modules/UseJava.cmake
index 54a8cf7..99fd617 100644
--- a/Modules/UseJava.cmake
+++ b/Modules/UseJava.cmake
@@ -294,7 +294,7 @@
 
   .. deprecated:: 3.11
     This command will no longer be supported starting with version 10 of the JDK
-    due to the `suppression of javah tool <https://openjdk.java.net/jeps/313>`_.
+    due to the `suppression of javah tool <https://openjdk.org/jeps/313>`_.
     The :ref:`add_jar(GENERATE_NATIVE_HEADERS) <add_jar>` command should be
     used instead.
 
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index e99da49..bcaf890 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -11,18 +11,6 @@
 
 include(CheckIncludeFile)
 
-if(NOT CMake_DEFAULT_RECURSION_LIMIT)
-  if(DEFINED ENV{DASHBOARD_TEST_FROM_CTEST})
-    set(CMake_DEFAULT_RECURSION_LIMIT 100)
-  elseif(MINGW OR MSYS)
-    set(CMake_DEFAULT_RECURSION_LIMIT 400)
-  elseif(WIN32 AND CMAKE_C_COMPILER_ID STREQUAL "IntelLLVM")
-    set(CMake_DEFAULT_RECURSION_LIMIT 600)
-  else()
-    set(CMake_DEFAULT_RECURSION_LIMIT 1000)
-  endif()
-endif()
-
 if(APPLE)
   set(CMake_USE_MACH_PARSER 1)
 endif()
@@ -140,6 +128,7 @@
   cmCLocaleEnvironmentScope.cxx
   cmCMakePath.h
   cmCMakePath.cxx
+  cmCMakePresetErrors.h
   cmCMakePresetsGraph.cxx
   cmCMakePresetsGraph.h
   cmCMakePresetsGraphInternal.h
@@ -204,6 +193,8 @@
   cmDyndepCollation.h
   cmELF.h
   cmELF.cxx
+  cmEvaluatedTargetProperty.cxx
+  cmEvaluatedTargetProperty.h
   cmExprParserHelper.cxx
   cmExportBuildAndroidMKGenerator.h
   cmExportBuildAndroidMKGenerator.cxx
@@ -331,6 +322,8 @@
   cmInstallDirectoryGenerator.h
   cmInstallDirectoryGenerator.cxx
   cmJSONHelpers.h
+  cmJSONState.cxx
+  cmJSONState.h
   cmLDConfigLDConfigTool.cxx
   cmLDConfigLDConfigTool.h
   cmLDConfigTool.cxx
@@ -342,6 +335,8 @@
   cmLinkLineComputer.h
   cmLinkLineDeviceComputer.cxx
   cmLinkLineDeviceComputer.h
+  cmList.h
+  cmList.cxx
   cmListFileCache.cxx
   cmListFileCache.h
   cmLocalCommonGenerator.cxx
@@ -767,6 +762,38 @@
     ZLIB::ZLIB
   )
 
+if(CMake_ENABLE_DEBUGGER)
+  target_sources(
+    CMakeLib
+    PRIVATE
+      cmDebuggerAdapter.cxx
+      cmDebuggerAdapter.h
+      cmDebuggerBreakpointManager.cxx
+      cmDebuggerBreakpointManager.h
+      cmDebuggerExceptionManager.cxx
+      cmDebuggerExceptionManager.h
+      cmDebuggerPipeConnection.cxx
+      cmDebuggerPipeConnection.h
+      cmDebuggerProtocol.cxx
+      cmDebuggerProtocol.h
+      cmDebuggerSourceBreakpoint.cxx
+      cmDebuggerSourceBreakpoint.h
+      cmDebuggerStackFrame.cxx
+      cmDebuggerStackFrame.h
+      cmDebuggerThread.cxx
+      cmDebuggerThread.h
+      cmDebuggerThreadManager.cxx
+      cmDebuggerThreadManager.h
+      cmDebuggerVariables.cxx
+      cmDebuggerVariables.h
+      cmDebuggerVariablesHelper.cxx
+      cmDebuggerVariablesHelper.h
+      cmDebuggerVariablesManager.cxx
+      cmDebuggerVariablesManager.h
+    )
+  target_link_libraries(CMakeLib PUBLIC cppdap::cppdap)
+endif()
+
 # Check if we can build the Mach-O parser.
 if(CMake_USE_MACH_PARSER)
   target_sources(
@@ -1023,6 +1050,7 @@
   CPack/cmCPackGeneratorFactory.cxx
   CPack/cmCPackGenerator.cxx
   CPack/cmCPackLog.cxx
+  CPack/cmCPackInnoSetupGenerator.cxx
   CPack/cmCPackNSISGenerator.cxx
   CPack/cmCPackNuGetGenerator.cxx
   CPack/cmCPackSTGZGenerator.cxx
diff --git a/Source/CMakeVersion.cmake b/Source/CMakeVersion.cmake
index 200557b..00d3236 100644
--- a/Source/CMakeVersion.cmake
+++ b/Source/CMakeVersion.cmake
@@ -1,7 +1,7 @@
 # CMake version number components.
 set(CMake_VERSION_MAJOR 3)
 set(CMake_VERSION_MINOR 26)
-set(CMake_VERSION_PATCH 4)
+set(CMake_VERSION_PATCH 20230531)
 #set(CMake_VERSION_RC 0)
 set(CMake_VERSION_IS_DIRTY 0)
 
diff --git a/Source/CPack/IFW/cmCPackIFWCommon.cxx b/Source/CPack/IFW/cmCPackIFWCommon.cxx
index 5d995c3..4a868ae 100644
--- a/Source/CPack/IFW/cmCPackIFWCommon.cxx
+++ b/Source/CPack/IFW/cmCPackIFWCommon.cxx
@@ -5,12 +5,11 @@
 #include <cstddef> // IWYU pragma: keep
 #include <sstream>
 #include <utility>
-#include <vector>
 
 #include "cmCPackGenerator.h"
 #include "cmCPackIFWGenerator.h"
 #include "cmCPackLog.h" // IWYU pragma: keep
-#include "cmStringAlgorithms.h"
+#include "cmList.h"
 #include "cmSystemTools.h"
 #include "cmTimestamp.h"
 #include "cmVersionConfig.h"
@@ -76,13 +75,13 @@
 void cmCPackIFWCommon::ExpandListArgument(
   const std::string& arg, std::map<std::string, std::string>& argsOut)
 {
-  std::vector<std::string> args = cmExpandedList(arg, false);
+  cmList args{ arg };
   if (args.empty()) {
     return;
   }
 
-  std::size_t i = 0;
-  std::size_t c = args.size();
+  cmList::size_type i = 0;
+  auto c = args.size();
   if (c % 2) {
     argsOut[""] = args[i];
     ++i;
@@ -97,13 +96,13 @@
 void cmCPackIFWCommon::ExpandListArgument(
   const std::string& arg, std::multimap<std::string, std::string>& argsOut)
 {
-  std::vector<std::string> args = cmExpandedList(arg, false);
+  cmList args{ arg };
   if (args.empty()) {
     return;
   }
 
-  std::size_t i = 0;
-  std::size_t c = args.size();
+  cmList::size_type i = 0;
+  auto c = args.size();
   if (c % 2) {
     argsOut.insert(std::pair<std::string, std::string>("", args[i]));
     ++i;
diff --git a/Source/CPack/IFW/cmCPackIFWGenerator.cxx b/Source/CPack/IFW/cmCPackIFWGenerator.cxx
index bc14eb4..5724175 100644
--- a/Source/CPack/IFW/cmCPackIFWGenerator.cxx
+++ b/Source/CPack/IFW/cmCPackIFWGenerator.cxx
@@ -14,6 +14,7 @@
 #include "cmCPackLog.h" // IWYU pragma: keep
 #include "cmDuration.h"
 #include "cmGeneratedFileStream.h"
+#include "cmList.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmValue.h"
@@ -409,8 +410,8 @@
 
   // Repositories
   if (cmValue RepoAllStr = this->GetOption("CPACK_IFW_REPOSITORIES_ALL")) {
-    std::vector<std::string> RepoAllVector = cmExpandedList(RepoAllStr);
-    for (std::string const& r : RepoAllVector) {
+    cmList RepoAllList{ RepoAllStr };
+    for (std::string const& r : RepoAllList) {
       this->GetRepository(r);
     }
   }
diff --git a/Source/CPack/IFW/cmCPackIFWInstaller.cxx b/Source/CPack/IFW/cmCPackIFWInstaller.cxx
index 69440d9..a77c22f 100644
--- a/Source/CPack/IFW/cmCPackIFWInstaller.cxx
+++ b/Source/CPack/IFW/cmCPackIFWInstaller.cxx
@@ -12,6 +12,7 @@
 #include "cmCPackIFWRepository.h"
 #include "cmCPackLog.h" // IWYU pragma: keep
 #include "cmGeneratedFileStream.h"
+#include "cmList.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmValue.h"
diff --git a/Source/CPack/IFW/cmCPackIFWPackage.cxx b/Source/CPack/IFW/cmCPackIFWPackage.cxx
index 1668fb5..083f1ef 100644
--- a/Source/CPack/IFW/cmCPackIFWPackage.cxx
+++ b/Source/CPack/IFW/cmCPackIFWPackage.cxx
@@ -15,6 +15,7 @@
 #include "cmCPackIFWInstaller.h"
 #include "cmCPackLog.h" // IWYU pragma: keep
 #include "cmGeneratedFileStream.h"
+#include "cmList.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmTimestamp.h"
@@ -427,16 +428,16 @@
   }
 
   // QtIFW dependencies
-  std::vector<std::string> deps;
+  cmList deps;
   option = prefix + "DEPENDS";
   if (cmValue value = this->GetOption(option)) {
-    cmExpandList(value, deps);
+    deps.assign(value);
   }
   option = prefix + "DEPENDENCIES";
   if (cmValue value = this->GetOption(option)) {
-    cmExpandList(value, deps);
+    deps.append(value);
   }
-  for (std::string const& d : deps) {
+  for (auto const& d : deps) {
     DependenceStruct dep(d);
     if (this->Generator->Packages.count(dep.Name)) {
       cmCPackIFWPackage& depPkg = this->Generator->Packages[dep.Name];
@@ -455,7 +456,7 @@
   if (this->IsSetToEmpty(option)) {
     this->AlienAutoDependOn.clear();
   } else if (cmValue value = this->GetOption(option)) {
-    std::vector<std::string> depsOn = cmExpandedList(value);
+    cmList depsOn{ value };
     for (std::string const& d : depsOn) {
       DependenceStruct dep(d);
       if (this->Generator->Packages.count(dep.Name)) {
diff --git a/Source/CPack/WiX/cmCPackWIXGenerator.cxx b/Source/CPack/WiX/cmCPackWIXGenerator.cxx
index aeb3db3..1ea78fd 100644
--- a/Source/CPack/WiX/cmCPackWIXGenerator.cxx
+++ b/Source/CPack/WiX/cmCPackWIXGenerator.cxx
@@ -18,6 +18,7 @@
 #include "cmCryptoHash.h"
 #include "cmGeneratedFileStream.h"
 #include "cmInstalledFile.h"
+#include "cmList.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmUuid.h"
@@ -239,7 +240,7 @@
 
   cmValue patchFilePath = GetOption("CPACK_WIX_PATCH_FILE");
   if (patchFilePath) {
-    std::vector<std::string> patchFilePaths = cmExpandedList(patchFilePath);
+    cmList patchFilePaths{ patchFilePath };
 
     for (std::string const& p : patchFilePaths) {
       if (!this->Patch->LoadFragments(p)) {
@@ -322,8 +323,7 @@
   if (!cpackWixExtraObjects)
     return;
 
-  std::vector<std::string> expandedExtraObjects =
-    cmExpandedList(cpackWixExtraObjects);
+  cmList expandedExtraObjects{ cpackWixExtraObjects };
 
   for (std::string const& obj : expandedExtraObjects) {
     stream << " " << QuotePath(obj);
@@ -681,10 +681,10 @@
   featureDefinitions.BeginElement("FeatureRef");
   featureDefinitions.AddAttribute("Id", featureId);
 
-  std::vector<std::string> cpackPackageExecutablesList;
+  cmList cpackPackageExecutablesList;
   cmValue cpackPackageExecutables = GetOption("CPACK_PACKAGE_EXECUTABLES");
   if (cpackPackageExecutables) {
-    cmExpandList(cpackPackageExecutables, cpackPackageExecutablesList);
+    cpackPackageExecutablesList.assign(cpackPackageExecutables);
     if (cpackPackageExecutablesList.size() % 2 != 0) {
       cmCPackLogger(
         cmCPackLog::LOG_ERROR,
@@ -695,10 +695,10 @@
     }
   }
 
-  std::vector<std::string> cpackPackageDesktopLinksList;
+  cmList cpackPackageDesktopLinksList;
   cmValue cpackPackageDesktopLinks = GetOption("CPACK_CREATE_DESKTOP_LINKS");
   if (cpackPackageDesktopLinks) {
-    cmExpandList(cpackPackageDesktopLinks, cpackPackageDesktopLinksList);
+    cpackPackageDesktopLinksList.assign(cpackPackageDesktopLinks);
   }
 
   AddDirectoryAndFileDefinitions(
@@ -1160,7 +1160,7 @@
   if (!variableContent)
     return;
 
-  std::vector<std::string> list = cmExpandedList(variableContent);
+  cmList list{ variableContent };
   extensions.insert(list.begin(), list.end());
 }
 
@@ -1172,7 +1172,7 @@
     return;
   }
 
-  std::vector<std::string> list = cmExpandedList(variableContent);
+  cmList list{ variableContent };
   for (std::string const& str : list) {
     auto pos = str.find('=');
     if (pos != std::string::npos) {
@@ -1200,7 +1200,7 @@
   if (!variableContent)
     return;
 
-  std::vector<std::string> list = cmExpandedList(variableContent);
+  cmList list{ variableContent };
 
   for (std::string const& i : list) {
     stream << " " << QuotePath(i);
diff --git a/Source/CPack/WiX/cmWIXAccessControlList.cxx b/Source/CPack/WiX/cmWIXAccessControlList.cxx
index 9685a7f..2261a66 100644
--- a/Source/CPack/WiX/cmWIXAccessControlList.cxx
+++ b/Source/CPack/WiX/cmWIXAccessControlList.cxx
@@ -19,10 +19,9 @@
 
 bool cmWIXAccessControlList::Apply()
 {
-  std::vector<std::string> entries;
-  this->InstalledFile.GetPropertyAsList("CPACK_WIX_ACL", entries);
+  auto entries = this->InstalledFile.GetPropertyAsList("CPACK_WIX_ACL");
 
-  for (std::string const& entry : entries) {
+  for (auto const& entry : entries) {
     this->CreatePermissionElement(entry);
   }
 
diff --git a/Source/CPack/WiX/cmWIXShortcut.cxx b/Source/CPack/WiX/cmWIXShortcut.cxx
index cd1988a..c3eb219 100644
--- a/Source/CPack/WiX/cmWIXShortcut.cxx
+++ b/Source/CPack/WiX/cmWIXShortcut.cxx
@@ -91,10 +91,9 @@
                                         std::string const& directoryId,
                                         cmInstalledFile const& installedFile)
 {
-  std::vector<std::string> list;
-  installedFile.GetPropertyAsList(propertyName, list);
+  auto list = installedFile.GetPropertyAsList(propertyName);
 
-  for (std::string const& label : list) {
+  for (auto const& label : list) {
     cmWIXShortcut shortcut;
     shortcut.label = label;
     shortcut.workingDirectoryId = directoryId;
diff --git a/Source/CPack/cmCPackBundleGenerator.cxx b/Source/CPack/cmCPackBundleGenerator.cxx
index b3d425a..7e6e473 100644
--- a/Source/CPack/cmCPackBundleGenerator.cxx
+++ b/Source/CPack/cmCPackBundleGenerator.cxx
@@ -6,6 +6,7 @@
 #include <vector>
 
 #include "cmCPackLog.h"
+#include "cmList.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmValue.h"
@@ -191,7 +192,7 @@
 
     cmValue sign_files = this->GetOption("CPACK_BUNDLE_APPLE_CODESIGN_FILES");
 
-    std::vector<std::string> relFiles = cmExpandedList(sign_files);
+    cmList relFiles{ sign_files };
 
     // sign the files supplied by the user, ie. frameworks.
     for (auto const& file : relFiles) {
diff --git a/Source/CPack/cmCPackDebGenerator.cxx b/Source/CPack/cmCPackDebGenerator.cxx
index 6ba28d1..34c56c9 100644
--- a/Source/CPack/cmCPackDebGenerator.cxx
+++ b/Source/CPack/cmCPackDebGenerator.cxx
@@ -20,6 +20,7 @@
 #include "cmCPackLog.h"
 #include "cmCryptoHash.h"
 #include "cmGeneratedFileStream.h"
+#include "cmList.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmValue.h"
@@ -427,8 +428,7 @@
     // default
     control_tar.ClearPermissions();
 
-    std::vector<std::string> controlExtraList =
-      cmExpandedList(this->ControlExtra);
+    cmList controlExtraList{ this->ControlExtra };
     for (std::string const& i : controlExtraList) {
       std::string filenamename = cmsys::SystemTools::GetFilenameName(i);
       std::string localcopy = this->WorkDir + "/" + filenamename;
diff --git a/Source/CPack/cmCPackDragNDropGenerator.cxx b/Source/CPack/cmCPackDragNDropGenerator.cxx
index 68e7ba3..768bfbe 100644
--- a/Source/CPack/cmCPackDragNDropGenerator.cxx
+++ b/Source/CPack/cmCPackDragNDropGenerator.cxx
@@ -19,6 +19,7 @@
 #include "cmCPackLog.h"
 #include "cmDuration.h"
 #include "cmGeneratedFileStream.h"
+#include "cmList.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmValue.h"
@@ -128,8 +129,7 @@
       return 0;
     }
 
-    std::vector<std::string> languages =
-      cmExpandedList(this->GetOption("CPACK_DMG_SLA_LANGUAGES"));
+    cmList languages{ this->GetOption("CPACK_DMG_SLA_LANGUAGES") };
     if (languages.empty()) {
       cmCPackLogger(cmCPackLog::LOG_ERROR,
                     "CPACK_DMG_SLA_LANGUAGES set but empty" << std::endl);
@@ -543,9 +543,9 @@
     std::string sla_xml =
       cmStrCat(this->GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/sla.xml");
 
-    std::vector<std::string> languages;
+    cmList languages;
     if (!oldStyle) {
-      cmExpandList(cpack_dmg_languages, languages);
+      languages.assign(cpack_dmg_languages);
     }
 
     std::vector<uint16_t> header_data;
@@ -574,7 +574,7 @@
 
       header_data.push_back(0);
       header_data.push_back(languages.size());
-      for (size_t i = 0; i < languages.size(); ++i) {
+      for (cmList::size_type i = 0; i < languages.size(); ++i) {
         CFStringRef language_cfstring = CFStringCreateWithCString(
           nullptr, languages[i].c_str(), kCFStringEncodingUTF8);
         CFStringRef iso_language =
diff --git a/Source/CPack/cmCPackExternalGenerator.cxx b/Source/CPack/cmCPackExternalGenerator.cxx
index 4c92592..8ba015c 100644
--- a/Source/CPack/cmCPackExternalGenerator.cxx
+++ b/Source/CPack/cmCPackExternalGenerator.cxx
@@ -15,8 +15,8 @@
 
 #include "cmCPackComponentGroup.h"
 #include "cmCPackLog.h"
+#include "cmList.h"
 #include "cmMakefile.h"
-#include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmValue.h"
 
@@ -79,7 +79,7 @@
 
     cmValue builtPackages = this->GetOption("CPACK_EXTERNAL_BUILT_PACKAGES");
     if (builtPackages) {
-      cmExpandList(builtPackages, this->packageFileNames, false);
+      cmExpandList(builtPackages, this->packageFileNames);
     }
   }
 
diff --git a/Source/CPack/cmCPackFreeBSDGenerator.cxx b/Source/CPack/cmCPackFreeBSDGenerator.cxx
index 162dfc7..0840e33 100644
--- a/Source/CPack/cmCPackFreeBSDGenerator.cxx
+++ b/Source/CPack/cmCPackFreeBSDGenerator.cxx
@@ -2,15 +2,6 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmCPackFreeBSDGenerator.h"
 
-#include "cmArchiveWrite.h"
-#include "cmCPackArchiveGenerator.h"
-#include "cmCPackLog.h"
-#include "cmGeneratedFileStream.h"
-#include "cmStringAlgorithms.h"
-#include "cmSystemTools.h"
-#include "cmWorkingDirectory.h"
-
-// Needed for ::open() and ::stat()
 #include <algorithm>
 #include <ostream>
 #include <utility>
@@ -21,6 +12,15 @@
 
 #include <sys/stat.h>
 
+#include "cmArchiveWrite.h"
+#include "cmCPackArchiveGenerator.h"
+#include "cmCPackLog.h"
+#include "cmGeneratedFileStream.h"
+#include "cmList.h"
+#include "cmStringAlgorithms.h"
+#include "cmSystemTools.h"
+#include "cmWorkingDirectory.h"
+
 // Suffix used to tell libpkg what compression to use
 static const char FreeBSDPackageCompression[] = "txz";
 static const char FreeBSDPackageSuffix_17[] = ".pkg";
@@ -263,7 +263,7 @@
 }
 
 // Look up variable; if no value is set, returns an empty string;
-// basically a wrapper that handles the NULL-ptr return from GetOption().
+// basically a wrapper that handles the nullptr return from GetOption().
 std::string cmCPackFreeBSDGenerator::var_lookup(const char* var_name)
 {
   cmValue pv = this->GetOption(var_name);
@@ -292,8 +292,7 @@
   manifest << ManifestKeyValue(
     "desc", var_lookup("CPACK_FREEBSD_PACKAGE_DESCRIPTION"));
   manifest << ManifestKeyValue("www", var_lookup("CPACK_FREEBSD_PACKAGE_WWW"));
-  std::vector<std::string> licenses =
-    cmExpandedList(var_lookup("CPACK_FREEBSD_PACKAGE_LICENSE"));
+  cmList licenses{ var_lookup("CPACK_FREEBSD_PACKAGE_LICENSE") };
   std::string licenselogic("single");
   if (licenses.empty()) {
     cmSystemTools::SetFatalErrorOccurred();
@@ -302,12 +301,10 @@
   }
   manifest << ManifestKeyValue("licenselogic", licenselogic);
   manifest << (ManifestKeyListValue("licenses") << licenses);
-  std::vector<std::string> categories =
-    cmExpandedList(var_lookup("CPACK_FREEBSD_PACKAGE_CATEGORIES"));
+  cmList categories{ var_lookup("CPACK_FREEBSD_PACKAGE_CATEGORIES") };
   manifest << (ManifestKeyListValue("categories") << categories);
   manifest << ManifestKeyValue("prefix", var_lookup("CMAKE_INSTALL_PREFIX"));
-  std::vector<std::string> deps =
-    cmExpandedList(var_lookup("CPACK_FREEBSD_PACKAGE_DEPS"));
+  cmList deps{ var_lookup("CPACK_FREEBSD_PACKAGE_DEPS") };
   if (!deps.empty()) {
     manifest << (ManifestKeyDepsValue("deps") << deps);
   }
@@ -402,7 +399,7 @@
     this->packageFileNames.emplace_back(actualPackage);
   }
 
-  if (!pkg_initialized() && pkg_init(NULL, NULL) != EPKG_OK) {
+  if (!pkg_initialized() && pkg_init(nullptr, nullptr) != EPKG_OK) {
     cmCPackLogger(cmCPackLog::LOG_ERROR,
                   "Can not initialize FreeBSD libpkg." << std::endl);
     return 0;
diff --git a/Source/CPack/cmCPackGenerator.cxx b/Source/CPack/cmCPackGenerator.cxx
index 2ac5b3d..afd85cd 100644
--- a/Source/CPack/cmCPackGenerator.cxx
+++ b/Source/CPack/cmCPackGenerator.cxx
@@ -19,6 +19,7 @@
 #include "cmFileTimes.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGlobalGenerator.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmState.h"
 #include "cmStateSnapshot.h"
@@ -216,8 +217,7 @@
   cmValue default_dir_install_permissions =
     this->GetOption("CPACK_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS");
   if (cmNonempty(default_dir_install_permissions)) {
-    std::vector<std::string> items =
-      cmExpandedList(default_dir_install_permissions);
+    cmList items{ default_dir_install_permissions };
     for (const auto& arg : items) {
       if (!cmFSPermissions::stringToModeT(arg, default_dir_mode_v)) {
         cmCPackLogger(cmCPackLog::LOG_ERROR,
@@ -266,7 +266,7 @@
   // Run pre-build actions
   cmValue preBuildScripts = this->GetOption("CPACK_PRE_BUILD_SCRIPTS");
   if (preBuildScripts) {
-    const auto scripts = cmExpandedList(preBuildScripts, false);
+    const cmList scripts{ preBuildScripts };
     for (const auto& script : scripts) {
       cmCPackLogger(cmCPackLog::LOG_OUTPUT,
                     "Executing pre-build script: " << script << std::endl);
@@ -296,8 +296,7 @@
     std::string tempInstallDirectoryEnv =
       cmStrCat("CMAKE_INSTALL_PREFIX=", tempInstallDirectory);
     cmSystemTools::PutEnv(tempInstallDirectoryEnv);
-    std::vector<std::string> installCommandsVector =
-      cmExpandedList(installCommands);
+    cmList installCommandsVector{ installCommands };
     for (std::string const& ic : installCommandsVector) {
       cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Execute: " << ic << std::endl);
       std::string output;
@@ -333,8 +332,7 @@
   std::vector<cmsys::RegularExpression> ignoreFilesRegex;
   cmValue cpackIgnoreFiles = this->GetOption("CPACK_IGNORE_FILES");
   if (cpackIgnoreFiles) {
-    std::vector<std::string> ignoreFilesRegexString =
-      cmExpandedList(cpackIgnoreFiles);
+    cmList ignoreFilesRegexString{ cpackIgnoreFiles };
     for (std::string const& ifr : ignoreFilesRegexString) {
       cmCPackLogger(cmCPackLog::LOG_VERBOSE,
                     "Create ignore files regex for: " << ifr << std::endl);
@@ -343,9 +341,8 @@
   }
   cmValue installDirectories = this->GetOption("CPACK_INSTALLED_DIRECTORIES");
   if (cmNonempty(installDirectories)) {
-    std::vector<std::string> installDirectoriesVector =
-      cmExpandedList(installDirectories);
-    if (installDirectoriesVector.size() % 2 != 0) {
+    cmList installDirectoriesList{ installDirectories };
+    if (installDirectoriesList.size() % 2 != 0) {
       cmCPackLogger(
         cmCPackLog::LOG_ERROR,
         "CPACK_INSTALLED_DIRECTORIES should contain pairs of <directory> "
@@ -355,10 +352,10 @@
           << std::endl);
       return 0;
     }
-    std::vector<std::string>::iterator it;
+    cmList::iterator it;
     const std::string& tempDir = tempInstallDirectory;
-    for (it = installDirectoriesVector.begin();
-         it != installDirectoriesVector.end(); ++it) {
+    for (it = installDirectoriesList.begin();
+         it != installDirectoriesList.end(); ++it) {
       std::vector<std::pair<std::string, std::string>> symlinkedFiles;
       cmCPackLogger(cmCPackLog::LOG_DEBUG, "Find files" << std::endl);
       cmsys::Glob gl;
@@ -485,7 +482,7 @@
   if (cmakeScripts && !cmakeScripts->empty()) {
     cmCPackLogger(cmCPackLog::LOG_OUTPUT,
                   "- Install scripts: " << cmakeScripts << std::endl);
-    std::vector<std::string> cmakeScriptsVector = cmExpandedList(cmakeScripts);
+    cmList cmakeScriptsVector{ cmakeScripts };
     for (std::string const& installScript : cmakeScriptsVector) {
 
       cmCPackLogger(cmCPackLog::LOG_OUTPUT,
@@ -549,14 +546,12 @@
                       << std::endl);
       return 0;
     }
-    std::vector<std::string> cmakeProjectsVector =
-      cmExpandedList(cmakeProjects);
-    std::vector<std::string>::iterator it;
-    for (it = cmakeProjectsVector.begin(); it != cmakeProjectsVector.end();
-         ++it) {
-      if (it + 1 == cmakeProjectsVector.end() ||
-          it + 2 == cmakeProjectsVector.end() ||
-          it + 3 == cmakeProjectsVector.end()) {
+    cmList cmakeProjectsList{ cmakeProjects };
+    cmList::iterator it;
+    for (it = cmakeProjectsList.begin(); it != cmakeProjectsList.end(); ++it) {
+      if (it + 1 == cmakeProjectsList.end() ||
+          it + 2 == cmakeProjectsList.end() ||
+          it + 3 == cmakeProjectsList.end()) {
         cmCPackLogger(
           cmCPackLog::LOG_ERROR,
           "Not enough items on list: CPACK_INSTALL_CMAKE_PROJECTS. "
@@ -578,7 +573,7 @@
       ++it;
       project.SubDirectory = *it;
 
-      std::vector<std::string> componentsVector;
+      cmList componentsList;
 
       bool componentInstall = false;
       /*
@@ -593,10 +588,9 @@
         std::string installTypesVar = "CPACK_" +
           cmSystemTools::UpperCase(project.Component) + "_INSTALL_TYPES";
         cmValue installTypes = this->GetOption(installTypesVar);
-        if (cmNonempty(installTypes)) {
-          std::vector<std::string> installTypesVector =
-            cmExpandedList(installTypes);
-          for (std::string const& installType : installTypesVector) {
+        if (!installTypes.IsEmpty()) {
+          cmList installTypesList{ installTypes };
+          for (std::string const& installType : installTypesList) {
             project.InstallationTypes.push_back(
               this->GetInstallationType(project.ProjectName, installType));
           }
@@ -606,23 +600,23 @@
         std::string componentsVar =
           "CPACK_COMPONENTS_" + cmSystemTools::UpperCase(project.Component);
         cmValue components = this->GetOption(componentsVar);
-        if (cmNonempty(components)) {
-          cmExpandList(components, componentsVector);
-          for (std::string const& comp : componentsVector) {
+        if (!components.IsEmpty()) {
+          componentsList.assign(components);
+          for (auto const& comp : componentsList) {
             project.Components.push_back(
               this->GetComponent(project.ProjectName, comp));
           }
           componentInstall = true;
         }
       }
-      if (componentsVector.empty()) {
-        componentsVector.push_back(project.Component);
+      if (componentsList.empty()) {
+        componentsList.push_back(project.Component);
       }
 
-      std::vector<std::string> buildConfigs;
+      cmList buildConfigs;
 
       // Try get configuration names given via `-C` CLI option
-      cmExpandList(this->GetOption("CPACK_BUILD_CONFIG"), buildConfigs);
+      buildConfigs.assign(this->GetOption("CPACK_BUILD_CONFIG"));
 
       // Remove duplicates
       std::sort(buildConfigs.begin(), buildConfigs.end());
@@ -661,7 +655,7 @@
                                             << buildConfig << ']'
                                             << std::endl);
         // Run the installation for each component
-        for (std::string const& component : componentsVector) {
+        for (std::string const& component : componentsList) {
           if (!this->InstallCMakeProject(
                 setDestDir, project.Directory, baseTempInstallDirectory,
                 default_dir_mode, component, componentInstall,
@@ -894,9 +888,8 @@
     mf.AddDefinition("CMAKE_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION", "1");
   }
 
-  std::vector<std::string> custom_variables;
-  this->MakefileMap->GetDefExpandList("CPACK_CUSTOM_INSTALL_VARIABLES",
-                                      custom_variables);
+  cmList custom_variables{ this->MakefileMap->GetDefinition(
+    "CPACK_CUSTOM_INSTALL_VARIABLES") };
 
   for (auto const& custom_variable : custom_variables) {
     std::string value;
@@ -1129,7 +1122,7 @@
     this->MakefileMap->AddDefinition("CPACK_PACKAGE_FILES",
                                      cmJoin(this->packageFileNames, ";"));
 
-    const auto scripts = cmExpandedList(postBuildScripts, false);
+    const cmList scripts{ postBuildScripts };
     for (const auto& script : scripts) {
       cmCPackLogger(cmCPackLog::LOG_OUTPUT,
                     "Executing post-build script: " << script << std::endl);
@@ -1595,9 +1588,8 @@
     // Determine the installation types.
     cmValue installTypes = this->GetOption(macroPrefix + "_INSTALL_TYPES");
     if (cmNonempty(installTypes)) {
-      std::vector<std::string> installTypesVector =
-        cmExpandedList(installTypes);
-      for (std::string const& installType : installTypesVector) {
+      cmList installTypesList{ installTypes };
+      for (auto const& installType : installTypesList) {
         component->InstallationTypes.push_back(
           this->GetInstallationType(projectName, installType));
       }
@@ -1606,8 +1598,8 @@
     // Determine the component dependencies.
     cmValue depends = this->GetOption(macroPrefix + "_DEPENDS");
     if (cmNonempty(depends)) {
-      std::vector<std::string> dependsVector = cmExpandedList(depends);
-      for (std::string const& depend : dependsVector) {
+      cmList dependsList{ depends };
+      for (auto const& depend : dependsList) {
         cmCPackComponent* child = this->GetComponent(projectName, depend);
         component->Dependencies.push_back(child);
         child->ReverseDependencies.push_back(component);
diff --git a/Source/CPack/cmCPackGeneratorFactory.cxx b/Source/CPack/cmCPackGeneratorFactory.cxx
index efb94b9..6ca48bf 100644
--- a/Source/CPack/cmCPackGeneratorFactory.cxx
+++ b/Source/CPack/cmCPackGeneratorFactory.cxx
@@ -13,6 +13,7 @@
 #include "cmCPackDebGenerator.h"
 #include "cmCPackExternalGenerator.h"
 #include "cmCPackGenerator.h"
+#include "cmCPackInnoSetupGenerator.h"
 #include "cmCPackLog.h"
 #include "cmCPackNSISGenerator.h"
 #include "cmCPackNuGetGenerator.h"
@@ -60,6 +61,10 @@
     this->RegisterGenerator("STGZ", "Self extracting Tar GZip compression",
                             cmCPackSTGZGenerator::CreateGenerator);
   }
+  if (cmCPackInnoSetupGenerator::CanGenerate()) {
+    this->RegisterGenerator("INNOSETUP", "Inno Setup packages",
+                            cmCPackInnoSetupGenerator::CreateGenerator);
+  }
   if (cmCPackNSISGenerator::CanGenerate()) {
     this->RegisterGenerator("NSIS", "Null Soft Installer",
                             cmCPackNSISGenerator::CreateGenerator);
diff --git a/Source/CPack/cmCPackInnoSetupGenerator.cxx b/Source/CPack/cmCPackInnoSetupGenerator.cxx
new file mode 100644
index 0000000..5d2c208
--- /dev/null
+++ b/Source/CPack/cmCPackInnoSetupGenerator.cxx
@@ -0,0 +1,1159 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include "cmCPackInnoSetupGenerator.h"
+
+#include <algorithm>
+#include <cctype>
+#include <cstdlib>
+#include <ostream>
+#include <stack>
+#include <utility>
+
+#include "cmsys/RegularExpression.hxx"
+
+#include "cmCPackComponentGroup.h"
+#include "cmCPackLog.h"
+#include "cmDuration.h"
+#include "cmGeneratedFileStream.h"
+#include "cmList.h"
+#include "cmStringAlgorithms.h"
+#include "cmSystemTools.h"
+
+cmCPackInnoSetupGenerator::cmCPackInnoSetupGenerator() = default;
+cmCPackInnoSetupGenerator::~cmCPackInnoSetupGenerator() = default;
+
+bool cmCPackInnoSetupGenerator::CanGenerate()
+{
+  // Inno Setup is only available for Windows
+#ifdef _WIN32
+  return true;
+#else
+  return false;
+#endif
+}
+
+int cmCPackInnoSetupGenerator::InitializeInternal()
+{
+  if (cmIsOn(GetOption("CPACK_INCLUDE_TOPLEVEL_DIRECTORY"))) {
+    cmCPackLogger(cmCPackLog::LOG_WARNING,
+                  "Inno Setup Generator cannot work with "
+                  "CPACK_INCLUDE_TOPLEVEL_DIRECTORY set. "
+                  "This option will be reset to 0 (for this generator only)."
+                    << std::endl);
+    SetOption("CPACK_INCLUDE_TOPLEVEL_DIRECTORY", nullptr);
+  }
+
+  std::vector<std::string> path;
+
+#ifdef _WIN32
+  path.push_back("C:\\Program Files (x86)\\Inno Setup 5");
+  path.push_back("C:\\Program Files (x86)\\Inno Setup 6");
+#endif
+
+  SetOptionIfNotSet("CPACK_INNOSETUP_EXECUTABLE", "ISCC");
+  const std::string& isccPath = cmSystemTools::FindProgram(
+    GetOption("CPACK_INNOSETUP_EXECUTABLE"), path, false);
+
+  if (isccPath.empty()) {
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+                  "Cannot find Inno Setup compiler ISCC: "
+                  "likely it is not installed, or not in your PATH"
+                    << std::endl);
+
+    return 0;
+  }
+
+  const std::string isccCmd = cmStrCat(QuotePath(isccPath), "/?");
+  cmCPackLogger(cmCPackLog::LOG_VERBOSE,
+                "Test Inno Setup version: " << isccCmd << std::endl);
+  std::string output;
+  cmSystemTools::RunSingleCommand(isccCmd, &output, &output, nullptr, nullptr,
+                                  this->GeneratorVerbose, cmDuration::zero());
+  cmsys::RegularExpression vRex("Inno Setup ([0-9]+)");
+  if (!vRex.find(output)) {
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+                  "Problem checking Inno Setup version with command: "
+                    << isccCmd << std::endl
+                    << "Have you downloaded Inno Setup from "
+                       "https://jrsoftware.org/isinfo.php?"
+                    << std::endl);
+    return 0;
+  }
+
+  const int isccVersion = atoi(vRex.match(1).c_str());
+  const int minIsccVersion = 6;
+  cmCPackLogger(cmCPackLog::LOG_DEBUG,
+                "Inno Setup Version: " << isccVersion << std::endl);
+
+  if (isccVersion < minIsccVersion) {
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+                  "CPack requires Inno Setup Version 6 or greater. "
+                  "Inno Setup found on the system was: "
+                    << isccVersion << std::endl);
+    return 0;
+  }
+
+  SetOption("CPACK_INSTALLER_PROGRAM", isccPath);
+
+  return this->Superclass::InitializeInternal();
+}
+
+int cmCPackInnoSetupGenerator::PackageFiles()
+{
+  // Includes
+  if (IsSet("CPACK_INNOSETUP_EXTRA_SCRIPTS")) {
+    const cmList extraScripts(GetOption("CPACK_INNOSETUP_EXTRA_SCRIPTS"));
+
+    for (const std::string& i : extraScripts) {
+      includeDirectives.push_back(cmStrCat(
+        "#include ", QuotePath(cmSystemTools::CollapseFullPath(i, toplevel))));
+    }
+  }
+
+  // [Languages] section
+  SetOptionIfNotSet("CPACK_INNOSETUP_LANGUAGES", "english");
+  const cmList languages(GetOption("CPACK_INNOSETUP_LANGUAGES"));
+  for (std::string i : languages) {
+    cmCPackInnoSetupKeyValuePairs params;
+
+    params["Name"] = Quote(i);
+
+    if (cmSystemTools::LowerCase(i) == "english") {
+      params["MessagesFile"] = "\"compiler:Default.isl\"";
+    } else {
+      i[0] = static_cast<char>(std::toupper(i[0]));
+      params["MessagesFile"] = cmStrCat("\"compiler:Languages\\", i, ".isl\"");
+    }
+
+    languageInstructions.push_back(ISKeyValueLine(params));
+  }
+
+  if (!Components.empty() && !ProcessComponents()) {
+    return false;
+  }
+
+  if (!(ProcessSetupSection() && ProcessFiles())) {
+    return false;
+  }
+
+  // [Code] section
+  if (IsSet("CPACK_INNOSETUP_CODE_FILES")) {
+    const cmList codeFiles(GetOption("CPACK_INNOSETUP_CODE_FILES"));
+
+    for (const std::string& i : codeFiles) {
+      codeIncludes.push_back(cmStrCat(
+        "#include ", QuotePath(cmSystemTools::CollapseFullPath(i, toplevel))));
+    }
+  }
+
+  return ConfigureISScript() && Compile();
+}
+
+bool cmCPackInnoSetupGenerator::ProcessSetupSection()
+{
+  if (!RequireOption("CPACK_PACKAGE_INSTALL_REGISTRY_KEY")) {
+    return false;
+  }
+  setupDirectives["AppId"] = GetOption("CPACK_PACKAGE_INSTALL_REGISTRY_KEY");
+
+  if (!RequireOption("CPACK_PACKAGE_NAME")) {
+    return false;
+  }
+  setupDirectives["AppName"] = GetOption("CPACK_PACKAGE_NAME");
+  setupDirectives["UninstallDisplayName"] = GetOption("CPACK_PACKAGE_NAME");
+
+  if (!RequireOption("CPACK_PACKAGE_VERSION")) {
+    return false;
+  }
+  setupDirectives["AppVersion"] = GetOption("CPACK_PACKAGE_VERSION");
+
+  if (!RequireOption("CPACK_PACKAGE_VENDOR")) {
+    return false;
+  }
+  setupDirectives["AppPublisher"] = GetOption("CPACK_PACKAGE_VENDOR");
+
+  if (IsSet("CPACK_PACKAGE_HOMEPAGE_URL")) {
+    setupDirectives["AppPublisherURL"] =
+      GetOption("CPACK_PACKAGE_HOMEPAGE_URL");
+    setupDirectives["AppSupportURL"] = GetOption("CPACK_PACKAGE_HOMEPAGE_URL");
+    setupDirectives["AppUpdatesURL"] = GetOption("CPACK_PACKAGE_HOMEPAGE_URL");
+  }
+
+  SetOptionIfNotSet("CPACK_INNOSETUP_IGNORE_LICENSE_PAGE", "OFF");
+  if (IsSet("CPACK_RESOURCE_FILE_LICENSE") &&
+      !GetOption("CPACK_INNOSETUP_IGNORE_LICENSE_PAGE").IsOn()) {
+    setupDirectives["LicenseFile"] = cmSystemTools::ConvertToWindowsOutputPath(
+      GetOption("CPACK_RESOURCE_FILE_LICENSE"));
+  }
+
+  SetOptionIfNotSet("CPACK_INNOSETUP_IGNORE_README_PAGE", "ON");
+  if (IsSet("CPACK_RESOURCE_FILE_README") &&
+      !GetOption("CPACK_INNOSETUP_IGNORE_README_PAGE").IsOn()) {
+    setupDirectives["InfoBeforeFile"] =
+      cmSystemTools::ConvertToWindowsOutputPath(
+        GetOption("CPACK_RESOURCE_FILE_README"));
+  }
+
+  SetOptionIfNotSet("CPACK_INNOSETUP_USE_MODERN_WIZARD", "OFF");
+  if (GetOption("CPACK_INNOSETUP_USE_MODERN_WIZARD").IsOn()) {
+    setupDirectives["WizardStyle"] = "modern";
+  } else {
+    setupDirectives["WizardStyle"] = "classic";
+    setupDirectives["WizardSmallImageFile"] =
+      "compiler:WizClassicSmallImage.bmp";
+    setupDirectives["WizardImageFile"] = "compiler:WizClassicImage.bmp";
+    setupDirectives["SetupIconFile"] = "compiler:SetupClassicIcon.ico";
+  }
+
+  if (IsSet("CPACK_INNOSETUP_ICON_FILE")) {
+    setupDirectives["SetupIconFile"] =
+      cmSystemTools::ConvertToWindowsOutputPath(
+        GetOption("CPACK_INNOSETUP_ICON_FILE"));
+  }
+
+  if (IsSet("CPACK_PACKAGE_ICON")) {
+    setupDirectives["WizardSmallImageFile"] =
+      cmSystemTools::ConvertToWindowsOutputPath(
+        GetOption("CPACK_PACKAGE_ICON"));
+  }
+
+  if (!RequireOption("CPACK_PACKAGE_INSTALL_DIRECTORY")) {
+    return false;
+  }
+  SetOptionIfNotSet("CPACK_INNOSETUP_INSTALL_ROOT", "{autopf}");
+  setupDirectives["DefaultDirName"] =
+    cmSystemTools::ConvertToWindowsOutputPath(
+      cmStrCat(GetOption("CPACK_INNOSETUP_INSTALL_ROOT"), '\\',
+               GetOption("CPACK_PACKAGE_INSTALL_DIRECTORY")));
+
+  SetOptionIfNotSet("CPACK_INNOSETUP_ALLOW_CUSTOM_DIRECTORY", "ON");
+  if (GetOption("CPACK_INNOSETUP_ALLOW_CUSTOM_DIRECTORY").IsOff()) {
+    setupDirectives["DisableDirPage"] = "yes";
+  }
+
+  SetOptionIfNotSet("CPACK_INNOSETUP_PROGRAM_MENU_FOLDER",
+                    GetOption("CPACK_PACKAGE_NAME"));
+  if (GetOption("CPACK_INNOSETUP_PROGRAM_MENU_FOLDER") == ".") {
+    setupDirectives["DisableProgramGroupPage"] = "yes";
+    toplevelProgramFolder = true;
+  } else {
+    setupDirectives["DefaultGroupName"] =
+      GetOption("CPACK_INNOSETUP_PROGRAM_MENU_FOLDER");
+    toplevelProgramFolder = false;
+  }
+
+  if (IsSet("CPACK_INNOSETUP_PASSWORD")) {
+    setupDirectives["Password"] = GetOption("CPACK_INNOSETUP_PASSWORD");
+    setupDirectives["Encryption"] = "yes";
+  }
+
+  /*
+   * These directives can only be modified using the
+   * CPACK_INNOSETUP_SETUP_<directive> variables
+   */
+  setupDirectives["ShowLanguageDialog"] = "auto";
+  setupDirectives["AllowNoIcons"] = "yes";
+  setupDirectives["Compression"] = "lzma";
+  setupDirectives["SolidCompression"] = "yes";
+
+  // Output file and directory
+  if (!RequireOption("CPACK_PACKAGE_FILE_NAME")) {
+    return false;
+  }
+  setupDirectives["OutputBaseFilename"] = GetOption("CPACK_PACKAGE_FILE_NAME");
+
+  if (!RequireOption("CPACK_TOPLEVEL_DIRECTORY")) {
+    return false;
+  }
+  setupDirectives["OutputDir"] = cmSystemTools::ConvertToWindowsOutputPath(
+    GetOption("CPACK_TOPLEVEL_DIRECTORY"));
+
+  setupDirectives["SourceDir"] =
+    cmSystemTools::ConvertToWindowsOutputPath(toplevel);
+
+  // Target architecture
+  if (!RequireOption("CPACK_INNOSETUP_ARCHITECTURE")) {
+    return false;
+  }
+
+  cmValue const architecture = GetOption("CPACK_INNOSETUP_ARCHITECTURE");
+  if (architecture != "x86" && architecture != "x64" &&
+      architecture != "arm64" && architecture != "ia64") {
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+                  "CPACK_INNOSETUP_ARCHITECTURE must be either x86, x64, "
+                  "arm64 or ia64"
+                    << std::endl);
+    return false;
+  }
+
+  // The following directives must not be set to target x86
+  if (architecture != "x86") {
+    setupDirectives["ArchitecturesAllowed"] = architecture;
+    setupDirectives["ArchitecturesInstallIn64BitMode"] = architecture;
+  }
+
+  /*
+   * Handle custom directives (they have higher priority than other variables,
+   * so they have to be processed after all other variables)
+   */
+  for (const std::string& i : GetOptions()) {
+    if (cmHasPrefix(i, "CPACK_INNOSETUP_SETUP_")) {
+      const std::string& directive =
+        i.substr(cmStrLen("CPACK_INNOSETUP_SETUP_"));
+      setupDirectives[directive] = GetOption(i);
+    }
+  }
+
+  return true;
+}
+
+bool cmCPackInnoSetupGenerator::ProcessFiles()
+{
+  std::map<std::string, std::string> customFileInstructions;
+  if (IsSet("CPACK_INNOSETUP_CUSTOM_INSTALL_INSTRUCTIONS")) {
+    const cmList instructions(
+      GetOption("CPACK_INNOSETUP_CUSTOM_INSTALL_INSTRUCTIONS"));
+    if (instructions.size() % 2 != 0) {
+      cmCPackLogger(cmCPackLog::LOG_ERROR,
+                    "CPACK_INNOSETUP_CUSTOM_INSTALL_INSTRUCTIONS should "
+                    "contain pairs of <path> and <instruction>"
+                      << std::endl);
+      return false;
+    }
+
+    for (auto it = instructions.begin(); it != instructions.end(); ++it) {
+      const std::string& key =
+        QuotePath(cmSystemTools::CollapseFullPath(*it, toplevel));
+      customFileInstructions[key] = *(++it);
+    }
+  }
+
+  const std::string& iconsPrefix =
+    toplevelProgramFolder ? "{autoprograms}\\" : "{group}\\";
+
+  std::map<std::string, std::string> icons;
+  if (IsSet("CPACK_PACKAGE_EXECUTABLES")) {
+    const cmList executables(GetOption("CPACK_PACKAGE_EXECUTABLES"));
+    if (executables.size() % 2 != 0) {
+      cmCPackLogger(cmCPackLog::LOG_ERROR,
+                    "CPACK_PACKAGE_EXECUTABLES should should contain pairs of "
+                    "<executable> and <text label>"
+                      << std::endl);
+      return false;
+    }
+
+    for (auto it = executables.begin(); it != executables.end(); ++it) {
+      const std::string& key = *it;
+      icons[key] = *(++it);
+    }
+  }
+
+  std::vector<std::string> desktopIcons;
+  if (IsSet("CPACK_CREATE_DESKTOP_LINKS")) {
+    cmExpandList(GetOption("CPACK_CREATE_DESKTOP_LINKS"), desktopIcons);
+  }
+
+  std::vector<std::string> runExecutables;
+  if (IsSet("CPACK_INNOSETUP_RUN_EXECUTABLES")) {
+    cmExpandList(GetOption("CPACK_INNOSETUP_RUN_EXECUTABLES"), runExecutables);
+  }
+
+  for (const std::string& i : files) {
+    cmCPackInnoSetupKeyValuePairs params;
+
+    std::string toplevelDirectory;
+    std::string outputDir;
+    cmCPackComponent* component = nullptr;
+    std::string componentParam;
+    if (!Components.empty()) {
+      const std::string& fileName = cmSystemTools::RelativePath(toplevel, i);
+      const std::string::size_type pos = fileName.find('/');
+
+      // Use the custom component install directory if we have one
+      if (pos != std::string::npos) {
+        const std::string& componentName = fileName.substr(0, pos);
+        component = &Components[componentName];
+
+        toplevelDirectory =
+          cmSystemTools::CollapseFullPath(componentName, toplevel);
+        outputDir = CustomComponentInstallDirectory(component);
+        componentParam =
+          CreateRecursiveComponentPath(component->Group, component->Name);
+
+        if (component->IsHidden && component->IsDisabledByDefault) {
+          continue;
+        }
+
+        if (component->IsHidden) {
+          componentParam.clear();
+        }
+      } else {
+        // Don't install component directories
+        continue;
+      }
+    } else {
+      toplevelDirectory = toplevel;
+      outputDir = "{app}";
+    }
+
+    if (!componentParam.empty()) {
+      params["Components"] = componentParam;
+    }
+
+    if (cmSystemTools::FileIsDirectory(i)) {
+      // Custom instructions replace the automatic generated instructions
+      if (customFileInstructions.count(QuotePath(i))) {
+        dirInstructions.push_back(customFileInstructions[QuotePath(i)]);
+      } else {
+        std::string destDir = cmSystemTools::ConvertToWindowsOutputPath(
+          cmStrCat(outputDir, '\\',
+                   cmSystemTools::RelativePath(toplevelDirectory, i)));
+        cmStripSuffixIfExists(destDir, '\\');
+
+        params["Name"] = QuotePath(destDir);
+
+        dirInstructions.push_back(ISKeyValueLine(params));
+      }
+    } else {
+      // Custom instructions replace the automatic generated instructions
+      if (customFileInstructions.count(QuotePath(i))) {
+        fileInstructions.push_back(customFileInstructions[QuotePath(i)]);
+      } else {
+        std::string destDir = cmSystemTools::ConvertToWindowsOutputPath(
+          cmStrCat(outputDir, '\\',
+                   cmSystemTools::GetParentDirectory(
+                     cmSystemTools::RelativePath(toplevelDirectory, i))));
+        cmStripSuffixIfExists(destDir, '\\');
+
+        params["DestDir"] = QuotePath(destDir);
+
+        if (component != nullptr && component->IsDownloaded) {
+          const std::string& archiveName =
+            cmSystemTools::GetFilenameWithoutLastExtension(
+              component->ArchiveFile);
+          const std::string& relativePath =
+            cmSystemTools::RelativePath(toplevelDirectory, i);
+
+          params["Source"] =
+            QuotePath(cmStrCat("{tmp}\\", archiveName, '\\', relativePath));
+          params["ExternalSize"] =
+            std::to_string(cmSystemTools::FileLength(i));
+          params["Flags"] = "external ignoreversion";
+          params["BeforeInstall"] =
+            cmStrCat("CPackExtractFile('", archiveName, "', '",
+                     cmRemoveQuotes(cmSystemTools::ConvertToWindowsOutputPath(
+                       relativePath)),
+                     "')");
+        } else {
+          params["Source"] = QuotePath(i);
+          params["Flags"] = "ignoreversion";
+        }
+
+        fileInstructions.push_back(ISKeyValueLine(params));
+
+        // Icon
+        const std::string& name =
+          cmSystemTools::GetFilenameWithoutLastExtension(i);
+        const std::string& extension =
+          cmSystemTools::GetFilenameLastExtension(i);
+        if ((extension == ".exe" || extension == ".com") && // only .exe, .com
+            icons.count(name)) {
+          cmCPackInnoSetupKeyValuePairs iconParams;
+
+          iconParams["Name"] = QuotePath(cmStrCat(iconsPrefix, icons[name]));
+          iconParams["Filename"] =
+            QuotePath(cmStrCat(destDir, '\\', name, extension));
+
+          if (!componentParam.empty()) {
+            iconParams["Components"] = componentParam;
+          }
+
+          iconInstructions.push_back(ISKeyValueLine(iconParams));
+
+          // Desktop icon
+          if (std::find(desktopIcons.begin(), desktopIcons.end(), name) !=
+              desktopIcons.end()) {
+            iconParams["Name"] =
+              QuotePath(cmStrCat("{autodesktop}\\", icons[name]));
+            iconParams["Tasks"] = "desktopicon";
+
+            if (!componentParam.empty() &&
+                std::find(desktopIconComponents.begin(),
+                          desktopIconComponents.end(),
+                          componentParam) == desktopIconComponents.end()) {
+              desktopIconComponents.push_back(componentParam);
+            }
+            iconInstructions.push_back(ISKeyValueLine(iconParams));
+          }
+
+          // [Run] section
+          if (std::find(runExecutables.begin(), runExecutables.end(), name) !=
+              runExecutables.end()) {
+            cmCPackInnoSetupKeyValuePairs runParams;
+
+            runParams["Filename"] = iconParams["Filename"];
+            runParams["Description"] = cmStrCat(
+              "\"{cm:LaunchProgram,", PrepareForConstant(icons[name]), "}\"");
+            runParams["Flags"] = "nowait postinstall skipifsilent";
+
+            if (!componentParam.empty()) {
+              runParams["Components"] = componentParam;
+            }
+
+            runInstructions.push_back(ISKeyValueLine(runParams));
+          }
+        }
+      }
+    }
+  }
+
+  // Additional icons
+  static cmsys::RegularExpression urlRegex(
+    "^(mailto:|(ftps?|https?|news)://).*$");
+
+  if (IsSet("CPACK_INNOSETUP_MENU_LINKS")) {
+    const cmList menuIcons(GetOption("CPACK_INNOSETUP_MENU_LINKS"));
+    if (menuIcons.size() % 2 != 0) {
+      cmCPackLogger(cmCPackLog::LOG_ERROR,
+                    "CPACK_INNOSETUP_MENU_LINKS should "
+                    "contain pairs of <shortcut target> and <shortcut label>"
+                      << std::endl);
+      return false;
+    }
+
+    for (auto it = menuIcons.begin(); it != menuIcons.end(); ++it) {
+      const std::string& target = *it;
+      const std::string& label = *(++it);
+      cmCPackInnoSetupKeyValuePairs params;
+
+      params["Name"] = QuotePath(cmStrCat(iconsPrefix, label));
+      if (urlRegex.find(target)) {
+        params["Filename"] = Quote(target);
+      } else {
+        std::string dir = "{app}";
+        std::string componentName;
+        for (const auto& i : Components) {
+          if (cmSystemTools::FileExists(cmSystemTools::CollapseFullPath(
+                cmStrCat(i.second.Name, '\\', target), toplevel))) {
+            dir = CustomComponentInstallDirectory(&i.second);
+            componentName =
+              CreateRecursiveComponentPath(i.second.Group, i.second.Name);
+
+            if (i.second.IsHidden && i.second.IsDisabledByDefault) {
+              goto continueOuterLoop;
+            } else if (i.second.IsHidden) {
+              componentName.clear();
+            }
+
+            break;
+          }
+        }
+
+        params["Filename"] = QuotePath(cmStrCat(dir, '\\', target));
+
+        if (!componentName.empty()) {
+          params["Components"] = componentName;
+        }
+      }
+
+      iconInstructions.push_back(ISKeyValueLine(params));
+    continueOuterLoop:;
+    }
+  }
+
+  SetOptionIfNotSet("CPACK_INNOSETUP_CREATE_UNINSTALL_LINK", "OFF");
+  if (GetOption("CPACK_INNOSETUP_CREATE_UNINSTALL_LINK").IsOn()) {
+    cmCPackInnoSetupKeyValuePairs params;
+
+    params["Name"] = QuotePath(
+      cmStrCat(iconsPrefix, "{cm:UninstallProgram,",
+               PrepareForConstant(GetOption("CPACK_PACKAGE_NAME")), '}'));
+    params["Filename"] = "\"{uninstallexe}\"";
+
+    iconInstructions.push_back(ISKeyValueLine(params));
+  }
+
+  return true;
+}
+
+bool cmCPackInnoSetupGenerator::ProcessComponents()
+{
+  codeIncludes.push_back("{ The following lines are required by CPack because "
+                         "this script uses components }");
+
+  // Installation types
+  bool noTypes = true;
+  std::vector<cmCPackInstallationType*> types(InstallationTypes.size());
+  for (auto& i : InstallationTypes) {
+    noTypes = false;
+    types[i.second.Index - 1] = &i.second;
+  }
+
+  std::vector<std::string> allTypes; // For required components
+  for (cmCPackInstallationType* i : types) {
+    cmCPackInnoSetupKeyValuePairs params;
+
+    params["Name"] = Quote(i->Name);
+    params["Description"] = Quote(i->DisplayName);
+
+    allTypes.push_back(i->Name);
+    typeInstructions.push_back(ISKeyValueLine(params));
+  }
+
+  if (!noTypes) {
+    // Inno Setup requires the "custom" type
+    cmCPackInnoSetupKeyValuePairs params;
+
+    params["Name"] = "\"custom\"";
+    params["Description"] = "\"{code:CPackGetCustomInstallationMessage}\"";
+    params["Flags"] = "iscustom";
+
+    allTypes.push_back("custom");
+    typeInstructions.push_back(ISKeyValueLine(params));
+  }
+
+  // Components
+  std::vector<cmCPackComponent*> downloadedComponents;
+  std::stack<cmCPackComponentGroup*> groups;
+  for (auto& i : Components) {
+    cmCPackInnoSetupKeyValuePairs params;
+    cmCPackComponent* component = &i.second;
+
+    if (component->IsHidden) {
+      continue;
+    }
+
+    CreateRecursiveComponentGroups(component->Group);
+
+    params["Name"] =
+      Quote(CreateRecursiveComponentPath(component->Group, component->Name));
+    params["Description"] = Quote(component->DisplayName);
+
+    if (component->IsRequired) {
+      params["Types"] = cmJoin(allTypes, " ");
+      params["Flags"] = "fixed";
+    } else if (!component->InstallationTypes.empty()) {
+      std::vector<std::string> installationTypes;
+
+      for (cmCPackInstallationType* j : component->InstallationTypes) {
+        installationTypes.push_back(j->Name);
+      }
+
+      params["Types"] = cmJoin(installationTypes, " ");
+    }
+
+    componentInstructions.push_back(ISKeyValueLine(params));
+
+    if (component->IsDownloaded) {
+      downloadedComponents.push_back(component);
+
+      if (component->ArchiveFile.empty()) {
+        // Compute the name of the archive.
+        if (!RequireOption("CPACK_TEMPORARY_DIRECTORY")) {
+          return false;
+        }
+
+        std::string packagesDir =
+          cmStrCat(GetOption("CPACK_TEMPORARY_DIRECTORY"), ".dummy");
+        component->ArchiveFile =
+          cmStrCat(cmSystemTools::GetFilenameWithoutLastExtension(packagesDir),
+                   '-', component->Name, ".zip");
+      } else if (!cmHasSuffix(component->ArchiveFile, ".zip")) {
+        component->ArchiveFile = cmStrCat(component->ArchiveFile, ".zip");
+      }
+    }
+  }
+
+  // Downloaded components
+  if (!downloadedComponents.empty()) {
+    // Create the directory for the upload area
+    cmValue userUploadDirectory = GetOption("CPACK_UPLOAD_DIRECTORY");
+    std::string uploadDirectory;
+    if (cmNonempty(userUploadDirectory)) {
+      uploadDirectory = *userUploadDirectory;
+    } else {
+      if (!RequireOption("CPACK_PACKAGE_DIRECTORY")) {
+        return false;
+      }
+
+      uploadDirectory =
+        cmStrCat(GetOption("CPACK_PACKAGE_DIRECTORY"), "/CPackUploads");
+    }
+
+    if (!cmSystemTools::FileExists(uploadDirectory)) {
+      if (!cmSystemTools::MakeDirectory(uploadDirectory)) {
+        cmCPackLogger(cmCPackLog::LOG_ERROR,
+                      "Unable to create Inno Setup upload directory "
+                        << uploadDirectory << std::endl);
+        return false;
+      }
+    }
+
+    if (!RequireOption("CPACK_DOWNLOAD_SITE")) {
+      return false;
+    }
+
+    SetOptionIfNotSet("CPACK_INNOSETUP_VERIFY_DOWNLOADS", "ON");
+    const bool verifyDownloads =
+      GetOption("CPACK_INNOSETUP_VERIFY_DOWNLOADS").IsOn();
+
+    const std::string& urlPrefix =
+      cmHasSuffix(GetOption("CPACK_DOWNLOAD_SITE").GetCStr(), '/')
+      ? GetOption("CPACK_DOWNLOAD_SITE")
+      : cmStrCat(GetOption("CPACK_DOWNLOAD_SITE"), '/');
+
+    std::vector<std::string> archiveUrls;
+    std::vector<std::string> archiveFiles;
+    std::vector<std::string> archiveHashes;
+    std::vector<std::string> archiveComponents;
+    for (cmCPackComponent* i : downloadedComponents) {
+      std::string hash;
+      if (!BuildDownloadedComponentArchive(
+            i, uploadDirectory, (verifyDownloads ? &hash : nullptr))) {
+        return false;
+      }
+
+      archiveUrls.push_back(Quote(cmStrCat(urlPrefix, i->ArchiveFile)));
+      archiveFiles.push_back(
+        Quote(cmSystemTools::GetFilenameWithoutLastExtension(i->ArchiveFile)));
+      archiveHashes.push_back(Quote(hash));
+      archiveComponents.push_back(
+        Quote(CreateRecursiveComponentPath(i->Group, i->Name)));
+    }
+
+    SetOption("CPACK_INNOSETUP_DOWNLOAD_COUNT_INTERNAL",
+              std::to_string(archiveFiles.size()));
+    SetOption("CPACK_INNOSETUP_DOWNLOAD_URLS_INTERNAL",
+              cmJoin(archiveUrls, ", "));
+    SetOption("CPACK_INNOSETUP_DOWNLOAD_ARCHIVES_INTERNAL",
+              cmJoin(archiveFiles, ", "));
+    SetOption("CPACK_INNOSETUP_DOWNLOAD_HASHES_INTERNAL",
+              cmJoin(archiveHashes, ", "));
+    SetOption("CPACK_INNOSETUP_DOWNLOAD_COMPONENTS_INTERNAL",
+              cmJoin(archiveComponents, ", "));
+
+    static const std::string& downloadLines =
+      "#define protected CPackDownloadCount "
+      "@CPACK_INNOSETUP_DOWNLOAD_COUNT_INTERNAL@\n"
+      "#dim protected CPackDownloadUrls[CPackDownloadCount] "
+      "{@CPACK_INNOSETUP_DOWNLOAD_URLS_INTERNAL@}\n"
+      "#dim protected CPackDownloadArchives[CPackDownloadCount] "
+      "{@CPACK_INNOSETUP_DOWNLOAD_ARCHIVES_INTERNAL@}\n"
+      "#dim protected CPackDownloadHashes[CPackDownloadCount] "
+      "{@CPACK_INNOSETUP_DOWNLOAD_HASHES_INTERNAL@}\n"
+      "#dim protected CPackDownloadComponents[CPackDownloadCount] "
+      "{@CPACK_INNOSETUP_DOWNLOAD_COMPONENTS_INTERNAL@}";
+
+    std::string output;
+    if (!ConfigureString(downloadLines, output)) {
+      return false;
+    }
+    codeIncludes.push_back(output);
+  }
+
+  // Add the required script
+  const std::string& componentsScriptTemplate =
+    FindTemplate("ISComponents.pas");
+  if (componentsScriptTemplate.empty()) {
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+                  "Could not find additional Inno Setup script file."
+                    << std::endl);
+    return false;
+  }
+
+  codeIncludes.push_back("#include " + QuotePath(componentsScriptTemplate) +
+                         "\n");
+
+  return true;
+}
+
+bool cmCPackInnoSetupGenerator::ConfigureISScript()
+{
+  const std::string& isScriptTemplate = FindTemplate("ISScript.template.in");
+  const std::string& isScriptFile =
+    cmStrCat(GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/ISScript.iss");
+
+  if (isScriptTemplate.empty()) {
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+                  "Could not find Inno Setup installer template file."
+                    << std::endl);
+    return false;
+  }
+
+  // Create internal variables
+  std::vector<std::string> setupSection;
+  for (const auto& i : setupDirectives) {
+    setupSection.push_back(cmStrCat(i.first, '=', TranslateBool(i.second)));
+  }
+
+  // Also create comments if the sections are empty
+  const std::string& defaultMessage =
+    "; CPack didn't find any entries for this section";
+
+  if (IsSet("CPACK_CREATE_DESKTOP_LINKS") &&
+      !GetOption("CPACK_CREATE_DESKTOP_LINKS").Get()->empty()) {
+    cmCPackInnoSetupKeyValuePairs tasks;
+    tasks["Name"] = "\"desktopicon\"";
+    tasks["Description"] = "\"{cm:CreateDesktopIcon}\"";
+    tasks["GroupDescription"] = "\"{cm:AdditionalIcons}\"";
+    tasks["Flags"] = "unchecked";
+
+    if (!desktopIconComponents.empty()) {
+      tasks["Components"] = cmJoin(desktopIconComponents, " ");
+    }
+
+    SetOption("CPACK_INNOSETUP_TASKS_INTERNAL", ISKeyValueLine(tasks));
+  } else {
+    SetOption("CPACK_INNOSETUP_TASKS_INTERNAL", defaultMessage);
+  }
+
+  SetOption("CPACK_INNOSETUP_INCLUDES_INTERNAL",
+            includeDirectives.empty() ? "; No extra script files specified"
+                                      : cmJoin(includeDirectives, "\n"));
+  SetOption("CPACK_INNOSETUP_SETUP_INTERNAL",
+            setupSection.empty() ? defaultMessage
+                                 : cmJoin(setupSection, "\n"));
+  SetOption("CPACK_INNOSETUP_LANGUAGES_INTERNAL",
+            languageInstructions.empty() ? defaultMessage
+                                         : cmJoin(languageInstructions, "\n"));
+  SetOption("CPACK_INNOSETUP_DIRS_INTERNAL",
+            dirInstructions.empty() ? defaultMessage
+                                    : cmJoin(dirInstructions, "\n"));
+  SetOption("CPACK_INNOSETUP_FILES_INTERNAL",
+            fileInstructions.empty() ? defaultMessage
+                                     : cmJoin(fileInstructions, "\n"));
+  SetOption("CPACK_INNOSETUP_TYPES_INTERNAL",
+            typeInstructions.empty() ? defaultMessage
+                                     : cmJoin(typeInstructions, "\n"));
+  SetOption("CPACK_INNOSETUP_COMPONENTS_INTERNAL",
+            componentInstructions.empty()
+              ? defaultMessage
+              : cmJoin(componentInstructions, "\n"));
+  SetOption("CPACK_INNOSETUP_ICONS_INTERNAL",
+            iconInstructions.empty() ? defaultMessage
+                                     : cmJoin(iconInstructions, "\n"));
+  SetOption("CPACK_INNOSETUP_RUN_INTERNAL",
+            runInstructions.empty() ? defaultMessage
+                                    : cmJoin(runInstructions, "\n"));
+  SetOption("CPACK_INNOSETUP_CODE_INTERNAL",
+            codeIncludes.empty() ? "{ No extra code files specified }"
+                                 : cmJoin(codeIncludes, "\n"));
+
+  cmCPackLogger(cmCPackLog::LOG_VERBOSE,
+                "Configure file: " << isScriptTemplate << " to "
+                                   << isScriptFile << std::endl);
+
+  return ConfigureFile(isScriptTemplate, isScriptFile);
+}
+
+bool cmCPackInnoSetupGenerator::Compile()
+{
+  const std::string& isScriptFile =
+    cmStrCat(GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/ISScript.iss");
+  const std::string& isccLogFile =
+    cmStrCat(GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/ISCCOutput.log");
+
+  std::vector<std::string> isccArgs;
+
+  // Custom defines
+  for (const std::string& i : GetOptions()) {
+    if (cmHasPrefix(i, "CPACK_INNOSETUP_DEFINE_")) {
+      const std::string& name = i.substr(cmStrLen("CPACK_INNOSETUP_DEFINE_"));
+      isccArgs.push_back(
+        cmStrCat("\"/D", name, '=', TranslateBool(GetOption(i)), '"'));
+    }
+  }
+
+  if (IsSet("CPACK_INNOSETUP_EXECUTABLE_ARGUMENTS")) {
+    const cmList args(GetOption("CPACK_INNOSETUP_EXECUTABLE_ARGUMENTS"));
+
+    isccArgs.insert(isccArgs.end(), args.begin(), args.end());
+  }
+
+  const std::string& isccCmd =
+    cmStrCat(QuotePath(GetOption("CPACK_INSTALLER_PROGRAM")), ' ',
+             cmJoin(isccArgs, " "), ' ', QuotePath(isScriptFile));
+
+  cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Execute: " << isccCmd << std::endl);
+
+  std::string output;
+  int retVal = 1;
+  const bool res = cmSystemTools::RunSingleCommand(
+    isccCmd, &output, &output, &retVal, nullptr, this->GeneratorVerbose,
+    cmDuration::zero());
+
+  if (!res || retVal) {
+    cmGeneratedFileStream ofs(isccLogFile);
+    ofs << "# Run command: " << isccCmd << std::endl
+        << "# Output:" << std::endl
+        << output << std::endl;
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+                  "Problem running ISCC. Please check "
+                    << isccLogFile << " for errors." << std::endl);
+    return false;
+  }
+
+  return true;
+}
+
+bool cmCPackInnoSetupGenerator::BuildDownloadedComponentArchive(
+  cmCPackComponent* component, const std::string& uploadDirectory,
+  std::string* hash)
+{
+  // Remove the old archive, if one exists
+  const std::string& archiveFile =
+    uploadDirectory + '/' + component->ArchiveFile;
+  cmCPackLogger(cmCPackLog::LOG_OUTPUT,
+                "-   Building downloaded component archive: " << archiveFile
+                                                              << std::endl);
+  if (cmSystemTools::FileExists(archiveFile, true)) {
+    if (!cmSystemTools::RemoveFile(archiveFile)) {
+      cmCPackLogger(cmCPackLog::LOG_ERROR,
+                    "Unable to remove archive file " << archiveFile
+                                                     << std::endl);
+      return false;
+    }
+  }
+
+  // Find a ZIP program
+  if (!IsSet("ZIP_EXECUTABLE")) {
+    ReadListFile("Internal/CPack/CPackZIP.cmake");
+
+    if (!IsSet("ZIP_EXECUTABLE")) {
+      cmCPackLogger(cmCPackLog::LOG_ERROR,
+                    "Unable to find ZIP program" << std::endl);
+      return false;
+    }
+  }
+
+  if (!RequireOption("CPACK_TOPLEVEL_DIRECTORY") ||
+      !RequireOption("CPACK_TEMPORARY_DIRECTORY") ||
+      !RequireOption("CPACK_ZIP_NEED_QUOTES") ||
+      !RequireOption("CPACK_ZIP_COMMAND")) {
+    return false;
+  }
+
+  // The directory where this component's files reside
+  const std::string& dirName =
+    cmStrCat(GetOption("CPACK_TEMPORARY_DIRECTORY"), '/', component->Name);
+
+  // Build the list of files to go into this archive
+  const std::string& zipListFileName =
+    cmStrCat(GetOption("CPACK_TEMPORARY_DIRECTORY"), "/winZip.filelist");
+  const bool needQuotesInFile = cmIsOn(GetOption("CPACK_ZIP_NEED_QUOTES"));
+  { // the scope is needed for cmGeneratedFileStream
+    cmGeneratedFileStream out(zipListFileName);
+    for (const std::string& i : component->Files) {
+      out << (needQuotesInFile ? Quote(i) : i) << std::endl;
+    }
+  }
+
+  // Build the archive in the upload area
+  std::string cmd = GetOption("CPACK_ZIP_COMMAND");
+  cmsys::SystemTools::ReplaceString(cmd, "<ARCHIVE>", archiveFile.c_str());
+  cmsys::SystemTools::ReplaceString(cmd, "<FILELIST>",
+                                    zipListFileName.c_str());
+  std::string output;
+  int retVal = -1;
+  const bool res = cmSystemTools::RunSingleCommand(
+    cmd, &output, &output, &retVal, dirName.c_str(), this->GeneratorVerbose,
+    cmDuration::zero());
+  if (!res || retVal) {
+    std::string tmpFile =
+      cmStrCat(GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/CompressZip.log");
+    cmGeneratedFileStream ofs(tmpFile);
+    ofs << "# Run command: " << cmd << std::endl
+        << "# Output:" << std::endl
+        << output << std::endl;
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+                  "Problem running zip command: " << cmd << std::endl
+                                                  << "Please check " << tmpFile
+                                                  << " for errors"
+                                                  << std::endl);
+    return false;
+  }
+
+  // Try to get the SHA256 hash of the archive file
+  if (hash == nullptr) {
+    return true;
+  }
+
+#ifdef _WIN32
+  const std::string& hashCmd =
+    cmStrCat("certutil -hashfile ", QuotePath(archiveFile), " SHA256");
+
+  std::string hashOutput;
+  int hashRetVal = -1;
+  const bool hashRes = cmSystemTools::RunSingleCommand(
+    hashCmd, &hashOutput, &hashOutput, &hashRetVal, nullptr,
+    this->GeneratorVerbose, cmDuration::zero());
+  if (!hashRes || hashRetVal) {
+    cmCPackLogger(cmCPackLog::LOG_WARNING,
+                  "Problem running certutil command: " << hashCmd
+                                                       << std::endl);
+  }
+  *hash = cmTrimWhitespace(cmTokenize(hashOutput, "\n").at(1));
+
+  if (hash->length() != 64) {
+    cmCPackLogger(cmCPackLog::LOG_WARNING,
+                  "Problem parsing certutil output of command: " << hashCmd
+                                                                 << std::endl);
+    hash->clear();
+  }
+#endif
+
+  return true;
+}
+
+cmValue cmCPackInnoSetupGenerator::RequireOption(const std::string& key)
+{
+  cmValue value = GetOption(key);
+
+  if (!value) {
+    cmCPackLogger(cmCPackLog::LOG_ERROR,
+                  "Required variable " << key << " not set" << std::endl);
+  }
+
+  return value;
+}
+
+std::string cmCPackInnoSetupGenerator::CustomComponentInstallDirectory(
+  const cmCPackComponent* component)
+{
+  cmValue outputDir = GetOption(
+    cmStrCat("CPACK_INNOSETUP_", component->Name, "_INSTALL_DIRECTORY"));
+  if (outputDir) {
+    std::string destDir = cmSystemTools::ConvertToWindowsOutputPath(outputDir);
+    cmStripSuffixIfExists(destDir, '\\');
+
+    /*
+     * Add a dir instruction for the custom directory
+     * (only once and not for Inno Setup constants ending with '}')
+     */
+    static std::vector<std::string> customDirectories;
+    if (!cmHasSuffix(destDir, '}') &&
+        std::find(customDirectories.begin(), customDirectories.end(),
+                  component->Name) == customDirectories.end()) {
+      cmCPackInnoSetupKeyValuePairs params;
+      params["Name"] = QuotePath(destDir);
+      params["Components"] =
+        CreateRecursiveComponentPath(component->Group, component->Name);
+
+      dirInstructions.push_back(ISKeyValueLine(params));
+      customDirectories.push_back(component->Name);
+    }
+    return destDir;
+  }
+
+  return "{app}";
+}
+
+std::string cmCPackInnoSetupGenerator::TranslateBool(const std::string& value)
+{
+  if (value.empty()) {
+    return value;
+  }
+
+  SetOptionIfNotSet("CPACK_INNOSETUP_USE_CMAKE_BOOL_FORMAT", "ON");
+  if (GetOption("CPACK_INNOSETUP_USE_CMAKE_BOOL_FORMAT").IsOn()) {
+    if (cmIsOn(value)) {
+      return "yes";
+    }
+    if (cmIsOff(value)) {
+      return "no";
+    }
+  }
+
+  return value;
+}
+
+std::string cmCPackInnoSetupGenerator::ISKeyValueLine(
+  const cmCPackInnoSetupKeyValuePairs& params)
+{
+  /*
+   * To simplify readability of the generated code, the keys are sorted.
+   * Unknown keys are ignored to avoid errors during compilation.
+   */
+  static const char* const availableKeys[] = {
+    "Source",       "DestDir",          "Name",         "Filename",
+    "Description",  "GroupDescription", "MessagesFile", "Types",
+    "ExternalSize", "BeforeInstall",    "Flags",        "Components",
+    "Tasks"
+  };
+
+  std::vector<std::string> keys;
+  for (const char* i : availableKeys) {
+    if (params.count(i)) {
+      keys.push_back(cmStrCat(i, ": ", params.at(i)));
+    }
+  }
+
+  return cmJoin(keys, "; ");
+}
+
+std::string cmCPackInnoSetupGenerator::CreateRecursiveComponentPath(
+  cmCPackComponentGroup* group, const std::string& path)
+{
+  if (group == nullptr) {
+    return path;
+  }
+
+  const std::string& newPath =
+    path.empty() ? group->Name : cmStrCat(group->Name, '\\', path);
+  return CreateRecursiveComponentPath(group->ParentGroup, newPath);
+}
+
+void cmCPackInnoSetupGenerator::CreateRecursiveComponentGroups(
+  cmCPackComponentGroup* group)
+{
+  if (group == nullptr) {
+    return;
+  }
+
+  CreateRecursiveComponentGroups(group->ParentGroup);
+
+  static std::vector<cmCPackComponentGroup*> processedGroups;
+  if (std::find(processedGroups.begin(), processedGroups.end(), group) ==
+      processedGroups.end()) {
+    processedGroups.push_back(group);
+
+    cmCPackInnoSetupKeyValuePairs params;
+
+    params["Name"] = Quote(CreateRecursiveComponentPath(group));
+    params["Description"] = Quote(group->DisplayName);
+
+    componentInstructions.push_back(ISKeyValueLine(params));
+  }
+}
+
+std::string cmCPackInnoSetupGenerator::Quote(const std::string& string)
+{
+  if (cmHasPrefix(string, '"') && cmHasSuffix(string, '"')) {
+    return Quote(string.substr(1, string.length() - 2));
+  }
+
+  // Double quote syntax
+  std::string nString = string;
+  cmSystemTools::ReplaceString(nString, "\"", "\"\"");
+  return cmStrCat('"', nString, '"');
+}
+
+std::string cmCPackInnoSetupGenerator::QuotePath(const std::string& path)
+{
+  return Quote(cmSystemTools::ConvertToWindowsOutputPath(path));
+}
+
+std::string cmCPackInnoSetupGenerator::PrepareForConstant(
+  const std::string& string)
+{
+  std::string nString = string;
+
+  cmSystemTools::ReplaceString(nString, "%", "%25"); // First replacement!
+  cmSystemTools::ReplaceString(nString, "\"", "%22");
+  cmSystemTools::ReplaceString(nString, ",", "%2c");
+  cmSystemTools::ReplaceString(nString, "|", "%7c");
+  cmSystemTools::ReplaceString(nString, "}", "%7d");
+
+  return nString;
+}
diff --git a/Source/CPack/cmCPackInnoSetupGenerator.h b/Source/CPack/cmCPackInnoSetupGenerator.h
new file mode 100644
index 0000000..342f227
--- /dev/null
+++ b/Source/CPack/cmCPackInnoSetupGenerator.h
@@ -0,0 +1,116 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+file Copyright.txt or https://cmake.org/licensing for details. */
+
+#pragma once
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "cmCPackGenerator.h"
+#include "cmValue.h"
+
+using cmCPackInnoSetupKeyValuePairs = std::map<std::string, std::string>;
+
+class cmCPackComponentGroup;
+class cmCPackComponent;
+
+/** \class cmCPackInnoSetupGenerator
+ * \brief A generator for Inno Setup
+ *
+ * https://jrsoftware.org/isinfo.php
+ */
+class cmCPackInnoSetupGenerator : public cmCPackGenerator
+{
+public:
+  cmCPackTypeMacro(cmCPackInnoSetupGenerator, cmCPackGenerator);
+
+  /**
+   * Construct generator
+   */
+  cmCPackInnoSetupGenerator();
+  ~cmCPackInnoSetupGenerator() override;
+
+  static bool CanGenerate();
+
+protected:
+  int InitializeInternal() override;
+  int PackageFiles() override;
+
+  inline const char* GetOutputExtension() override { return ".exe"; }
+
+  inline cmCPackGenerator::CPackSetDestdirSupport SupportsSetDestdir()
+    const override
+  {
+    return cmCPackGenerator::SETDESTDIR_UNSUPPORTED;
+  }
+
+  inline bool SupportsAbsoluteDestination() const override { return false; }
+  inline bool SupportsComponentInstallation() const override { return true; }
+
+private:
+  bool ProcessSetupSection();
+  bool ProcessFiles();
+  bool ProcessComponents();
+
+  bool ConfigureISScript();
+  bool Compile();
+
+  bool BuildDownloadedComponentArchive(cmCPackComponent* component,
+                                       const std::string& uploadDirectory,
+                                       std::string* hash);
+
+  /**
+   * Returns the option's value or an empty string if the option isn't set.
+   */
+  cmValue RequireOption(const std::string& key);
+
+  std::string CustomComponentInstallDirectory(
+    const cmCPackComponent* component);
+
+  /**
+   * Translates boolean expressions into "yes" or "no", as required in
+   * Inno Setup (only if "CPACK_INNOSETUP_USE_CMAKE_BOOL_FORMAT" is on).
+   */
+  std::string TranslateBool(const std::string& value);
+
+  /**
+   * Creates a typical line of key and value pairs using the given map.
+   *
+   * (e.g.: Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}";
+   * GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked)
+   */
+  std::string ISKeyValueLine(const cmCPackInnoSetupKeyValuePairs& params);
+
+  std::string CreateRecursiveComponentPath(cmCPackComponentGroup* group,
+                                           const std::string& path = "");
+
+  void CreateRecursiveComponentGroups(cmCPackComponentGroup* group);
+
+  /**
+   * These functions add quotes if the given value hasn't already quotes.
+   * Paths are converted into the format used by Windows before.
+   */
+  std::string Quote(const std::string& string);
+  std::string QuotePath(const std::string& path);
+
+  /**
+   * This function replaces the following 5 characters with their %-encoding:
+   * '|'  '}'  ','  '%'  '"'
+   * Required for Inno Setup constants like {cm:...}
+   */
+  std::string PrepareForConstant(const std::string& string);
+
+  std::vector<std::string> includeDirectives;
+  cmCPackInnoSetupKeyValuePairs setupDirectives;
+  bool toplevelProgramFolder;
+  std::vector<std::string> languageInstructions;
+  std::vector<std::string> fileInstructions;
+  std::vector<std::string> dirInstructions;
+  std::vector<std::string> typeInstructions;
+  std::vector<std::string> componentInstructions;
+  std::vector<std::string> iconInstructions;
+  std::vector<std::string> desktopIconComponents;
+  std::vector<std::string> runInstructions;
+  std::vector<std::string> codeIncludes;
+};
diff --git a/Source/CPack/cmCPackNSISGenerator.cxx b/Source/CPack/cmCPackNSISGenerator.cxx
index d7119c5..7749b29 100644
--- a/Source/CPack/cmCPackNSISGenerator.cxx
+++ b/Source/CPack/cmCPackNSISGenerator.cxx
@@ -19,6 +19,7 @@
 #include "cmCPackLog.h"
 #include "cmDuration.h"
 #include "cmGeneratedFileStream.h"
+#include "cmList.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmValue.h"
@@ -245,8 +246,7 @@
   std::string nsisPreArguments;
   if (cmValue nsisArguments =
         this->GetOption("CPACK_NSIS_EXECUTABLE_PRE_ARGUMENTS")) {
-    std::vector<std::string> expandedArguments;
-    cmExpandList(nsisArguments, expandedArguments);
+    cmList expandedArguments{ nsisArguments };
 
     for (auto& arg : expandedArguments) {
       if (!cmHasPrefix(arg, NSIS_OPT)) {
@@ -259,8 +259,7 @@
   std::string nsisPostArguments;
   if (cmValue nsisArguments =
         this->GetOption("CPACK_NSIS_EXECUTABLE_POST_ARGUMENTS")) {
-    std::vector<std::string> expandedArguments;
-    cmExpandList(nsisArguments, expandedArguments);
+    cmList expandedArguments{ nsisArguments };
     for (auto& arg : expandedArguments) {
       if (!cmHasPrefix(arg, NSIS_OPT)) {
         nsisPostArguments = cmStrCat(nsisPostArguments, NSIS_OPT);
@@ -545,14 +544,14 @@
     this->GetOption("CPACK_CREATE_DESKTOP_LINKS");
   cmValue cpackNsisExecutablesDirectory =
     this->GetOption("CPACK_NSIS_EXECUTABLES_DIRECTORY");
-  std::vector<std::string> cpackPackageDesktopLinksVector;
+  cmList cpackPackageDesktopLinksList;
   if (cpackPackageDeskTopLinks) {
     cmCPackLogger(cmCPackLog::LOG_DEBUG,
                   "CPACK_CREATE_DESKTOP_LINKS: " << cpackPackageDeskTopLinks
                                                  << std::endl);
 
-    cmExpandList(cpackPackageDeskTopLinks, cpackPackageDesktopLinksVector);
-    for (std::string const& cpdl : cpackPackageDesktopLinksVector) {
+    cpackPackageDesktopLinksList.assign(cpackPackageDeskTopLinks);
+    for (std::string const& cpdl : cpackPackageDesktopLinksList) {
       cmCPackLogger(cmCPackLog::LOG_DEBUG,
                     "CPACK_CREATE_DESKTOP_LINKS: " << cpdl << std::endl);
     }
@@ -569,9 +568,8 @@
     cmCPackLogger(cmCPackLog::LOG_DEBUG,
                   "The cpackPackageExecutables: " << cpackPackageExecutables
                                                   << "." << std::endl);
-    std::vector<std::string> cpackPackageExecutablesVector =
-      cmExpandedList(cpackPackageExecutables);
-    if (cpackPackageExecutablesVector.size() % 2 != 0) {
+    cmList cpackPackageExecutablesList{ cpackPackageExecutables };
+    if (cpackPackageExecutablesList.size() % 2 != 0) {
       cmCPackLogger(
         cmCPackLog::LOG_ERROR,
         "CPACK_PACKAGE_EXECUTABLES should contain pairs of <executable> and "
@@ -579,9 +577,9 @@
           << std::endl);
       return 0;
     }
-    std::vector<std::string>::iterator it;
-    for (it = cpackPackageExecutablesVector.begin();
-         it != cpackPackageExecutablesVector.end(); ++it) {
+    cmList::iterator it;
+    for (it = cpackPackageExecutablesList.begin();
+         it != cpackPackageExecutablesList.end(); ++it) {
       std::string execName = *it;
       ++it;
       std::string linkName = *it;
@@ -592,7 +590,7 @@
                 << ".lnk\"" << std::endl;
       // see if CPACK_CREATE_DESKTOP_LINK_ExeName is on
       // if so add a desktop link
-      if (cm::contains(cpackPackageDesktopLinksVector, execName)) {
+      if (cm::contains(cpackPackageDesktopLinksList, execName)) {
         str << "  StrCmp \"$INSTALL_DESKTOP\" \"1\" 0 +2\n";
         str << "    CreateShortCut \"$DESKTOP\\" << linkName
             << R"(.lnk" "$INSTDIR\)" << cpackNsisExecutablesDirectory << "\\"
@@ -622,9 +620,8 @@
   }
   cmCPackLogger(cmCPackLog::LOG_DEBUG,
                 "The cpackMenuLinks: " << cpackMenuLinks << "." << std::endl);
-  std::vector<std::string> cpackMenuLinksVector =
-    cmExpandedList(cpackMenuLinks);
-  if (cpackMenuLinksVector.size() % 2 != 0) {
+  cmList cpackMenuLinksList{ cpackMenuLinks };
+  if (cpackMenuLinksList.size() % 2 != 0) {
     cmCPackLogger(
       cmCPackLog::LOG_ERROR,
       "CPACK_NSIS_MENU_LINKS should contain pairs of <shortcut target> and "
@@ -636,9 +633,8 @@
   static cmsys::RegularExpression urlRegex(
     "^(mailto:|(ftps?|https?|news)://).*$");
 
-  std::vector<std::string>::iterator it;
-  for (it = cpackMenuLinksVector.begin(); it != cpackMenuLinksVector.end();
-       ++it) {
+  cmList::iterator it;
+  for (it = cpackMenuLinksList.begin(); it != cpackMenuLinksList.end(); ++it) {
     std::string sourceName = *it;
     const bool url = urlRegex.find(sourceName);
 
diff --git a/Source/CPack/cpack.cxx b/Source/CPack/cpack.cxx
index 2257118..90716e6 100644
--- a/Source/CPack/cpack.cxx
+++ b/Source/CPack/cpack.cxx
@@ -28,6 +28,8 @@
 #include "cmDocumentation.h"
 #include "cmDocumentationEntry.h"
 #include "cmGlobalGenerator.h"
+#include "cmJSONState.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmState.h"
 #include "cmStateSnapshot.h"
@@ -265,11 +267,11 @@
 
     cmCMakePresetsGraph presetsGraph;
     auto result = presetsGraph.ReadProjectPresets(workingDirectory);
-    if (result != cmCMakePresetsGraph::ReadFileResult::READ_OK) {
+    if (result != true) {
       cmCPack_Log(&log, cmCPackLog::LOG_ERROR,
                   "Could not read presets from "
-                    << workingDirectory << ": "
-                    << cmCMakePresetsGraph::ResultToString(result) << '\n');
+                    << workingDirectory << ":"
+                    << presetsGraph.parseState.GetErrorMessage() << '\n');
       return 1;
     }
 
@@ -508,8 +510,8 @@
       cmCPack_Log(&log, cmCPackLog::LOG_ERROR,
                   "CPack generator not specified\n");
     } else {
-      std::vector<std::string> generatorsVector = cmExpandedList(*genList);
-      for (std::string const& gen : generatorsVector) {
+      cmList generatorsList{ *genList };
+      for (std::string const& gen : generatorsList) {
         cmMakefile::ScopePushPop raii(&globalMF);
         cmMakefile* mf = &globalMF;
         cmCPack_Log(&log, cmCPackLog::LOG_VERBOSE,
diff --git a/Source/CTest/cmCTestBuildAndTestHandler.cxx b/Source/CTest/cmCTestBuildAndTestHandler.cxx
index 643bc6f..5feb953 100644
--- a/Source/CTest/cmCTestBuildAndTestHandler.cxx
+++ b/Source/CTest/cmCTestBuildAndTestHandler.cxx
@@ -72,11 +72,6 @@
   if (!this->CTest->GetConfigType().empty()) {
     config = this->CTest->GetConfigType().c_str();
   }
-#ifdef CMAKE_INTDIR
-  if (!config) {
-    config = CMAKE_INTDIR;
-  }
-#endif
 
   if (config) {
     args.push_back("-DCMAKE_BUILD_TYPE:STRING=" + std::string(config));
@@ -251,16 +246,10 @@
         return 1;
       }
     }
-    std::string output;
     const char* config = nullptr;
     if (!this->CTest->GetConfigType().empty()) {
       config = this->CTest->GetConfigType().c_str();
     }
-#ifdef CMAKE_INTDIR
-    if (!config) {
-      config = CMAKE_INTDIR;
-    }
-#endif
     if (!config) {
       config = "Debug";
     }
@@ -269,9 +258,8 @@
                                 PackageResolveMode::Disable);
     int retVal = cm.GetGlobalGenerator()->Build(
       cmake::NO_BUILD_PARALLEL_LEVEL, this->SourceDir, this->BinaryDir,
-      this->BuildProject, { tar }, output, this->BuildMakeProgram, config,
+      this->BuildProject, { tar }, out, this->BuildMakeProgram, config,
       buildOptions, false, remainingTime);
-    out << output;
     // if the build failed then return
     if (retVal) {
       if (outstring) {
diff --git a/Source/CTest/cmCTestBuildCommand.cxx b/Source/CTest/cmCTestBuildCommand.cxx
index 71787ea..50d69df 100644
--- a/Source/CTest/cmCTestBuildCommand.cxx
+++ b/Source/CTest/cmCTestBuildCommand.cxx
@@ -89,11 +89,7 @@
         }
       }
       if (cmakeBuildConfiguration.empty()) {
-#ifdef CMAKE_INTDIR
-        cmakeBuildConfiguration = CMAKE_INTDIR;
-#else
         cmakeBuildConfiguration = "Debug";
-#endif
       }
 
       std::string dir = this->CTest->GetCTestConfiguration("BuildDirectory");
diff --git a/Source/CTest/cmCTestBuildHandler.cxx b/Source/CTest/cmCTestBuildHandler.cxx
index c6387ab..882b579 100644
--- a/Source/CTest/cmCTestBuildHandler.cxx
+++ b/Source/CTest/cmCTestBuildHandler.cxx
@@ -17,6 +17,7 @@
 #include "cmDuration.h"
 #include "cmFileTimeCache.h"
 #include "cmGeneratedFileStream.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmProcessOutput.h"
 #include "cmStringAlgorithms.h"
diff --git a/Source/CTest/cmCTestConfigureCommand.cxx b/Source/CTest/cmCTestConfigureCommand.cxx
index 1f3633d..bae1f54 100644
--- a/Source/CTest/cmCTestConfigureCommand.cxx
+++ b/Source/CTest/cmCTestConfigureCommand.cxx
@@ -4,13 +4,13 @@
 
 #include <cstring>
 #include <sstream>
-#include <vector>
 
 #include <cmext/string_view>
 
 #include "cmCTest.h"
 #include "cmCTestConfigureHandler.h"
 #include "cmGlobalGenerator.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
@@ -25,10 +25,10 @@
 
 cmCTestGenericHandler* cmCTestConfigureCommand::InitializeHandler()
 {
-  std::vector<std::string> options;
+  cmList options;
 
   if (!this->Options.empty()) {
-    cmExpandList(this->Options, options);
+    options.assign(this->Options);
   }
 
   if (this->CTest->GetCTestConfiguration("BuildDirectory").empty()) {
diff --git a/Source/CTest/cmCTestCurl.cxx b/Source/CTest/cmCTestCurl.cxx
index 13b0278..8f7d581 100644
--- a/Source/CTest/cmCTestCurl.cxx
+++ b/Source/CTest/cmCTestCurl.cxx
@@ -157,7 +157,7 @@
   // Now run off and do what you've been told!
   ::curl_easy_perform(this->Curl);
   ::fclose(ftpfile);
-  ::curl_easy_setopt(this->Curl, CURLOPT_HTTPHEADER, NULL);
+  ::curl_easy_setopt(this->Curl, CURLOPT_HTTPHEADER, nullptr);
   ::curl_slist_free_all(headers);
 
   if (!responseData.empty()) {
diff --git a/Source/CTest/cmCTestGIT.cxx b/Source/CTest/cmCTestGIT.cxx
index b2fb069..a6e7ac5 100644
--- a/Source/CTest/cmCTestGIT.cxx
+++ b/Source/CTest/cmCTestGIT.cxx
@@ -14,6 +14,7 @@
 
 #include "cmCTest.h"
 #include "cmCTestVC.h"
+#include "cmList.h"
 #include "cmProcessOutput.h"
 #include "cmProcessTools.h"
 #include "cmStringAlgorithms.h"
@@ -215,7 +216,7 @@
 
 bool cmCTestGIT::UpdateByCustom(std::string const& custom)
 {
-  std::vector<std::string> git_custom_command = cmExpandedList(custom, true);
+  cmList git_custom_command{ custom, cmList::EmptyElements::Yes };
   std::vector<char const*> git_custom;
   git_custom.reserve(git_custom_command.size() + 1);
   for (std::string const& i : git_custom_command) {
diff --git a/Source/CTest/cmCTestMultiProcessHandler.cxx b/Source/CTest/cmCTestMultiProcessHandler.cxx
index abd1aa6..44eccb2 100644
--- a/Source/CTest/cmCTestMultiProcessHandler.cxx
+++ b/Source/CTest/cmCTestMultiProcessHandler.cxx
@@ -19,6 +19,7 @@
 #include <vector>
 
 #include <cm/memory>
+#include <cm/optional>
 #include <cmext/algorithm>
 
 #include <cm3p/json/value.h>
@@ -1095,9 +1096,9 @@
     properties.append(
       DumpCTestProperty("SKIP_RETURN_CODE", testProperties.SkipReturnCode));
   }
-  if (testProperties.ExplicitTimeout) {
+  if (testProperties.Timeout) {
     properties.append(
-      DumpCTestProperty("TIMEOUT", testProperties.Timeout.count()));
+      DumpCTestProperty("TIMEOUT", testProperties.Timeout->count()));
   }
   if (!testProperties.TimeoutRegularExpressions.empty()) {
     properties.append(DumpCTestProperty(
diff --git a/Source/CTest/cmCTestP4.cxx b/Source/CTest/cmCTestP4.cxx
index 0e67c41..e22ec4b 100644
--- a/Source/CTest/cmCTestP4.cxx
+++ b/Source/CTest/cmCTestP4.cxx
@@ -13,9 +13,9 @@
 
 #include "cmCTest.h"
 #include "cmCTestVC.h"
+#include "cmList.h"
 #include "cmProcessTools.h"
 #include "cmRange.h"
-#include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 
 cmCTestP4::cmCTestP4(cmCTest* ct, std::ostream& log)
@@ -460,7 +460,7 @@
 
 bool cmCTestP4::UpdateCustom(const std::string& custom)
 {
-  std::vector<std::string> p4_custom_command = cmExpandedList(custom, true);
+  cmList p4_custom_command{ custom, cmList::EmptyElements::Yes };
 
   std::vector<char const*> p4_custom;
   p4_custom.reserve(p4_custom_command.size() + 1);
diff --git a/Source/CTest/cmCTestResourceSpec.cxx b/Source/CTest/cmCTestResourceSpec.cxx
index 142b07d..0e81fa9 100644
--- a/Source/CTest/cmCTestResourceSpec.cxx
+++ b/Source/CTest/cmCTestResourceSpec.cxx
@@ -10,17 +10,14 @@
 
 #include <cmext/string_view>
 
-#include <cm3p/json/reader.h>
 #include <cm3p/json/value.h>
 
-#include "cmsys/FStream.hxx"
 #include "cmsys/RegularExpression.hxx"
 
 #include "cmJSONHelpers.h"
 
 namespace {
-using JSONHelperBuilder =
-  cmJSONHelperBuilder<cmCTestResourceSpec::ReadFileResult>;
+using JSONHelperBuilder = cmJSONHelperBuilder;
 const cmsys::RegularExpression IdentifierRegex{ "^[a-z_][a-z0-9_]*$" };
 const cmsys::RegularExpression IdRegex{ "^[a-z0-9_]+$" };
 
@@ -36,165 +33,104 @@
 };
 
 auto const VersionFieldHelper =
-  JSONHelperBuilder::Int(cmCTestResourceSpec::ReadFileResult::READ_OK,
-                         cmCTestResourceSpec::ReadFileResult::INVALID_VERSION);
+  JSONHelperBuilder::Int(cmCTestResourceSpecErrors::INVALID_VERSION);
 
 auto const VersionHelper = JSONHelperBuilder::Required<Version>(
-  cmCTestResourceSpec::ReadFileResult::NO_VERSION,
-  JSONHelperBuilder::Object<Version>(
-    cmCTestResourceSpec::ReadFileResult::READ_OK,
-    cmCTestResourceSpec::ReadFileResult::INVALID_VERSION)
+  cmCTestResourceSpecErrors::NO_VERSION,
+  JSONHelperBuilder::Object<Version>()
     .Bind("major"_s, &Version::Major, VersionFieldHelper)
     .Bind("minor"_s, &Version::Minor, VersionFieldHelper));
 
-auto const RootVersionHelper =
-  JSONHelperBuilder::Object<TopVersion>(
-    cmCTestResourceSpec::ReadFileResult::READ_OK,
-    cmCTestResourceSpec::ReadFileResult::INVALID_ROOT)
-    .Bind("version"_s, &TopVersion::Version, VersionHelper, false);
+auto const RootVersionHelper = JSONHelperBuilder::Object<TopVersion>().Bind(
+  "version"_s, &TopVersion::Version, VersionHelper, false);
 
-cmCTestResourceSpec::ReadFileResult ResourceIdHelper(std::string& out,
-                                                     const Json::Value* value)
+bool ResourceIdHelper(std::string& out, const Json::Value* value,
+                      cmJSONState* state)
 {
-  auto result = JSONHelperBuilder::String(
-    cmCTestResourceSpec::ReadFileResult::READ_OK,
-    cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE)(out, value);
-  if (result != cmCTestResourceSpec::ReadFileResult::READ_OK) {
-    return result;
+  if (!JSONHelperBuilder::String(cmCTestResourceSpecErrors::INVALID_RESOURCE)(
+        out, value, state)) {
+    return false;
   }
   cmsys::RegularExpressionMatch match;
   if (!IdRegex.find(out.c_str(), match)) {
-    return cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE;
+    cmCTestResourceSpecErrors::INVALID_RESOURCE(value, state);
+    return false;
   }
-  return cmCTestResourceSpec::ReadFileResult::READ_OK;
+  return true;
 }
 
 auto const ResourceHelper =
-  JSONHelperBuilder::Object<cmCTestResourceSpec::Resource>(
-    cmCTestResourceSpec::ReadFileResult::READ_OK,
-    cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE)
+  JSONHelperBuilder::Object<cmCTestResourceSpec::Resource>()
     .Bind("id"_s, &cmCTestResourceSpec::Resource::Id, ResourceIdHelper)
-    .Bind("slots"_s, &cmCTestResourceSpec::Resource::Capacity,
-          JSONHelperBuilder::UInt(
-            cmCTestResourceSpec::ReadFileResult::READ_OK,
-            cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE, 1),
-          false);
+    .Bind(
+      "slots"_s, &cmCTestResourceSpec::Resource::Capacity,
+      JSONHelperBuilder::UInt(cmCTestResourceSpecErrors::INVALID_RESOURCE, 1),
+      false);
 
 auto const ResourceListHelper =
   JSONHelperBuilder::Vector<cmCTestResourceSpec::Resource>(
-    cmCTestResourceSpec::ReadFileResult::READ_OK,
-    cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE_TYPE,
-    ResourceHelper);
+    cmCTestResourceSpecErrors::INVALID_RESOURCE_TYPE, ResourceHelper);
 
 auto const ResourceMapHelper =
   JSONHelperBuilder::MapFilter<std::vector<cmCTestResourceSpec::Resource>>(
-    cmCTestResourceSpec::ReadFileResult::READ_OK,
-    cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC,
-    ResourceListHelper, [](const std::string& key) -> bool {
+    cmCTestResourceSpecErrors::INVALID_SOCKET_SPEC, ResourceListHelper,
+    [](const std::string& key) -> bool {
       cmsys::RegularExpressionMatch match;
       return IdentifierRegex.find(key.c_str(), match);
     });
 
 auto const SocketSetHelper = JSONHelperBuilder::Vector<
   std::map<std::string, std::vector<cmCTestResourceSpec::Resource>>>(
-  cmCTestResourceSpec::ReadFileResult::READ_OK,
-  cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC, ResourceMapHelper);
+  cmCTestResourceSpecErrors::INVALID_SOCKET_SPEC, ResourceMapHelper);
 
-cmCTestResourceSpec::ReadFileResult SocketHelper(
-  cmCTestResourceSpec::Socket& out, const Json::Value* value)
+bool SocketHelper(cmCTestResourceSpec::Socket& out, const Json::Value* value,
+                  cmJSONState* state)
 {
   std::vector<
     std::map<std::string, std::vector<cmCTestResourceSpec::Resource>>>
     sockets;
-  cmCTestResourceSpec::ReadFileResult result = SocketSetHelper(sockets, value);
-  if (result != cmCTestResourceSpec::ReadFileResult::READ_OK) {
-    return result;
+  if (!SocketSetHelper(sockets, value, state)) {
+    return false;
   }
   if (sockets.size() > 1) {
-    return cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC;
+    cmCTestResourceSpecErrors::INVALID_SOCKET_SPEC(value, state);
+    return false;
   }
   if (sockets.empty()) {
     out.Resources.clear();
   } else {
     out.Resources = std::move(sockets[0]);
   }
-  return cmCTestResourceSpec::ReadFileResult::READ_OK;
+  return true;
 }
 
 auto const LocalRequiredHelper =
   JSONHelperBuilder::Required<cmCTestResourceSpec::Socket>(
-    cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC, SocketHelper);
+    cmCTestResourceSpecErrors::INVALID_SOCKET_SPEC, SocketHelper);
 
-auto const RootHelper = JSONHelperBuilder::Object<cmCTestResourceSpec>(
-                          cmCTestResourceSpec::ReadFileResult::READ_OK,
-                          cmCTestResourceSpec::ReadFileResult::INVALID_ROOT)
-                          .Bind("local", &cmCTestResourceSpec::LocalSocket,
-                                LocalRequiredHelper, false);
+auto const RootHelper = JSONHelperBuilder::Object<cmCTestResourceSpec>().Bind(
+  "local", &cmCTestResourceSpec::LocalSocket, LocalRequiredHelper, false);
 }
 
-cmCTestResourceSpec::ReadFileResult cmCTestResourceSpec::ReadFromJSONFile(
-  const std::string& filename)
+bool cmCTestResourceSpec::ReadFromJSONFile(const std::string& filename)
 {
-  cmsys::ifstream fin(filename.c_str());
-  if (!fin) {
-    return ReadFileResult::FILE_NOT_FOUND;
-  }
-
   Json::Value root;
-  Json::CharReaderBuilder builder;
-  if (!Json::parseFromStream(builder, fin, &root, nullptr)) {
-    return ReadFileResult::JSON_PARSE_ERROR;
+
+  this->parseState = cmJSONState(filename, &root);
+  if (!this->parseState.errors.empty()) {
+    return false;
   }
 
   TopVersion version;
-  ReadFileResult result;
-  if ((result = RootVersionHelper(version, &root)) !=
-      ReadFileResult::READ_OK) {
+  bool result;
+  if ((result = RootVersionHelper(version, &root, &parseState)) != true) {
     return result;
   }
   if (version.Version.Major != 1 || version.Version.Minor != 0) {
-    return ReadFileResult::UNSUPPORTED_VERSION;
+    return false;
   }
 
-  return RootHelper(*this, &root);
-}
-
-const char* cmCTestResourceSpec::ResultToString(ReadFileResult result)
-{
-  switch (result) {
-    case ReadFileResult::READ_OK:
-      return "OK";
-
-    case ReadFileResult::FILE_NOT_FOUND:
-      return "File not found";
-
-    case ReadFileResult::JSON_PARSE_ERROR:
-      return "JSON parse error";
-
-    case ReadFileResult::INVALID_ROOT:
-      return "Invalid root object";
-
-    case ReadFileResult::NO_VERSION:
-      return "No version specified";
-
-    case ReadFileResult::INVALID_VERSION:
-      return "Invalid version object";
-
-    case ReadFileResult::UNSUPPORTED_VERSION:
-      return "Unsupported version";
-
-    case ReadFileResult::INVALID_SOCKET_SPEC:
-      return "Invalid socket object";
-
-    case ReadFileResult::INVALID_RESOURCE_TYPE:
-      return "Invalid resource type object";
-
-    case ReadFileResult::INVALID_RESOURCE:
-      return "Invalid resource object";
-
-    default:
-      return "Unknown";
-  }
+  return RootHelper(*this, &root, &parseState);
 }
 
 bool cmCTestResourceSpec::operator==(const cmCTestResourceSpec& other) const
diff --git a/Source/CTest/cmCTestResourceSpec.h b/Source/CTest/cmCTestResourceSpec.h
index 72628a3..37ccd72 100644
--- a/Source/CTest/cmCTestResourceSpec.h
+++ b/Source/CTest/cmCTestResourceSpec.h
@@ -8,6 +8,12 @@
 #include <string>
 #include <vector>
 
+#include "cmJSONState.h"
+
+namespace Json {
+class Value;
+}
+
 class cmCTestResourceSpec
 {
 public:
@@ -31,24 +37,45 @@
   };
 
   Socket LocalSocket;
+  cmJSONState parseState;
 
-  enum class ReadFileResult
-  {
-    READ_OK,
-    FILE_NOT_FOUND,
-    JSON_PARSE_ERROR,
-    INVALID_ROOT,
-    NO_VERSION,
-    INVALID_VERSION,
-    UNSUPPORTED_VERSION,
-    INVALID_SOCKET_SPEC, // Can't be INVALID_SOCKET due to a Windows macro
-    INVALID_RESOURCE_TYPE,
-    INVALID_RESOURCE,
-  };
-
-  ReadFileResult ReadFromJSONFile(const std::string& filename);
-  static const char* ResultToString(ReadFileResult result);
+  bool ReadFromJSONFile(const std::string& filename);
 
   bool operator==(const cmCTestResourceSpec& other) const;
   bool operator!=(const cmCTestResourceSpec& other) const;
 };
+
+namespace cmCTestResourceSpecErrors {
+const auto FILE_NOT_FOUND = [](const Json::Value*, cmJSONState* state) {
+  state->AddError("File not found");
+};
+const auto JSON_PARSE_ERROR = [](const Json::Value* value,
+                                 cmJSONState* state) {
+  state->AddErrorAtValue("JSON parse error", value);
+};
+const auto INVALID_ROOT = [](const Json::Value* value, cmJSONState* state) {
+  state->AddErrorAtValue("Invalid root object", value);
+};
+const auto NO_VERSION = [](const Json::Value* value, cmJSONState* state) {
+  state->AddErrorAtValue("No version specified", value);
+};
+const auto INVALID_VERSION = [](const Json::Value* value, cmJSONState* state) {
+  state->AddErrorAtValue("Invalid version object", value);
+};
+const auto UNSUPPORTED_VERSION = [](const Json::Value* value,
+                                    cmJSONState* state) {
+  state->AddErrorAtValue("Unsupported version", value);
+};
+const auto INVALID_SOCKET_SPEC = [](const Json::Value* value,
+                                    cmJSONState* state) {
+  state->AddErrorAtValue("Invalid socket object", value);
+};
+const auto INVALID_RESOURCE_TYPE = [](const Json::Value* value,
+                                      cmJSONState* state) {
+  state->AddErrorAtValue("Invalid resource type object", value);
+};
+const auto INVALID_RESOURCE = [](const Json::Value* value,
+                                 cmJSONState* state) {
+  state->AddErrorAtValue("Invalid resource object", value);
+};
+}
diff --git a/Source/CTest/cmCTestRunTest.cxx b/Source/CTest/cmCTestRunTest.cxx
index 5efe69f..cd2b230 100644
--- a/Source/CTest/cmCTestRunTest.cxx
+++ b/Source/CTest/cmCTestRunTest.cxx
@@ -14,12 +14,14 @@
 #include <utility>
 
 #include <cm/memory>
+#include <cm/optional>
 
 #include "cmsys/RegularExpression.hxx"
 
 #include "cmCTest.h"
 #include "cmCTestMemCheckHandler.h"
 #include "cmCTestMultiProcessHandler.h"
+#include "cmDuration.h"
 #include "cmProcess.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
@@ -30,11 +32,6 @@
 {
   this->CTest = multiHandler.CTest;
   this->TestHandler = multiHandler.TestHandler;
-  this->TestResult.ExecutionTime = cmDuration::zero();
-  this->TestResult.ReturnValue = 0;
-  this->TestResult.Status = cmCTestTestHandler::NOT_RUN;
-  this->TestResult.TestCount = 0;
-  this->TestResult.Properties = nullptr;
 }
 
 void cmCTestRunTest::CheckOutput(std::string const& line)
@@ -623,28 +620,7 @@
   }
   this->StartTime = this->CTest->CurrentTime();
 
-  auto timeout = this->TestProperties->Timeout;
-
-  this->TimeoutIsForStopTime = false;
-  std::chrono::system_clock::time_point stop_time = this->CTest->GetStopTime();
-  if (stop_time != std::chrono::system_clock::time_point()) {
-    std::chrono::duration<double> stop_timeout =
-      (stop_time - std::chrono::system_clock::now()) % std::chrono::hours(24);
-
-    if (stop_timeout <= std::chrono::duration<double>::zero()) {
-      stop_timeout = std::chrono::duration<double>::zero();
-    }
-    if (timeout == std::chrono::duration<double>::zero() ||
-        stop_timeout < timeout) {
-      this->TimeoutIsForStopTime = true;
-      timeout = stop_timeout;
-    }
-  }
-
-  return this->ForkProcess(timeout, this->TestProperties->ExplicitTimeout,
-                           &this->TestProperties->Environment,
-                           &this->TestProperties->EnvironmentModification,
-                           &this->TestProperties->Affinity);
+  return this->ForkProcess();
 }
 
 void cmCTestRunTest::ComputeArguments()
@@ -742,46 +718,80 @@
   }
 }
 
-bool cmCTestRunTest::ForkProcess(
-  cmDuration testTimeOut, bool explicitTimeout,
-  std::vector<std::string>* environment,
-  std::vector<std::string>* environment_modification,
-  std::vector<size_t>* affinity)
+bool cmCTestRunTest::ForkProcess()
 {
   this->TestProcess->SetId(this->Index);
   this->TestProcess->SetWorkingDirectory(this->TestProperties->Directory);
   this->TestProcess->SetCommand(this->ActualCommand);
   this->TestProcess->SetCommandArguments(this->Arguments);
 
-  // determine how much time we have
-  cmDuration timeout = this->CTest->GetRemainingTimeAllowed();
-  if (timeout != cmCTest::MaxDuration()) {
-    timeout -= std::chrono::minutes(2);
-  }
-  if (this->CTest->GetTimeOut() > cmDuration::zero() &&
-      this->CTest->GetTimeOut() < timeout) {
-    timeout = this->CTest->GetTimeOut();
-  }
-  if (testTimeOut > cmDuration::zero() &&
-      testTimeOut < this->CTest->GetRemainingTimeAllowed()) {
-    timeout = testTimeOut;
-  }
-  // always have at least 1 second if we got to here
-  if (timeout <= cmDuration::zero()) {
-    timeout = std::chrono::seconds(1);
-  }
-  // handle timeout explicitly set to 0
-  if (testTimeOut == cmDuration::zero() && explicitTimeout) {
-    timeout = cmDuration::zero();
-  }
-  cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
-                     this->Index << ": "
-                                 << "Test timeout computed to be: "
-                                 << cmDurationTo<unsigned int>(timeout)
-                                 << "\n",
-                     this->TestHandler->GetQuiet());
+  cm::optional<cmDuration> timeout;
 
-  this->TestProcess->SetTimeout(timeout);
+  // Check TIMEOUT test property.
+  if (this->TestProperties->Timeout &&
+      *this->TestProperties->Timeout >= cmDuration::zero()) {
+    timeout = this->TestProperties->Timeout;
+  }
+
+  // An explicit TIMEOUT=0 test property means "no timeout".
+  if (timeout && *timeout == std::chrono::duration<double>::zero()) {
+    timeout = cm::nullopt;
+  } else {
+    // Check --timeout.
+    if (!timeout && this->CTest->GetGlobalTimeout() > cmDuration::zero()) {
+      timeout = this->CTest->GetGlobalTimeout();
+    }
+
+    // Check CTEST_TEST_TIMEOUT.
+    cmDuration ctestTestTimeout = this->CTest->GetTimeOut();
+    if (ctestTestTimeout > cmDuration::zero() &&
+        (!timeout || ctestTestTimeout < *timeout)) {
+      timeout = ctestTestTimeout;
+    }
+  }
+
+  // Check CTEST_TIME_LIMIT.
+  cmDuration timeRemaining = this->CTest->GetRemainingTimeAllowed();
+  if (timeRemaining != cmCTest::MaxDuration()) {
+    // This two minute buffer is historical.
+    timeRemaining -= std::chrono::minutes(2);
+  }
+
+  // Check --stop-time.
+  std::chrono::system_clock::time_point stop_time = this->CTest->GetStopTime();
+  if (stop_time != std::chrono::system_clock::time_point()) {
+    cmDuration timeUntilStop =
+      (stop_time - std::chrono::system_clock::now()) % std::chrono::hours(24);
+    if (timeUntilStop < timeRemaining) {
+      timeRemaining = timeUntilStop;
+    }
+  }
+
+  // Enforce remaining time even over explicit TIMEOUT=0.
+  if (timeRemaining <= cmDuration::zero()) {
+    timeRemaining = cmDuration::zero();
+  }
+  if (!timeout || timeRemaining < *timeout) {
+    this->TimeoutIsForStopTime = true;
+    timeout = timeRemaining;
+  }
+
+  if (timeout) {
+    cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+                       this->Index << ": "
+                                   << "Test timeout computed to be: "
+                                   << cmDurationTo<unsigned int>(*timeout)
+                                   << "\n",
+                       this->TestHandler->GetQuiet());
+
+    this->TestProcess->SetTimeout(*timeout);
+  } else {
+    cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+                       this->Index
+                         << ": "
+                         << "Test timeout suppressed by TIMEOUT property.\n",
+                       this->TestHandler->GetQuiet());
+  }
 
   cmSystemTools::SaveRestoreEnvironment sre;
   std::ostringstream envMeasurement;
@@ -789,17 +799,17 @@
   // We split processing ENVIRONMENT and ENVIRONMENT_MODIFICATION into two
   // phases to ensure that MYVAR=reset: in the latter phase resets to the
   // former phase's settings, rather than to the original environment.
-  if (environment && !environment->empty()) {
+  if (!this->TestProperties->Environment.empty()) {
     cmSystemTools::EnvDiff diff;
-    diff.AppendEnv(*environment);
+    diff.AppendEnv(this->TestProperties->Environment);
     diff.ApplyToCurrentEnv(&envMeasurement);
   }
 
-  if (environment_modification && !environment_modification->empty()) {
+  if (!this->TestProperties->EnvironmentModification.empty()) {
     cmSystemTools::EnvDiff diff;
     bool env_ok = true;
 
-    for (auto const& envmod : *environment_modification) {
+    for (auto const& envmod : this->TestProperties->EnvironmentModification) {
       env_ok &= diff.ParseOperation(envmod);
     }
 
@@ -828,7 +838,7 @@
                                      1);
 
   return this->TestProcess->StartProcess(this->MultiTestHandler.Loop,
-                                         affinity);
+                                         &this->TestProperties->Affinity);
 }
 
 void cmCTestRunTest::SetupResourcesEnvironment(std::vector<std::string>* log)
diff --git a/Source/CTest/cmCTestRunTest.h b/Source/CTest/cmCTestRunTest.h
index 7a97fa9..6a507f4 100644
--- a/Source/CTest/cmCTestRunTest.h
+++ b/Source/CTest/cmCTestRunTest.h
@@ -14,7 +14,6 @@
 #include "cmCTest.h"
 #include "cmCTestMultiProcessHandler.h"
 #include "cmCTestTestHandler.h"
-#include "cmDuration.h"
 #include "cmProcess.h"
 
 /** \class cmRunTest
@@ -110,10 +109,7 @@
   bool NeedsToRepeat();
   void ParseOutputForMeasurements();
   void ExeNotFound(std::string exe);
-  bool ForkProcess(cmDuration testTimeOut, bool explicitTimeout,
-                   std::vector<std::string>* environment,
-                   std::vector<std::string>* environment_modification,
-                   std::vector<size_t>* affinity);
+  bool ForkProcess();
   void WriteLogOutputTop(size_t completed, size_t total);
   // Run post processing of the process output for MemCheck
   void MemCheckPostProcess();
diff --git a/Source/CTest/cmCTestScriptHandler.cxx b/Source/CTest/cmCTestScriptHandler.cxx
index ee06b29..461ad1a 100644
--- a/Source/CTest/cmCTestScriptHandler.cxx
+++ b/Source/CTest/cmCTestScriptHandler.cxx
@@ -33,6 +33,7 @@
 #include "cmDuration.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGlobalGenerator.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmState.h"
 #include "cmStateDirectory.h"
@@ -521,7 +522,7 @@
 
   // set any environment variables
   if (!this->CTestEnv.empty()) {
-    std::vector<std::string> envArgs = cmExpandedList(this->CTestEnv);
+    cmList envArgs{ this->CTestEnv };
     cmSystemTools::AppendEnv(envArgs);
   }
 
@@ -625,7 +626,7 @@
   // do an initial cvs update as required
   command = this->UpdateCmd;
   for (std::string const& eu : this->ExtraUpdates) {
-    std::vector<std::string> cvsArgs = cmExpandedList(eu);
+    cmList cvsArgs{ eu };
     if (cvsArgs.size() == 2) {
       std::string fullCommand = cmStrCat(command, " update ", cvsArgs[1]);
       output.clear();
@@ -764,7 +765,7 @@
   }
 
   // run ctest, it may be more than one command in here
-  std::vector<std::string> ctestCommands = cmExpandedList(this->CTestCmd);
+  cmList ctestCommands{ this->CTestCmd };
   // for each variable/argument do a putenv
   for (std::string const& ctestCommand : ctestCommands) {
     command = ctestCommand;
diff --git a/Source/CTest/cmCTestSubmitCommand.cxx b/Source/CTest/cmCTestSubmitCommand.cxx
index a1933cc..a92f9f2 100644
--- a/Source/CTest/cmCTestSubmitCommand.cxx
+++ b/Source/CTest/cmCTestSubmitCommand.cxx
@@ -13,10 +13,10 @@
 #include "cmCTest.h"
 #include "cmCTestSubmitHandler.h"
 #include "cmCommand.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
 #include "cmRange.h"
-#include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmValue.h"
 
@@ -64,14 +64,14 @@
   cmValue notesFilesVariable =
     this->Makefile->GetDefinition("CTEST_NOTES_FILES");
   if (notesFilesVariable) {
-    std::vector<std::string> notesFiles = cmExpandedList(*notesFilesVariable);
+    cmList notesFiles{ *notesFilesVariable };
     this->CTest->GenerateNotesFile(notesFiles);
   }
 
   cmValue extraFilesVariable =
     this->Makefile->GetDefinition("CTEST_EXTRA_SUBMIT_FILES");
   if (extraFilesVariable) {
-    std::vector<std::string> extraFiles = cmExpandedList(*extraFilesVariable);
+    cmList extraFiles{ *extraFilesVariable };
     if (!this->CTest->SubmitExtraFiles(extraFiles)) {
       this->SetError("problem submitting extra files.");
       return nullptr;
diff --git a/Source/CTest/cmCTestSubmitHandler.cxx b/Source/CTest/cmCTestSubmitHandler.cxx
index 3ff8c8f..a095e5d 100644
--- a/Source/CTest/cmCTestSubmitHandler.cxx
+++ b/Source/CTest/cmCTestSubmitHandler.cxx
@@ -22,6 +22,7 @@
 #include "cmCurl.h"
 #include "cmDuration.h"
 #include "cmGeneratedFileStream.h"
+#include "cmList.h"
 #include "cmState.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
@@ -160,7 +161,7 @@
   /* In windows, this will init the winsock stuff */
   ::curl_global_init(CURL_GLOBAL_ALL);
   std::string curlopt(this->CTest->GetCTestConfiguration("CurlOptions"));
-  std::vector<std::string> args = cmExpandedList(curlopt);
+  cmList args{ curlopt };
   bool verifyPeerOff = false;
   bool verifyHostOff = false;
   for (std::string const& arg : args) {
@@ -499,7 +500,7 @@
   cmCTestCurl curl(this->CTest);
   curl.SetQuiet(this->Quiet);
   std::string curlopt(this->CTest->GetCTestConfiguration("CurlOptions"));
-  std::vector<std::string> args = cmExpandedList(curlopt);
+  cmList args{ curlopt };
   curl.SetCurlOptions(args);
   auto submitInactivityTimeout = this->GetSubmitInactivityTimeout();
   if (submitInactivityTimeout != 0) {
diff --git a/Source/CTest/cmCTestTestHandler.cxx b/Source/CTest/cmCTestTestHandler.cxx
index 1c8c713..7764f2b 100644
--- a/Source/CTest/cmCTestTestHandler.cxx
+++ b/Source/CTest/cmCTestTestHandler.cxx
@@ -37,6 +37,8 @@
 #include "cmExecutionStatus.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGlobalGenerator.h"
+#include "cmJSONState.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmState.h"
 #include "cmStateSnapshot.h"
@@ -1346,12 +1348,11 @@
   }
   if (!this->ResourceSpecFile.empty()) {
     this->UseResourceSpec = true;
-    auto result = this->ResourceSpec.ReadFromJSONFile(this->ResourceSpecFile);
-    if (result != cmCTestResourceSpec::ReadFileResult::READ_OK) {
+    if (!this->ResourceSpec.ReadFromJSONFile(this->ResourceSpecFile)) {
       cmCTestLog(this->CTest, ERROR_MESSAGE,
                  "Could not read/parse resource spec file "
                    << this->ResourceSpecFile << ": "
-                   << cmCTestResourceSpec::ResultToString(result)
+                   << this->ResourceSpec.parseState.GetErrorMessage()
                    << std::endl);
       return false;
     }
@@ -1378,11 +1379,6 @@
       p.Cost = static_cast<float>(rand());
     }
 
-    if (p.Timeout == cmDuration::zero() &&
-        this->CTest->GetGlobalTimeout() != cmDuration::zero()) {
-      p.Timeout = this->CTest->GetGlobalTimeout();
-    }
-
     if (!p.Depends.empty()) {
       for (std::string const& i : p.Depends) {
         for (cmCTestTestProperties const& it2 : this->TestList) {
@@ -2203,9 +2199,8 @@
       for (cmCTestTestProperties& rt : this->TestList) {
         if (t == rt.Name) {
           if (key == "_BACKTRACE_TRIPLES"_s) {
-            std::vector<std::string> triples;
             // allow empty args in the triples
-            cmExpandList(val, triples, true);
+            cmList triples{ val, cmList::EmptyElements::Yes };
 
             // Ensure we have complete triples otherwise the data is corrupt.
             if (triples.size() % 3 == 0) {
@@ -2214,7 +2209,7 @@
 
               // the first entry represents the top of the trace so we need to
               // reconstruct the backtrace in reverse
-              for (size_t i = triples.size(); i >= 3; i -= 3) {
+              for (auto i = triples.size(); i >= 3; i -= 3) {
                 cmListFileContext fc;
                 fc.FilePath = triples[i - 3];
                 long line = 0;
@@ -2235,24 +2230,23 @@
           } else if (key == "ATTACHED_FILES_ON_FAIL"_s) {
             cmExpandList(val, rt.AttachOnFail);
           } else if (key == "RESOURCE_LOCK"_s) {
-            std::vector<std::string> lval = cmExpandedList(val);
+            cmList lval{ val };
 
             rt.LockedResources.insert(lval.begin(), lval.end());
           } else if (key == "FIXTURES_SETUP"_s) {
-            std::vector<std::string> lval = cmExpandedList(val);
+            cmList lval{ val };
 
             rt.FixturesSetup.insert(lval.begin(), lval.end());
           } else if (key == "FIXTURES_CLEANUP"_s) {
-            std::vector<std::string> lval = cmExpandedList(val);
+            cmList lval{ val };
 
             rt.FixturesCleanup.insert(lval.begin(), lval.end());
           } else if (key == "FIXTURES_REQUIRED"_s) {
-            std::vector<std::string> lval = cmExpandedList(val);
+            cmList lval{ val };
 
             rt.FixturesRequired.insert(lval.begin(), lval.end());
           } else if (key == "TIMEOUT"_s) {
             rt.Timeout = cmDuration(atof(val.c_str()));
-            rt.ExplicitTimeout = true;
           } else if (key == "COST"_s) {
             rt.Cost = static_cast<float>(atof(val.c_str()));
           } else if (key == "REQUIRED_FILES"_s) {
@@ -2260,12 +2254,12 @@
           } else if (key == "RUN_SERIAL"_s) {
             rt.RunSerial = cmIsOn(val);
           } else if (key == "FAIL_REGULAR_EXPRESSION"_s) {
-            std::vector<std::string> lval = cmExpandedList(val);
+            cmList lval{ val };
             for (std::string const& cr : lval) {
               rt.ErrorRegularExpressions.emplace_back(cr, cr);
             }
           } else if (key == "SKIP_REGULAR_EXPRESSION"_s) {
-            std::vector<std::string> lval = cmExpandedList(val);
+            cmList lval{ val };
             for (std::string const& cr : lval) {
               rt.SkipRegularExpressions.emplace_back(cr, cr);
             }
@@ -2292,7 +2286,7 @@
           } else if (key == "ENVIRONMENT_MODIFICATION"_s) {
             cmExpandList(val, rt.EnvironmentModification);
           } else if (key == "LABELS"_s) {
-            std::vector<std::string> Labels = cmExpandedList(val);
+            cmList Labels{ val };
             rt.Labels.insert(rt.Labels.end(), Labels.begin(), Labels.end());
             // sort the array
             std::sort(rt.Labels.begin(), rt.Labels.end());
@@ -2309,21 +2303,21 @@
               rt.Measurements[val] = "1";
             }
           } else if (key == "PASS_REGULAR_EXPRESSION"_s) {
-            std::vector<std::string> lval = cmExpandedList(val);
+            cmList lval{ val };
             for (std::string const& cr : lval) {
               rt.RequiredRegularExpressions.emplace_back(cr, cr);
             }
           } else if (key == "WORKING_DIRECTORY"_s) {
             rt.Directory = val;
           } else if (key == "TIMEOUT_AFTER_MATCH"_s) {
-            std::vector<std::string> propArgs = cmExpandedList(val);
+            cmList propArgs{ val };
             if (propArgs.size() != 2) {
               cmCTestLog(this->CTest, WARNING,
                          "TIMEOUT_AFTER_MATCH expects two arguments, found "
                            << propArgs.size() << std::endl);
             } else {
               rt.AlternateTimeout = cmDuration(atof(propArgs[0].c_str()));
-              std::vector<std::string> lval = cmExpandedList(propArgs[1]);
+              cmList lval{ propArgs[1] };
               for (std::string const& cr : lval) {
                 rt.TimeoutRegularExpressions.emplace_back(cr, cr);
               }
@@ -2365,7 +2359,7 @@
       std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
       if (cwd == rt.Directory) {
         if (key == "LABELS"_s) {
-          std::vector<std::string> DirectoryLabels = cmExpandedList(val);
+          cmList DirectoryLabels{ val };
           rt.Labels.insert(rt.Labels.end(), DirectoryLabels.begin(),
                            DirectoryLabels.end());
 
@@ -2431,17 +2425,6 @@
                      "Set test directory: " << test.Directory << std::endl,
                      this->Quiet);
 
-  test.IsInBasedOnREOptions = true;
-  test.WillFail = false;
-  test.Disabled = false;
-  test.RunSerial = false;
-  test.Timeout = cmDuration::zero();
-  test.ExplicitTimeout = false;
-  test.Cost = 0;
-  test.Processors = 1;
-  test.WantAffinity = false;
-  test.SkipReturnCode = -1;
-  test.PreviousRuns = 0;
   if (this->UseIncludeRegExpFlag &&
       (!this->IncludeTestsRegularExpression.find(testname) ||
        (!this->UseExcludeRegExpFirst &&
@@ -2569,7 +2552,7 @@
       xml.EndElement(); // </skipped>
     } else if (status == "fail") {
       xml.StartElement("failure");
-      xml.Attribute("message", result.Reason);
+      xml.Attribute("message", this->GetTestStatus(result));
       xml.EndElement(); // </failure>
     }
 
diff --git a/Source/CTest/cmCTestTestHandler.h b/Source/CTest/cmCTestTestHandler.h
index d0049da..b7c0faf 100644
--- a/Source/CTest/cmCTestTestHandler.h
+++ b/Source/CTest/cmCTestTestHandler.h
@@ -14,6 +14,8 @@
 #include <utility>
 #include <vector>
 
+#include <cm/optional>
+
 #include "cmsys/RegularExpression.hxx"
 
 #include "cmCTest.h"
@@ -139,22 +141,21 @@
     std::vector<std::pair<cmsys::RegularExpression, std::string>>
       TimeoutRegularExpressions;
     std::map<std::string, std::string> Measurements;
-    bool IsInBasedOnREOptions;
-    bool WillFail;
-    bool Disabled;
-    float Cost;
-    int PreviousRuns;
-    bool RunSerial;
-    cmDuration Timeout;
-    bool ExplicitTimeout;
+    bool IsInBasedOnREOptions = true;
+    bool WillFail = false;
+    bool Disabled = false;
+    float Cost = 0;
+    int PreviousRuns = 0;
+    bool RunSerial = false;
+    cm::optional<cmDuration> Timeout;
     cmDuration AlternateTimeout;
-    int Index;
+    int Index = 0;
     // Requested number of process slots
-    int Processors;
-    bool WantAffinity;
+    int Processors = 1;
+    bool WantAffinity = false;
     std::vector<size_t> Affinity;
     // return code of test which will mark test as "not run"
-    int SkipReturnCode;
+    int SkipReturnCode = -1;
     std::vector<std::string> Environment;
     std::vector<std::string> EnvironmentModification;
     std::vector<std::string> Labels;
@@ -175,17 +176,17 @@
     std::string Reason;
     std::string FullCommandLine;
     std::string Environment;
-    cmDuration ExecutionTime;
-    std::int64_t ReturnValue;
-    int Status;
+    cmDuration ExecutionTime = cmDuration::zero();
+    std::int64_t ReturnValue = 0;
+    int Status = NOT_RUN;
     std::string ExceptionStatus;
     bool CompressOutput;
     std::string CompletionStatus;
     std::string CustomCompletionStatus;
     std::string Output;
     std::string TestMeasurementsOutput;
-    int TestCount;
-    cmCTestTestProperties* Properties;
+    int TestCount = 0;
+    cmCTestTestProperties* Properties = nullptr;
   };
 
   struct cmCTestTestResultLess
diff --git a/Source/CTest/cmProcess.cxx b/Source/CTest/cmProcess.cxx
index e14a4e1..780d626 100644
--- a/Source/CTest/cmProcess.cxx
+++ b/Source/CTest/cmProcess.cxx
@@ -13,7 +13,6 @@
 
 #include "cmCTest.h"
 #include "cmCTestRunTest.h"
-#include "cmCTestTestHandler.h"
 #include "cmGetPipes.h"
 #include "cmStringAlgorithms.h"
 #if defined(_WIN32)
@@ -26,7 +25,6 @@
   : Runner(std::move(runner))
   , Conv(cmProcessOutput::UTF8, CM_PROCESS_BUF_SIZE)
 {
-  this->Timeout = cmDuration::zero();
   this->TotalTime = cmDuration::zero();
   this->ExitValue = 0;
   this->Id = 0;
@@ -152,11 +150,9 @@
 
 void cmProcess::StartTimer()
 {
-  auto* properties = this->Runner->GetTestProperties();
-  auto msec =
-    std::chrono::duration_cast<std::chrono::milliseconds>(this->Timeout);
-
-  if (msec != std::chrono::milliseconds(0) || !properties->ExplicitTimeout) {
+  if (this->Timeout) {
+    auto msec =
+      std::chrono::duration_cast<std::chrono::milliseconds>(*this->Timeout);
     this->Timer.start(&cmProcess::OnTimeoutCB,
                       static_cast<uint64_t>(msec.count()), 0);
   }
diff --git a/Source/CTest/cmProcess.h b/Source/CTest/cmProcess.h
index be030e4..1578687 100644
--- a/Source/CTest/cmProcess.h
+++ b/Source/CTest/cmProcess.h
@@ -12,6 +12,8 @@
 #include <utility>
 #include <vector>
 
+#include <cm/optional>
+
 #include <cm3p/uv.h>
 
 #include "cmDuration.h"
@@ -76,7 +78,7 @@
   }
 
 private:
-  cmDuration Timeout;
+  cm::optional<cmDuration> Timeout;
   std::chrono::steady_clock::time_point StartTime;
   cmDuration TotalTime;
   bool ReadHandleClosed = false;
diff --git a/Source/Checks/cm_cxx_features.cmake b/Source/Checks/cm_cxx_features.cmake
index 73e7ef2..f5b7587 100644
--- a/Source/Checks/cm_cxx_features.cmake
+++ b/Source/Checks/cm_cxx_features.cmake
@@ -38,6 +38,8 @@
     string(REGEX REPLACE " +0 Warning\\(s\\)" "" check_output "${check_output}")
     # Filter out MSBuild output that looks like a warning.
     string(REGEX REPLACE "[^\n]*warning MSB[0-9][0-9][0-9][0-9][^\n]*" "" check_output "${check_output}")
+    # Filter out MSVC output that looks like a command-line warning.
+    string(REGEX REPLACE "[^\n]*warning D[0-9][0-9][0-9][0-9][^\n]*" "" check_output "${check_output}")
     # Filter out warnings caused by user flags.
     string(REGEX REPLACE "[^\n]*warning:[^\n]*-Winvalid-command-line-argument[^\n]*" "" check_output "${check_output}")
     # Filter out warnings caused by local configuration.
diff --git a/Source/CursesDialog/cmCursesCacheEntryComposite.cxx b/Source/CursesDialog/cmCursesCacheEntryComposite.cxx
index e9ec09e..fc5450e 100644
--- a/Source/CursesDialog/cmCursesCacheEntryComposite.cxx
+++ b/Source/CursesDialog/cmCursesCacheEntryComposite.cxx
@@ -4,7 +4,6 @@
 
 #include <cassert>
 #include <utility>
-#include <vector>
 
 #include <cm/memory>
 
@@ -15,9 +14,9 @@
 #include "cmCursesPathWidget.h"
 #include "cmCursesStringWidget.h"
 #include "cmCursesWidget.h"
+#include "cmList.h"
 #include "cmState.h"
 #include "cmStateTypes.h"
-#include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmValue.h"
 
@@ -76,7 +75,7 @@
       if (stringsProp) {
         auto ow =
           cm::make_unique<cmCursesOptionsWidget>(this->EntryWidth, 1, 1, 1);
-        for (std::string const& opt : cmExpandedList(*stringsProp)) {
+        for (auto const& opt : cmList{ *stringsProp }) {
           ow->AddOption(opt);
         }
         ow->SetOption(*value);
diff --git a/Source/CursesDialog/form/frm_def.c b/Source/CursesDialog/form/frm_def.c
index 645b3ba..569057b 100644
--- a/Source/CursesDialog/form/frm_def.c
+++ b/Source/CursesDialog/form/frm_def.c
@@ -220,6 +220,10 @@
   for(page_nr = 0;page_nr < form->maxpage; page_nr++)
     {
       FIELD *fld = (FIELD *)0;
+      #ifdef __clang_analyzer__
+      /* Tell clang-analyzer the loop body runs at least once.  */
+      assert(form->page[page_nr].pmin <= form->page[page_nr].pmax);
+      #endif
       for(j = form->page[page_nr].pmin;j <= form->page[page_nr].pmax;j++)
 	{
 	  fields[j]->index = j;
diff --git a/Source/LexerParser/cmFortranLexer.cxx b/Source/LexerParser/cmFortranLexer.cxx
index 5703de1..df65472 100644
--- a/Source/LexerParser/cmFortranLexer.cxx
+++ b/Source/LexerParser/cmFortranLexer.cxx
@@ -548,8 +548,8 @@
 	yyg->yy_hold_char = *yy_cp; \
 	*yy_cp = '\0'; \
 	yyg->yy_c_buf_p = yy_cp;
-#define YY_NUM_RULES 54
-#define YY_END_OF_BUFFER 55
+#define YY_NUM_RULES 55
+#define YY_END_OF_BUFFER 56
 /* This struct is not used in this scanner,
    but its presence is necessary. */
 struct yy_trans_info
@@ -560,29 +560,29 @@
 static const flex_int16_t yy_accept[216] =
     {   0,
         0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
-       55,   49,   51,   50,   53,    1,   49,   33,    2,   47,
-       48,   35,   37,   50,   39,   49,   46,   46,   46,   46,
-       46,   46,   49,   46,   51,   49,   50,   51,   49,   46,
-        9,    8,    9,    9,    4,    3,   49,    0,   10,    0,
-        0,    0,    0,    0,   33,   33,   34,   36,   39,   49,
-       46,   46,   46,   46,   46,   46,    0,   52,    0,   46,
+       56,   50,   52,   51,   54,    1,   50,   34,    2,   48,
+       49,   36,   38,   51,   40,   50,   47,   47,   47,   47,
+       47,   47,   50,   47,   52,   50,   51,   52,   50,   47,
+        9,    8,    9,    9,    4,    3,   50,    0,   10,    0,
+        0,    0,    0,    0,   34,   34,   35,   37,   40,   50,
+       47,   47,   47,   47,   47,   47,    0,   53,    0,   47,
         0,    0,    0,   12,    0,    0,    0,    0,    0,    0,
-        0,   49,    0,   11,   46,    0,    0,    0,    5,    0,
-        0,    0,    0,    0,   29,    0,   33,   33,   33,   33,
+        0,   50,    0,   11,   47,    0,    0,    0,    5,    0,
+        0,    0,    0,    0,   30,    0,   34,   34,   34,   34,
 
-        0,    0,   40,   46,   46,   46,   46,   45,   12,   12,
-        0,    0,    0,   23,    0,    0,    0,    0,    0,    0,
+        0,    0,   41,   47,   47,   47,   47,   46,   12,   12,
+        0,    0,    0,   24,    0,    0,    0,    0,    0,    0,
         6,    0,    0,    0,    0,    0,    0,    0,    0,    0,
-       46,   46,   46,   46,    0,    0,    0,    0,    0,    0,
-        0,    0,    0,    0,    0,    0,    0,   30,   31,    0,
-        0,    0,    0,    0,   46,   46,   46,   46,    0,   24,
-       25,    0,    0,    0,    0,    0,    0,    0,    0,    0,
-       20,   32,   27,    0,    0,    0,   46,   46,   43,   46,
-        0,   26,   21,    0,    0,    0,   19,    0,    0,   18,
-       28,    0,    0,   41,   46,   46,   17,   22,    0,    7,
+       47,   47,   47,   47,    0,    0,    0,    0,    0,    0,
+        0,    0,    0,    0,    0,    0,    0,   31,   32,    0,
+        0,    0,    0,    0,   47,   47,   47,   47,    0,   25,
+       26,    0,    0,    0,    0,   13,    0,    0,    0,    0,
+       21,   33,   28,    0,    0,    0,   47,   47,   44,   47,
+        0,   27,   22,    0,    0,   13,   20,    0,    0,   19,
+       29,    0,    0,   42,   47,   47,   18,   23,    0,    7,
 
-       38,    7,   15,    0,   46,   46,   14,   16,   42,   44,
-        0,    0,    0,   13,    0
+       39,    7,   16,    0,   47,   47,   15,   17,   43,   45,
+        0,    0,    0,   14,    0
     } ;
 
 static const YY_CHAR yy_ec[256] =
@@ -1252,7 +1252,11 @@
 { return CPP_LINE_DIRECTIVE; }
 	YY_BREAK
 case 13:
-/* rule 13 can match eol */
+YY_RULE_SETUP
+{ return CPP_LINE_DIRECTIVE; }
+	YY_BREAK
+case 14:
+/* rule 14 can match eol */
 YY_RULE_SETUP
 {
   yytext[yyleng-1] = 0;
@@ -1260,172 +1264,172 @@
   return CPP_INCLUDE_ANGLE;
 }
 	YY_BREAK
-case 14:
+case 15:
 YY_RULE_SETUP
 { return CPP_INCLUDE; }
 	YY_BREAK
-case 15:
+case 16:
 YY_RULE_SETUP
 { return F90PPR_INCLUDE; }
 	YY_BREAK
-case 16:
+case 17:
 YY_RULE_SETUP
 { return COCO_INCLUDE; }
 	YY_BREAK
-case 17:
+case 18:
 YY_RULE_SETUP
 { return CPP_DEFINE; }
 	YY_BREAK
-case 18:
+case 19:
 YY_RULE_SETUP
 { return F90PPR_DEFINE; }
 	YY_BREAK
-case 19:
+case 20:
 YY_RULE_SETUP
 { return CPP_UNDEF; }
 	YY_BREAK
-case 20:
+case 21:
 YY_RULE_SETUP
 { return F90PPR_UNDEF; }
 	YY_BREAK
-case 21:
+case 22:
 YY_RULE_SETUP
 { return CPP_IFDEF; }
 	YY_BREAK
-case 22:
+case 23:
 YY_RULE_SETUP
 { return CPP_IFNDEF; }
 	YY_BREAK
-case 23:
+case 24:
 YY_RULE_SETUP
 { return CPP_IF; }
 	YY_BREAK
-case 24:
+case 25:
 YY_RULE_SETUP
 { return CPP_ELIF; }
 	YY_BREAK
-case 25:
+case 26:
 YY_RULE_SETUP
 { return CPP_ELSE; }
 	YY_BREAK
-case 26:
+case 27:
 YY_RULE_SETUP
 { return CPP_ENDIF; }
 	YY_BREAK
-case 27:
+case 28:
 YY_RULE_SETUP
 { return F90PPR_IFDEF; }
 	YY_BREAK
-case 28:
+case 29:
 YY_RULE_SETUP
 { return F90PPR_IFNDEF; }
 	YY_BREAK
-case 29:
+case 30:
 YY_RULE_SETUP
 { return F90PPR_IF; }
 	YY_BREAK
-case 30:
+case 31:
 YY_RULE_SETUP
 { return F90PPR_ELIF; }
 	YY_BREAK
-case 31:
+case 32:
 YY_RULE_SETUP
 { return F90PPR_ELSE; }
 	YY_BREAK
-case 32:
+case 33:
 YY_RULE_SETUP
 { return F90PPR_ENDIF; }
 	YY_BREAK
 /* Line continuations, possible involving comments.  */
-case 33:
-/* rule 33 can match eol */
-YY_RULE_SETUP
-
-	YY_BREAK
 case 34:
 /* rule 34 can match eol */
 YY_RULE_SETUP
 
 	YY_BREAK
 case 35:
+/* rule 35 can match eol */
 YY_RULE_SETUP
-{ return COMMA; }
+
 	YY_BREAK
 case 36:
 YY_RULE_SETUP
-{ return DCOLON; }
+{ return COMMA; }
 	YY_BREAK
 case 37:
 YY_RULE_SETUP
-{ return COLON; }
+{ return DCOLON; }
 	YY_BREAK
 case 38:
-/* rule 38 can match eol */
+YY_RULE_SETUP
+{ return COLON; }
+	YY_BREAK
+case 39:
+/* rule 39 can match eol */
 YY_RULE_SETUP
 { return GARBAGE; }
 	YY_BREAK
-case 39:
+case 40:
 YY_RULE_SETUP
 { return ASSIGNMENT_OP; }
 	YY_BREAK
-case 40:
+case 41:
 YY_RULE_SETUP
 { return END; }
 	YY_BREAK
-case 41:
+case 42:
 YY_RULE_SETUP
 { return INCLUDE; }
 	YY_BREAK
-case 42:
+case 43:
 YY_RULE_SETUP
 { return INTERFACE; }
 	YY_BREAK
-case 43:
+case 44:
 YY_RULE_SETUP
 { return MODULE; }
 	YY_BREAK
-case 44:
+case 45:
 YY_RULE_SETUP
 { return SUBMODULE; }
 	YY_BREAK
-case 45:
+case 46:
 YY_RULE_SETUP
 { return USE; }
 	YY_BREAK
-case 46:
+case 47:
 YY_RULE_SETUP
 {
   yylvalp->string = strdup(yytext);
   return WORD;
 }
 	YY_BREAK
-case 47:
+case 48:
 YY_RULE_SETUP
 { return LPAREN; }
 	YY_BREAK
-case 48:
+case 49:
 YY_RULE_SETUP
 { return RPAREN; }
 	YY_BREAK
-case 49:
+case 50:
 YY_RULE_SETUP
 { return GARBAGE; }
 	YY_BREAK
-case 50:
-/* rule 50 can match eol */
+case 51:
+/* rule 51 can match eol */
 YY_RULE_SETUP
 { return EOSTMT; }
 	YY_BREAK
-case 51:
+case 52:
 YY_RULE_SETUP
 /* Ignore */
 	YY_BREAK
-case 52:
-/* rule 52 can match eol */
+case 53:
+/* rule 53 can match eol */
 YY_RULE_SETUP
 /* Ignore line-endings preceded by \ */
 	YY_BREAK
-case 53:
+case 54:
 YY_RULE_SETUP
 { return *yytext; }
 	YY_BREAK
@@ -1441,7 +1445,7 @@
     }
 }
 	YY_BREAK
-case 54:
+case 55:
 YY_RULE_SETUP
 ECHO;
 	YY_BREAK
diff --git a/Source/LexerParser/cmFortranLexer.in.l b/Source/LexerParser/cmFortranLexer.in.l
index fac3181..7d97699 100644
--- a/Source/LexerParser/cmFortranLexer.in.l
+++ b/Source/LexerParser/cmFortranLexer.in.l
@@ -102,6 +102,7 @@
 <fixed_fmt>^[cC*dD].*\n { return EOSTMT; } /* empty lines */
 
 ^[ \t]*#([ \t]*line)?[ \t]*[0-9]+[ \t]* { return CPP_LINE_DIRECTIVE; }
+^[ \t]*#[ \t]*line[ \t]* { return CPP_LINE_DIRECTIVE; }
 ^[ \t]*#[ \t]*include[ \t]*<[^>]+> {
   yytext[yyleng-1] = 0;
   yylvalp->string = strdup(strchr(yytext, '<')+1);
diff --git a/Source/Modules/CMakeBuildUtilities.cmake b/Source/Modules/CMakeBuildUtilities.cmake
index 5cfb0e7..c891fe9 100644
--- a/Source/Modules/CMakeBuildUtilities.cmake
+++ b/Source/Modules/CMakeBuildUtilities.cmake
@@ -54,7 +54,6 @@
   CMAKE_SET_TARGET_FOLDER(${KWSYS_NAMESPACE}TestProcess "${kwsys_folder}")
   CMAKE_SET_TARGET_FOLDER(${KWSYS_NAMESPACE}TestsC "${kwsys_folder}")
   CMAKE_SET_TARGET_FOLDER(${KWSYS_NAMESPACE}TestsCxx "${kwsys_folder}")
-  CMAKE_SET_TARGET_FOLDER(${KWSYS_NAMESPACE}TestSharedForward "${kwsys_folder}")
 endif()
 
 #---------------------------------------------------------------------
@@ -280,7 +279,7 @@
   set(ENABLE_LIBXML2 OFF)
   set(ENABLE_EXPAT OFF)
   set(ENABLE_PCREPOSIX OFF)
-  set(ENABLE_LibGCC OFF)
+  set(ENABLE_LIBGCC OFF)
   set(ENABLE_CNG OFF)
   set(ENABLE_TAR OFF)
   set(ENABLE_TAR_SHARED OFF)
@@ -377,3 +376,19 @@
     message(FATAL_ERROR "CMAKE_USE_SYSTEM_FORM in ON but CURSES_FORM_LIBRARY is not set!")
   endif()
 endif()
+
+#---------------------------------------------------------------------
+# Build cppdap library.
+if(CMake_ENABLE_DEBUGGER)
+  if(CMAKE_USE_SYSTEM_CPPDAP)
+    find_package(cppdap CONFIG)
+    if(NOT cppdap_FOUND)
+      message(FATAL_ERROR
+        "CMAKE_USE_SYSTEM_CPPDAP is ON but a cppdap is not found!")
+    endif()
+  else()
+    add_subdirectory(Utilities/cmcppdap)
+    add_library(cppdap::cppdap ALIAS cmcppdap)
+    CMAKE_SET_TARGET_FOLDER(cppdap "Utilities/3rdParty")
+  endif()
+endif()
diff --git a/Source/QtDialog/AddCacheEntry.cxx b/Source/QtDialog/AddCacheEntry.cxx
index 1075895..7c34c60 100644
--- a/Source/QtDialog/AddCacheEntry.cxx
+++ b/Source/QtDialog/AddCacheEntry.cxx
@@ -2,6 +2,7 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "AddCacheEntry.h"
 
+#include "QCMakeSizeType.h"
 #include <QCompleter>
 #include <QMetaProperty>
 
@@ -88,7 +89,7 @@
 
 void AddCacheEntry::onCompletionActivated(const QString& text)
 {
-  int idx = this->VarNames.indexOf(text);
+  cm_qsizetype idx = this->VarNames.indexOf(text);
   if (idx != -1) {
     QString vartype = this->VarTypes[idx];
     for (int i = 0; i < NumTypes; i++) {
diff --git a/Source/QtDialog/CMakeLists.txt b/Source/QtDialog/CMakeLists.txt
index c90b7ee..e6e41ec 100644
--- a/Source/QtDialog/CMakeLists.txt
+++ b/Source/QtDialog/CMakeLists.txt
@@ -175,6 +175,7 @@
   QCMakePresetComboBox.h
   QCMakePresetItemModel.cxx
   QCMakePresetItemModel.h
+  QCMakeSizeType.h
   QCMakeWidgets.cxx
   QCMakeWidgets.h
   RegexExplorer.cxx
diff --git a/Source/QtDialog/CMakeSetup.cxx b/Source/QtDialog/CMakeSetup.cxx
index 50e8e3a..21ed8c8 100644
--- a/Source/QtDialog/CMakeSetup.cxx
+++ b/Source/QtDialog/CMakeSetup.cxx
@@ -34,7 +34,7 @@
   "  cmake-gui [options] <path-to-source>\n"
   "  cmake-gui [options] <path-to-existing-build>\n"
   "  cmake-gui [options] -S <path-to-source> -B <path-to-build>\n"
-  "  cmake-gui [options] --browse-manual"
+  "  cmake-gui [options] --browse-manual [<filename>]"
 };
 
 const cmDocumentationEntry cmDocumentationOptions[3] = {
@@ -62,7 +62,7 @@
 
 int CMakeGUIExec(CMakeSetupDialog* window);
 void SetupDefaultQSettings();
-void OpenReferenceManual();
+void OpenReferenceManual(const QString& filename);
 
 int main(int argc, char** argv)
 {
@@ -199,7 +199,12 @@
       }
       presetName = preset.toStdString();
     } else if (arg == "--browse-manual") {
-      OpenReferenceManual();
+      ++i;
+      if (i >= args.size()) {
+        OpenReferenceManual("index.html");
+      } else {
+        OpenReferenceManual(args[i]);
+      }
       return 0;
     }
   }
diff --git a/Source/QtDialog/CMakeSetupDialog.cxx b/Source/QtDialog/CMakeSetupDialog.cxx
index 3d4d726..ab77818 100644
--- a/Source/QtDialog/CMakeSetupDialog.cxx
+++ b/Source/QtDialog/CMakeSetupDialog.cxx
@@ -32,6 +32,7 @@
 
 #include "QCMake.h"
 #include "QCMakeCacheView.h"
+#include "QCMakeSizeType.h"
 
 #include "cmSystemTools.h"
 #include "cmVersion.h"
@@ -42,7 +43,7 @@
 #include "RegexExplorer.h"
 #include "WarningMessagesDialog.h"
 
-void OpenReferenceManual()
+void OpenReferenceManual(const QString& filename)
 {
   QString urlFormat("https://cmake.org/cmake/help/v%1.%2/");
   QUrl url(urlFormat.arg(QString::number(cmVersion::GetMajorVersion()),
@@ -51,7 +52,7 @@
   if (!cmSystemTools::GetHTMLDoc().empty()) {
     url = QUrl::fromLocalFile(
       QDir(QString::fromStdString(cmSystemTools::GetHTMLDoc()))
-        .filePath("index.html"));
+        .filePath(filename));
   }
 
   QDesktopServices::openUrl(url);
@@ -212,7 +213,8 @@
   QObject::connect(a, &QAction::triggered, this, &CMakeSetupDialog::doHelp);
   a->setShortcut(QKeySequence::HelpContents);
   a = HelpMenu->addAction(tr("CMake Reference Manual"));
-  QObject::connect(a, &QAction::triggered, this, OpenReferenceManual);
+  QObject::connect(a, &QAction::triggered, this,
+                   [] { OpenReferenceManual("index.html"); });
   a = HelpMenu->addAction(tr("About"));
   QObject::connect(a, &QAction::triggered, this, &CMakeSetupDialog::doAbout);
 
@@ -730,13 +732,12 @@
   }
 }
 
-void CMakeSetupDialog::showPresetLoadError(
-  const QString& dir, cmCMakePresetsGraph::ReadFileResult result)
+void CMakeSetupDialog::showPresetLoadError(const QString& dir,
+                                           const QString& message)
 {
   QMessageBox::warning(
     this, "Error Reading CMake Presets",
-    QString("Could not read presets from %1: %2")
-      .arg(dir, cmCMakePresetsGraph::ResultToString(result)));
+    QString("Could not read presets from %1: %2").arg(dir, message));
 }
 
 void CMakeSetupDialog::doBinaryBrowse()
@@ -1119,12 +1120,12 @@
   QSettings settings;
   settings.beginGroup("Settings/StartPath");
 
-  int num = paths.count();
+  cm_qsizetype num = paths.count();
   if (num > 10) {
     num = 10;
   }
 
-  for (int i = 0; i < num; i++) {
+  for (cm_qsizetype i = 0; i < num; i++) {
     settings.setValue(QString("WhereBuild%1").arg(i), paths[i]);
   }
 }
diff --git a/Source/QtDialog/CMakeSetupDialog.h b/Source/QtDialog/CMakeSetupDialog.h
index 8aee70d..d4c72cb 100644
--- a/Source/QtDialog/CMakeSetupDialog.h
+++ b/Source/QtDialog/CMakeSetupDialog.h
@@ -59,8 +59,7 @@
   void updateBinaryDirectory(const QString& dir);
   void updatePresets(const QVector<QCMakePreset>& presets);
   void updatePreset(const QString& name);
-  void showPresetLoadError(const QString& dir,
-                           cmCMakePresetsGraph::ReadFileResult result);
+  void showPresetLoadError(const QString& dir, const QString& message);
   void showProgress(const QString& msg, float percent);
   void setEnabledState(bool);
   bool setupFirstConfigure();
diff --git a/Source/QtDialog/FirstConfigure.cxx b/Source/QtDialog/FirstConfigure.cxx
index 707eaae..a454cb6 100644
--- a/Source/QtDialog/FirstConfigure.cxx
+++ b/Source/QtDialog/FirstConfigure.cxx
@@ -1,6 +1,7 @@
 
 #include "FirstConfigure.h"
 
+#include "QCMakeSizeType.h"
 #include <QComboBox>
 #include <QRadioButton>
 #include <QSettings>
@@ -242,10 +243,12 @@
 
     // Default to generator platform from environment
     if (!DefaultGeneratorPlatform.isEmpty()) {
-      int platform_index = platforms.indexOf(DefaultGeneratorPlatform);
+      cm_qsizetype platform_index =
+        platforms.indexOf(DefaultGeneratorPlatform);
       if (platform_index != -1) {
         // The index is off-by-one due to the first empty item added above.
-        this->PlatformOptions->setCurrentIndex(platform_index + 1);
+        this->PlatformOptions->setCurrentIndex(
+          static_cast<int>(platform_index + 1));
       }
     }
   } else {
diff --git a/Source/QtDialog/QCMake.cxx b/Source/QtDialog/QCMake.cxx
index 6b3cb9f..f43f05f 100644
--- a/Source/QtDialog/QCMake.cxx
+++ b/Source/QtDialog/QCMake.cxx
@@ -6,6 +6,7 @@
 
 #include <cm/memory>
 
+#include "QCMakeSizeType.h"
 #include <QCoreApplication>
 #include <QDir>
 #include <QString>
@@ -32,7 +33,6 @@
   qRegisterMetaType<QCMakePropertyList>();
   qRegisterMetaType<QProcessEnvironment>();
   qRegisterMetaType<QVector<QCMakePreset>>();
-  qRegisterMetaType<cmCMakePresetsGraph::ReadFileResult>();
 
   cmSystemTools::DisableRunCommandOutput();
   cmSystemTools::SetRunCommandHideConsole(true);
@@ -326,7 +326,7 @@
 
     QCMakeProperty prop;
     prop.Key = QString::fromStdString(key);
-    int idx = props.indexOf(prop);
+    cm_qsizetype idx = props.indexOf(prop);
     if (idx == -1) {
       toremove.append(QString::fromStdString(key));
     } else {
@@ -359,19 +359,19 @@
     if (s.Type == QCMakeProperty::BOOL) {
       this->CMakeInstance->AddCacheEntry(
         s.Key.toStdString(), s.Value.toBool() ? "ON" : "OFF",
-        s.Help.toStdString().c_str(), cmStateEnums::BOOL);
+        s.Help.toStdString(), cmStateEnums::BOOL);
     } else if (s.Type == QCMakeProperty::STRING) {
       this->CMakeInstance->AddCacheEntry(
         s.Key.toStdString(), s.Value.toString().toStdString(),
-        s.Help.toStdString().c_str(), cmStateEnums::STRING);
+        s.Help.toStdString(), cmStateEnums::STRING);
     } else if (s.Type == QCMakeProperty::PATH) {
       this->CMakeInstance->AddCacheEntry(
         s.Key.toStdString(), s.Value.toString().toStdString(),
-        s.Help.toStdString().c_str(), cmStateEnums::PATH);
+        s.Help.toStdString(), cmStateEnums::PATH);
     } else if (s.Type == QCMakeProperty::FILEPATH) {
       this->CMakeInstance->AddCacheEntry(
         s.Key.toStdString(), s.Value.toString().toStdString(),
-        s.Help.toStdString().c_str(), cmStateEnums::FILEPATH);
+        s.Help.toStdString(), cmStateEnums::FILEPATH);
     }
   }
 
@@ -529,9 +529,11 @@
 {
   auto result = this->CMakePresetsGraph.ReadProjectPresets(
     this->SourceDirectory.toStdString(), true);
-  if (result != this->LastLoadPresetsResult &&
-      result != cmCMakePresetsGraph::ReadFileResult::READ_OK) {
-    emit this->presetLoadError(this->SourceDirectory, result);
+  if (result != this->LastLoadPresetsResult && !result) {
+    emit this->presetLoadError(
+      this->SourceDirectory,
+      QString::fromStdString(
+        this->CMakePresetsGraph.parseState.GetErrorMessage(false)));
   }
   this->LastLoadPresetsResult = result;
 
diff --git a/Source/QtDialog/QCMake.h b/Source/QtDialog/QCMake.h
index 8a7e4cb..0890558 100644
--- a/Source/QtDialog/QCMake.h
+++ b/Source/QtDialog/QCMake.h
@@ -60,7 +60,6 @@
 Q_DECLARE_METATYPE(QCMakeProperty)
 Q_DECLARE_METATYPE(QCMakePropertyList)
 Q_DECLARE_METATYPE(QProcessEnvironment)
-Q_DECLARE_METATYPE(cmCMakePresetsGraph::ReadFileResult)
 
 /// Qt API for CMake library.
 /// Wrapper like class allows for easier integration with
@@ -158,8 +157,7 @@
   /// signal when the selected preset changes
   void presetChanged(const QString& name);
   /// signal when there's an error reading the presets files
-  void presetLoadError(const QString& dir,
-                       cmCMakePresetsGraph::ReadFileResult error);
+  void presetLoadError(const QString& dir, const QString& error);
   /// signal when uninitialized warning changes
   void warnUninitializedModeChanged(bool value);
   /// signal for progress events
@@ -203,8 +201,7 @@
   QString Toolset;
   std::vector<cmake::GeneratorInfo> AvailableGenerators;
   cmCMakePresetsGraph CMakePresetsGraph;
-  cmCMakePresetsGraph::ReadFileResult LastLoadPresetsResult =
-    cmCMakePresetsGraph::ReadFileResult::READ_OK;
+  bool LastLoadPresetsResult = true;
   QString PresetName;
   QString CMakeExecutable;
   QAtomicInt InterruptFlag;
diff --git a/Source/QtDialog/QCMakeCacheView.cxx b/Source/QtDialog/QCMakeCacheView.cxx
index 6f19b67..e67e0c2 100644
--- a/Source/QtDialog/QCMakeCacheView.cxx
+++ b/Source/QtDialog/QCMakeCacheView.cxx
@@ -2,6 +2,7 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "QCMakeCacheView.h"
 
+#include "QCMakeSizeType.h"
 #include "QCMakeWidgets.h"
 #include <QApplication>
 #include <QEvent>
@@ -188,7 +189,7 @@
 
 static uint qHash(const QCMakeProperty& p)
 {
-  return qHash(p.Key);
+  return static_cast<uint>(qHash(p.Key));
 }
 
 void QCMakeCacheModel::setShowNewProperties(bool f)
@@ -241,7 +242,7 @@
   bool b = this->blockSignals(true);
 
   this->clear();
-  this->NewPropertyCount = newProps.size();
+  this->NewPropertyCount = static_cast<int>(newProps.size());
 
   if (View == FlatView) {
     QCMakePropertyList newP = newProps.values();
@@ -297,8 +298,8 @@
       parentItems[1]->setData(1, GroupRole);
       root->appendRow(parentItems);
 
-      int num = props2.size();
-      for (int i = 0; i < num; i++) {
+      cm_qsizetype num = props2.size();
+      for (cm_qsizetype i = 0; i < num; i++) {
         QCMakeProperty const& prop = props2[i];
         QList<QStandardItem*> items;
         items.append(new QStandardItem());
@@ -319,8 +320,8 @@
       root->appendRow(parentItem);
       parentItem->setData(1, GroupRole);
 
-      int num = props2.size();
-      for (int i = 0; i < num; i++) {
+      cm_qsizetype num = props2.size();
+      for (cm_qsizetype i = 0; i < num; i++) {
         QCMakeProperty const& prop = props2[i];
         QList<QStandardItem*> items;
         items.append(new QStandardItem());
@@ -349,8 +350,8 @@
   QCMakePropertyList props = this->properties();
   QCMakePropertyList oldProps;
   int numNew = this->NewPropertyCount;
-  int numTotal = props.count();
-  for (int i = numNew; i < numTotal; i++) {
+  cm_qsizetype numTotal = props.count();
+  for (cm_qsizetype i = numNew; i < numTotal; i++) {
     oldProps.append(props[i]);
   }
 
diff --git a/Source/QtDialog/QCMakePresetItemModel.cxx b/Source/QtDialog/QCMakePresetItemModel.cxx
index 7ada2a5..31a6000 100644
--- a/Source/QtDialog/QCMakePresetItemModel.cxx
+++ b/Source/QtDialog/QCMakePresetItemModel.cxx
@@ -2,6 +2,7 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "QCMakePresetItemModel.h"
 
+#include "QCMakeSizeType.h"
 #include <QFont>
 
 QCMakePresetItemModel::QCMakePresetItemModel(QObject* parent)
@@ -27,7 +28,8 @@
       if (index.internalId() == SEPARATOR_INDEX) {
         return QVariant{};
       }
-      auto const& preset = this->m_presets[index.internalId()];
+      auto const& preset =
+        this->m_presets[static_cast<cm_qsizetype>(index.internalId())];
       return preset.displayName.isEmpty() ? preset.name : preset.displayName;
     }
     case Qt::ToolTipRole:
@@ -37,7 +39,8 @@
       if (index.internalId() == SEPARATOR_INDEX) {
         return QVariant{};
       }
-      return this->m_presets[index.internalId()].description;
+      return this->m_presets[static_cast<cm_qsizetype>(index.internalId())]
+        .description;
     case Qt::UserRole:
       if (index.internalId() == CUSTOM_INDEX) {
         return QVariant{};
@@ -45,7 +48,8 @@
       if (index.internalId() == SEPARATOR_INDEX) {
         return QVariant{};
       }
-      return QVariant::fromValue(this->m_presets[index.internalId()]);
+      return QVariant::fromValue(
+        this->m_presets[static_cast<cm_qsizetype>(index.internalId())]);
     case Qt::FontRole:
       if (index.internalId() == CUSTOM_INDEX) {
         QFont font;
@@ -64,7 +68,8 @@
     Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
   if (index.internalId() != SEPARATOR_INDEX &&
       (index.internalId() == CUSTOM_INDEX ||
-       this->m_presets[index.internalId()].enabled)) {
+       this->m_presets[static_cast<cm_qsizetype>(index.internalId())]
+         .enabled)) {
     flags |= Qt::ItemIsSelectable | Qt::ItemIsEnabled;
   }
   return flags;
@@ -78,7 +83,7 @@
   if (this->m_presets.empty()) {
     return 1;
   }
-  return this->m_presets.size() + 2;
+  return static_cast<int>(this->m_presets.size() + 2);
 }
 
 int QCMakePresetItemModel::columnCount(const QModelIndex& parent) const
@@ -139,5 +144,5 @@
     index++;
   }
 
-  return this->m_presets.size() + 1;
+  return static_cast<int>(this->m_presets.size() + 1);
 }
diff --git a/Source/QtDialog/QCMakeSizeType.h b/Source/QtDialog/QCMakeSizeType.h
new file mode 100644
index 0000000..b5cac17
--- /dev/null
+++ b/Source/QtDialog/QCMakeSizeType.h
@@ -0,0 +1,12 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <QtGlobal>
+
+// The signed integer type that Qt uses for indexing.
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+using cm_qsizetype = qsizetype;
+#else
+using cm_qsizetype = int;
+#endif
diff --git a/Source/bindexplib.cxx b/Source/bindexplib.cxx
index 52e200c..3495aed 100644
--- a/Source/bindexplib.cxx
+++ b/Source/bindexplib.cxx
@@ -281,6 +281,7 @@
           // the symbol
           const char* scalarPrefix = "??_G";
           const char* vectorPrefix = "??_E";
+          const char* vftablePrefix = "??_7";
           // The original code had a check for
           //     symbol.find("real@") == std::string::npos)
           // but this disallows member functions with the name "real".
@@ -302,7 +303,8 @@
                   this->DataSymbols.insert(symbol);
                 } else {
                   if (pSymbolTable->Type || !(SectChar & IMAGE_SCN_MEM_READ) ||
-                      (SectChar & IMAGE_SCN_MEM_EXECUTE)) {
+                      (SectChar & IMAGE_SCN_MEM_EXECUTE) ||
+                      (symbol.compare(0, 4, vftablePrefix) == 0)) {
                     this->Symbols.insert(symbol);
                   }
                 }
@@ -406,7 +408,7 @@
   LPVOID lpFileBase;
 
   hFile = CreateFileW(cmsys::Encoding::ToWide(filename).c_str(), GENERIC_READ,
-                      FILE_SHARE_READ, NULL, OPEN_EXISTING,
+                      FILE_SHARE_READ, nullptr, OPEN_EXISTING,
                       FILE_ATTRIBUTE_NORMAL, 0);
 
   if (hFile == INVALID_HANDLE_VALUE) {
@@ -414,7 +416,8 @@
     return false;
   }
 
-  hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
+  hFileMapping =
+    CreateFileMapping(hFile, nullptr, PAGE_READONLY, 0, 0, nullptr);
   if (hFileMapping == 0) {
     CloseHandle(hFile);
     fprintf(stderr, "Couldn't open file mapping with CreateFileMapping()\n");
diff --git a/Source/cmAddCustomCommandCommand.cxx b/Source/cmAddCustomCommandCommand.cxx
index 831e9c7..b1398db 100644
--- a/Source/cmAddCustomCommandCommand.cxx
+++ b/Source/cmAddCustomCommandCommand.cxx
@@ -49,6 +49,8 @@
   bool append = false;
   bool uses_terminal = false;
   bool command_expand_lists = false;
+  bool depends_explicit_only =
+    mf.IsOn("CMAKE_ADD_CUSTOM_COMMAND_DEPENDS_EXPLICIT_ONLY");
   std::string implicit_depends_lang;
   cmImplicitDependsList implicit_depends;
 
@@ -104,6 +106,7 @@
   MAKE_STATIC_KEYWORD(USES_TERMINAL);
   MAKE_STATIC_KEYWORD(VERBATIM);
   MAKE_STATIC_KEYWORD(WORKING_DIRECTORY);
+  MAKE_STATIC_KEYWORD(DEPENDS_EXPLICIT_ONLY);
 #undef MAKE_STATIC_KEYWORD
   static std::unordered_set<std::string> const keywords{
     keyAPPEND,
@@ -126,7 +129,8 @@
     keyTARGET,
     keyUSES_TERMINAL,
     keyVERBATIM,
-    keyWORKING_DIRECTORY
+    keyWORKING_DIRECTORY,
+    keyDEPENDS_EXPLICIT_ONLY
   };
 
   for (std::string const& copy : args) {
@@ -155,6 +159,8 @@
         uses_terminal = true;
       } else if (copy == keyCOMMAND_EXPAND_LISTS) {
         command_expand_lists = true;
+      } else if (copy == keyDEPENDS_EXPLICIT_ONLY) {
+        depends_explicit_only = true;
       } else if (copy == keyTARGET) {
         doing = doing_target;
       } else if (copy == keyARGS) {
@@ -329,6 +335,7 @@
   cc->SetDepfile(depfile);
   cc->SetJobPool(job_pool);
   cc->SetCommandExpandLists(command_expand_lists);
+  cc->SetDependsExplicitOnly(depends_explicit_only);
   if (source.empty() && output.empty()) {
     // Source is empty, use the target.
     mf.AddCustomCommandToTarget(target, cctype, std::move(cc));
diff --git a/Source/cmAlgorithms.h b/Source/cmAlgorithms.h
index a1830f9..2c3ee9b 100644
--- a/Source/cmAlgorithms.h
+++ b/Source/cmAlgorithms.h
@@ -95,7 +95,7 @@
 }
 
 template <typename Range, typename MatchRange>
-typename Range::const_iterator cmRemoveMatching(Range& r, MatchRange const& m)
+auto cmRemoveMatching(Range& r, MatchRange const& m) -> decltype(r.begin())
 {
   return std::remove_if(r.begin(), r.end(),
                         ContainerAlgorithms::BinarySearcher<MatchRange>(m));
diff --git a/Source/cmBinUtilsWindowsPELinker.cxx b/Source/cmBinUtilsWindowsPELinker.cxx
index 79e39e9..918f563 100644
--- a/Source/cmBinUtilsWindowsPELinker.cxx
+++ b/Source/cmBinUtilsWindowsPELinker.cxx
@@ -3,7 +3,10 @@
 
 #include "cmBinUtilsWindowsPELinker.h"
 
+#include <algorithm>
+#include <iterator>
 #include <sstream>
+#include <utility>
 #include <vector>
 
 #include <cm/memory>
@@ -16,6 +19,27 @@
 
 #ifdef _WIN32
 #  include <windows.h>
+
+#  include "cmsys/Encoding.hxx"
+#endif
+
+#ifdef _WIN32
+namespace {
+
+void ReplaceWithActualNameCasing(std::string& path)
+{
+  WIN32_FIND_DATAW findData;
+  HANDLE hFind = ::FindFirstFileW(
+    cmsys::Encoding::ToWindowsExtendedPath(path).c_str(), &findData);
+
+  if (hFind != INVALID_HANDLE_VALUE) {
+    auto onDiskName = cmsys::Encoding::ToNarrow(findData.cFileName);
+    ::FindClose(hFind);
+    path.replace(path.end() - onDiskName.size(), path.end(), onDiskName);
+  }
+}
+
+}
 #endif
 
 cmBinUtilsWindowsPELinker::cmBinUtilsWindowsPELinker(
@@ -60,29 +84,47 @@
   if (!this->Tool->GetFileInfo(file, needed)) {
     return false;
   }
-  for (auto& n : needed) {
-    n = cmSystemTools::LowerCase(n);
-  }
+
+  struct WinPEDependency
+  {
+    WinPEDependency(std::string o)
+      : Original(std::move(o))
+      , LowerCase(cmSystemTools::LowerCase(Original))
+    {
+    }
+    std::string const Original;
+    std::string const LowerCase;
+  };
+
+  std::vector<WinPEDependency> depends;
+  depends.reserve(needed.size());
+  std::move(needed.begin(), needed.end(), std::back_inserter(depends));
   std::string origin = cmSystemTools::GetFilenamePath(file);
 
-  for (auto const& lib : needed) {
-    if (!this->Archive->IsPreExcluded(lib)) {
+  for (auto const& lib : depends) {
+    if (!this->Archive->IsPreExcluded(lib.LowerCase)) {
       std::string path;
       bool resolved = false;
-      if (!this->ResolveDependency(lib, origin, path, resolved)) {
+      if (!this->ResolveDependency(lib.LowerCase, origin, path, resolved)) {
         return false;
       }
       if (resolved) {
         if (!this->Archive->IsPostExcluded(path)) {
+#ifdef _WIN32
+          ReplaceWithActualNameCasing(path);
+#else
+          path.replace(path.end() - lib.Original.size(), path.end(),
+                       lib.Original);
+#endif
           bool unique;
-          this->Archive->AddResolvedPath(lib, path, unique);
+          this->Archive->AddResolvedPath(lib.Original, path, unique);
           if (unique &&
               !this->ScanDependencies(path, cmStateEnums::SHARED_LIBRARY)) {
             return false;
           }
         }
       } else {
-        this->Archive->AddUnresolvedPath(lib);
+        this->Archive->AddUnresolvedPath(lib.Original);
       }
     }
   }
diff --git a/Source/cmCMakeHostSystemInformationCommand.cxx b/Source/cmCMakeHostSystemInformationCommand.cxx
index 58129a0..1c00f15 100644
--- a/Source/cmCMakeHostSystemInformationCommand.cxx
+++ b/Source/cmCMakeHostSystemInformationCommand.cxx
@@ -8,7 +8,6 @@
 #include <initializer_list>
 #include <map>
 #include <string>
-#include <type_traits>
 #include <utility>
 
 #include <cm/optional>
@@ -21,10 +20,12 @@
 
 #include "cmArgumentParser.h"
 #include "cmExecutionStatus.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmRange.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
+#include "cmValue.h"
 #include "cmWindowsRegistry.h"
 
 #ifdef _WIN32
@@ -303,7 +304,8 @@
   }
 
   // 2. User provided (append to the CMake prvided)
-  makefile.GetDefExpandList("CMAKE_GET_OS_RELEASE_FALLBACK_SCRIPTS", scripts);
+  cmList::append(
+    scripts, makefile.GetDefinition("CMAKE_GET_OS_RELEASE_FALLBACK_SCRIPTS"));
 
   // Filter out files that are not in format `NNN-name.cmake`
   auto checkName = [](std::string const& filepath) -> bool {
@@ -330,11 +332,11 @@
             });
 
   // Name of the variable to put the results
-  auto const result_variable = "CMAKE_GET_OS_RELEASE_FALLBACK_RESULT"_s;
+  std::string const result_variable{ "CMAKE_GET_OS_RELEASE_FALLBACK_RESULT" };
 
   for (auto const& script : scripts) {
     // Unset the result variable
-    makefile.RemoveDefinition(result_variable.data());
+    makefile.RemoveDefinition(result_variable);
 
     // include FATAL_ERROR and ERROR in the return status
     if (!makefile.ReadListFile(script) ||
@@ -343,8 +345,8 @@
       continue;
     }
 
-    std::vector<std::string> variables;
-    if (!makefile.GetDefExpandList(result_variable.data(), variables)) {
+    cmList variables{ makefile.GetDefinition(result_variable) };
+    if (variables.empty()) {
       // Heh, this script didn't found anything... go try the next one.
       continue;
     }
@@ -370,7 +372,7 @@
     }
   }
 
-  makefile.RemoveDefinition(result_variable.data());
+  makefile.RemoveDefinition(result_variable);
 
   return data;
 }
diff --git a/Source/cmCMakeLanguageCommand.cxx b/Source/cmCMakeLanguageCommand.cxx
index 68e658c..c7e9209 100644
--- a/Source/cmCMakeLanguageCommand.cxx
+++ b/Source/cmCMakeLanguageCommand.cxx
@@ -303,7 +303,7 @@
   state->SetDependencyProvider({ parsedArgs.Command, methods });
   state->SetGlobalProperty(
     fcmasProperty,
-    supportsFetchContentMakeAvailableSerial ? parsedArgs.Command.c_str() : "");
+    supportsFetchContentMakeAvailableSerial ? parsedArgs.Command : "");
 
   return true;
 }
diff --git a/Source/cmCMakePathCommand.cxx b/Source/cmCMakePathCommand.cxx
index 7755082..0c8f537 100644
--- a/Source/cmCMakePathCommand.cxx
+++ b/Source/cmCMakePathCommand.cxx
@@ -18,6 +18,7 @@
 #include "cmArgumentParserTypes.h"
 #include "cmCMakePath.h"
 #include "cmExecutionStatus.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmRange.h"
 #include "cmStringAlgorithms.h"
@@ -626,12 +627,12 @@
     return false;
   }
 
-  std::vector<std::string> paths;
+  cmList paths;
 
   if (action == cmakePath) {
     paths = cmSystemTools::SplitString(args[1], pathSep.front());
   } else {
-    cmExpandList(args[1], paths);
+    paths.assign(args[1]);
   }
 
   for (auto& path : paths) {
@@ -648,7 +649,7 @@
     }
   }
 
-  auto value = cmJoin(paths, action == cmakePath ? ";"_s : pathSep);
+  auto value = action == cmakePath ? paths.to_string() : paths.join(pathSep);
   status.GetMakefile().AddDefinition(args[3], value);
 
   return true;
diff --git a/Source/cmCMakePresetErrors.h b/Source/cmCMakePresetErrors.h
new file mode 100644
index 0000000..0db391e
--- /dev/null
+++ b/Source/cmCMakePresetErrors.h
@@ -0,0 +1,245 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <cm3p/json/value.h>
+
+#include "cmJSONHelpers.h"
+#include "cmJSONState.h"
+#include "cmStringAlgorithms.h"
+
+namespace cmCMakePresetErrors {
+const auto getPreset = [](cmJSONState* state) -> const Json::Value* {
+  if (state->parseStack.size() < 2) {
+    return nullptr;
+  }
+  std::string firstKey = state->parseStack[0].first;
+  if (firstKey == "configurePresets" || firstKey == "packagePresets" ||
+      firstKey == "buildPresets" || firstKey == "testPresets") {
+    return state->parseStack[1].second;
+  }
+  return nullptr;
+};
+const auto getPresetName = [](cmJSONState* state) -> std::string {
+#if !defined(CMAKE_BOOTSTRAP)
+  const Json::Value* preset = getPreset(state);
+  if (preset != nullptr && preset->isMember("name")) {
+    return preset->operator[]("name").asString();
+  }
+#endif
+  return "";
+};
+const auto getVariableName = [](cmJSONState* state) -> std::string {
+  std::string var = state->key_after("cacheVariables");
+  std::string errMsg = cmStrCat("variable \"", var, "\"");
+  errMsg = cmStrCat(errMsg, " for preset \"", getPresetName(state), "\"");
+  return errMsg;
+};
+const auto FILE_NOT_FOUND = [](const std::string& filename,
+                               cmJSONState* state) -> void {
+  state->AddError(cmStrCat("File not found: ", filename));
+};
+const auto INVALID_ROOT = [](const Json::Value* value,
+                             cmJSONState* state) -> void {
+  state->AddErrorAtValue("Invalid root object", value);
+};
+const auto NO_VERSION = [](const Json::Value* value,
+                           cmJSONState* state) -> void {
+  state->AddErrorAtValue("No \"version\" field", value);
+};
+const auto INVALID_VERSION = [](const Json::Value* value,
+                                cmJSONState* state) -> void {
+  state->AddErrorAtValue("Invalid \"version\" field", value);
+};
+const auto UNRECOGNIZED_VERSION = [](const Json::Value* value,
+                                     cmJSONState* state) -> void {
+  state->AddErrorAtValue("Unrecognized \"version\" field", value);
+};
+const auto INVALID_PRESETS = [](const Json::Value* value,
+                                cmJSONState* state) -> void {
+  state->AddErrorAtValue("Invalid \"configurePresets\" field", value);
+};
+const auto INVALID_PRESET = [](const Json::Value* value,
+                               cmJSONState* state) -> void {
+  state->AddErrorAtValue("Invalid preset", value);
+};
+const auto INVALID_PRESET_NAMED = [](const std::string& presetName,
+                                     cmJSONState* state) -> void {
+  state->AddError(cmStrCat("Invalid preset: \"", presetName, "\""));
+};
+const auto INVALID_VARIABLE = [](const Json::Value* value,
+                                 cmJSONState* state) -> void {
+  std::string var = cmCMakePresetErrors::getVariableName(state);
+  state->AddErrorAtValue(cmStrCat("Invalid CMake ", var), value);
+};
+const auto DUPLICATE_PRESETS = [](const std::string& presetName,
+                                  cmJSONState* state) -> void {
+  state->AddError(cmStrCat("Duplicate preset: \"", presetName, "\""));
+};
+const auto CYCLIC_PRESET_INHERITANCE = [](const std::string& presetName,
+                                          cmJSONState* state) -> void {
+  state->AddError(
+    cmStrCat("Cyclic preset inheritance for preset \"", presetName, "\""));
+};
+const auto INHERITED_PRESET_UNREACHABLE_FROM_FILE =
+  [](const std::string& presetName, cmJSONState* state) -> void {
+  state->AddError(cmStrCat("Inherited preset \"", presetName,
+                           "\" is unreachable from preset's file"));
+};
+const auto CONFIGURE_PRESET_UNREACHABLE_FROM_FILE =
+  [](const std::string& presetName, cmJSONState* state) -> void {
+  state->AddError(cmStrCat("Configure preset \"", presetName,
+                           "\" is unreachable from preset's file"));
+};
+const auto INVALID_MACRO_EXPANSION = [](const std::string& presetName,
+                                        cmJSONState* state) -> void {
+  state->AddError(cmStrCat("Invalid macro expansion in \"", presetName, "\""));
+};
+const auto BUILD_TEST_PRESETS_UNSUPPORTED = [](const Json::Value*,
+                                               cmJSONState* state) -> void {
+  state->AddError("File version must be 2 or higher for build and test preset "
+                  "support");
+};
+const auto PACKAGE_PRESETS_UNSUPPORTED = [](const Json::Value*,
+                                            cmJSONState* state) -> void {
+  state->AddError(
+    "File version must be 6 or higher for package preset support");
+};
+const auto WORKFLOW_PRESETS_UNSUPPORTED = [](const Json::Value*,
+                                             cmJSONState* state) -> void {
+  state->AddError(
+    "File version must be 6 or higher for workflow preset support");
+};
+const auto INCLUDE_UNSUPPORTED = [](const Json::Value*,
+                                    cmJSONState* state) -> void {
+  state->AddError("File version must be 4 or higher for include support");
+};
+const auto INVALID_INCLUDE = [](const Json::Value* value,
+                                cmJSONState* state) -> void {
+  state->AddErrorAtValue("Invalid \"include\" field", value);
+};
+const auto INVALID_CONFIGURE_PRESET = [](const std::string& presetName,
+                                         cmJSONState* state) -> void {
+  state->AddError(
+    cmStrCat(R"(Invalid "configurePreset": ")", presetName, "\""));
+};
+const auto INSTALL_PREFIX_UNSUPPORTED = [](const Json::Value* value,
+                                           cmJSONState* state) -> void {
+  state->AddErrorAtValue(
+    "File version must be 3 or higher for installDir preset "
+    "support",
+    value);
+};
+const auto CONDITION_UNSUPPORTED = [](cmJSONState* state) -> void {
+  state->AddError("File version must be 3 or higher for condition support");
+};
+const auto TOOLCHAIN_FILE_UNSUPPORTED = [](cmJSONState* state) -> void {
+  state->AddError("File version must be 3 or higher for toolchainFile preset "
+                  "support");
+};
+const auto CYCLIC_INCLUDE = [](const std::string& file,
+                               cmJSONState* state) -> void {
+  state->AddError(cmStrCat("Cyclic include among preset files: ", file));
+};
+const auto TEST_OUTPUT_TRUNCATION_UNSUPPORTED =
+  [](cmJSONState* state) -> void {
+  state->AddError("File version must be 5 or higher for testOutputTruncation "
+                  "preset support");
+};
+const auto INVALID_WORKFLOW_STEPS = [](const std::string& workflowStep,
+                                       cmJSONState* state) -> void {
+  state->AddError(cmStrCat("Invalid workflow step \"", workflowStep, "\""));
+};
+const auto NO_WORKFLOW_STEPS = [](const std::string& presetName,
+                                  cmJSONState* state) -> void {
+  state->AddError(
+    cmStrCat("No workflow steps specified for \"", presetName, "\""));
+};
+const auto FIRST_WORKFLOW_STEP_NOT_CONFIGURE = [](const std::string& stepName,
+                                                  cmJSONState* state) -> void {
+  state->AddError(cmStrCat("First workflow step \"", stepName,
+                           "\" must be a configure step"));
+};
+const auto CONFIGURE_WORKFLOW_STEP_NOT_FIRST = [](const std::string& stepName,
+                                                  cmJSONState* state) -> void {
+  state->AddError(cmStrCat("Configure workflow step \"", stepName,
+                           "\" must be the first step"));
+};
+const auto WORKFLOW_STEP_UNREACHABLE_FROM_FILE =
+  [](const std::string& workflowStep, cmJSONState* state) -> void {
+  state->AddError(cmStrCat("Workflow step \"", workflowStep,
+                           "\" is unreachable from preset's file"));
+};
+const auto CTEST_JUNIT_UNSUPPORTED = [](cmJSONState* state) -> void {
+  state->AddError(
+    "File version must be 6 or higher for CTest JUnit output support");
+};
+const auto TRACE_UNSUPPORTED = [](cmJSONState* state) -> void {
+  state->AddError("File version must be 7 or higher for trace preset support");
+};
+const auto UNRECOGNIZED_CMAKE_VERSION = [](const std::string& version,
+                                           int current, int required) {
+  return [version, current, required](const Json::Value* value,
+                                      cmJSONState* state) -> void {
+    state->AddErrorAtValue(cmStrCat("\"cmakeMinimumRequired\" ", version,
+                                    " version ", required,
+                                    " must be less than ", current),
+                           value);
+  };
+};
+const auto INVALID_PRESET_NAME = [](const Json::Value* value,
+                                    cmJSONState* state) -> void {
+  std::string errMsg = "Invalid Preset Name";
+  if (value && value->isConvertibleTo(Json::ValueType::stringValue) &&
+      !value->asString().empty()) {
+    errMsg = cmStrCat(errMsg, ": ", value->asString());
+  }
+  state->AddErrorAtValue(errMsg, value);
+};
+const auto INVALID_CONDITION = [](const Json::Value* value,
+                                  cmJSONState* state) -> void {
+  state->AddErrorAtValue(
+    cmStrCat("Invalid condition for preset \"", getPresetName(state), "\""),
+    value);
+};
+const auto INVALID_CONDITION_OBJECT =
+  [](JsonErrors::ObjectError errorType,
+     const Json::Value::Members& extraFields) {
+    return JsonErrors::INVALID_NAMED_OBJECT(
+      [](const Json::Value*, cmJSONState* state) -> std::string {
+        return cmStrCat(" condition for preset \"", getPresetName(state),
+                        "\"");
+      })(errorType, extraFields);
+  };
+const auto INVALID_VARIABLE_OBJECT =
+  [](JsonErrors::ObjectError errorType,
+     const Json::Value::Members& extraFields) {
+    return JsonErrors::INVALID_NAMED_OBJECT(
+      [](const Json::Value*, cmJSONState* state) -> std::string {
+        return getVariableName(state);
+      })(errorType, extraFields);
+  };
+const auto INVALID_PRESET_OBJECT =
+  [](JsonErrors::ObjectError errorType,
+     const Json::Value::Members& extraFields) {
+    return JsonErrors::INVALID_NAMED_OBJECT(
+      [](const Json::Value*, cmJSONState*) -> std::string {
+        return "Preset";
+      })(errorType, extraFields);
+  };
+const auto INVALID_ROOT_OBJECT = [](JsonErrors::ObjectError errorType,
+                                    const Json::Value::Members& extraFields) {
+  return JsonErrors::INVALID_NAMED_OBJECT(
+    [](const Json::Value*, cmJSONState*) -> std::string {
+      return "root object";
+    })(errorType, extraFields);
+};
+const auto PRESET_MISSING_FIELD = [](const std::string& presetName,
+                                     const std::string& missingField,
+                                     cmJSONState* state) {
+  state->AddError(cmStrCat("Preset \"", presetName, "\" missing field \"",
+                           missingField, "\""));
+};
+}
diff --git a/Source/cmCMakePresetsGraph.cxx b/Source/cmCMakePresetsGraph.cxx
index 7325e44..13eddbe 100644
--- a/Source/cmCMakePresetsGraph.cxx
+++ b/Source/cmCMakePresetsGraph.cxx
@@ -4,7 +4,6 @@
 
 #include <algorithm>
 #include <cassert>
-#include <cstdlib>
 #include <functional>
 #include <iostream>
 #include <iterator>
@@ -14,6 +13,7 @@
 
 #include "cmsys/RegularExpression.hxx"
 
+#include "cmCMakePresetErrors.h"
 #include "cmCMakePresetsGraphInternal.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
@@ -39,7 +39,6 @@
   Verified,
 };
 
-using ReadFileResult = cmCMakePresetsGraph::ReadFileResult;
 using ConfigurePreset = cmCMakePresetsGraph::ConfigurePreset;
 using BuildPreset = cmCMakePresetsGraph::BuildPreset;
 using TestPreset = cmCMakePresetsGraph::TestPreset;
@@ -49,6 +48,7 @@
 using PresetPair = cmCMakePresetsGraph::PresetPair<T>;
 using ExpandMacroResult = cmCMakePresetsGraphInternal::ExpandMacroResult;
 using MacroExpander = cmCMakePresetsGraphInternal::MacroExpander;
+using cmCMakePresetsGraphInternal::ExpandMacros;
 
 void InheritString(std::string& child, const std::string& parent)
 {
@@ -81,17 +81,18 @@
  * inheritance.
  */
 template <class T>
-ReadFileResult VisitPreset(
+bool VisitPreset(
   T& preset,
   std::map<std::string, cmCMakePresetsGraph::PresetPair<T>>& presets,
-  std::map<std::string, CycleStatus> cycleStatus,
-  const cmCMakePresetsGraph& graph)
+  std::map<std::string, CycleStatus> cycleStatus, cmCMakePresetsGraph& graph)
 {
   switch (cycleStatus[preset.Name]) {
     case CycleStatus::InProgress:
-      return ReadFileResult::CYCLIC_PRESET_INHERITANCE;
+      cmCMakePresetErrors::CYCLIC_PRESET_INHERITANCE(preset.Name,
+                                                     &graph.parseState);
+      return false;
     case CycleStatus::Verified:
-      return ReadFileResult::READ_OK;
+      return true;
     default:
       break;
   }
@@ -99,28 +100,41 @@
   cycleStatus[preset.Name] = CycleStatus::InProgress;
 
   if (preset.Environment.count("") != 0) {
-    return ReadFileResult::INVALID_PRESET;
+    cmCMakePresetErrors::INVALID_PRESET_NAMED(preset.Name, &graph.parseState);
+    return false;
   }
 
-  CHECK_OK(preset.VisitPresetBeforeInherit());
+  bool result = preset.VisitPresetBeforeInherit();
+  if (!result) {
+    cmCMakePresetErrors::INVALID_PRESET_NAMED(preset.Name, &graph.parseState);
+    return false;
+  }
 
   for (auto const& i : preset.Inherits) {
     auto parent = presets.find(i);
     if (parent == presets.end()) {
-      return ReadFileResult::INVALID_PRESET;
+      cmCMakePresetErrors::INVALID_PRESET_NAMED(preset.Name,
+                                                &graph.parseState);
+      return false;
     }
 
     auto& parentPreset = parent->second.Unexpanded;
     if (!preset.OriginFile->ReachableFiles.count(parentPreset.OriginFile)) {
-      return ReadFileResult::INHERITED_PRESET_UNREACHABLE_FROM_FILE;
+      cmCMakePresetErrors::INHERITED_PRESET_UNREACHABLE_FROM_FILE(
+        preset.Name, &graph.parseState);
+      return false;
     }
 
-    auto result = VisitPreset(parentPreset, presets, cycleStatus, graph);
-    if (result != ReadFileResult::READ_OK) {
-      return result;
+    if (!VisitPreset(parentPreset, presets, cycleStatus, graph)) {
+      return false;
     }
 
-    CHECK_OK(preset.VisitPresetInherit(parentPreset));
+    result = preset.VisitPresetInherit(parentPreset);
+    if (!result) {
+      cmCMakePresetErrors::INVALID_PRESET_NAMED(preset.Name,
+                                                &graph.parseState);
+      return false;
+    }
 
     for (auto const& v : parentPreset.Environment) {
       preset.Environment.insert(v);
@@ -135,16 +149,21 @@
     preset.ConditionEvaluator.reset();
   }
 
-  CHECK_OK(preset.VisitPresetAfterInherit(graph.GetVersion(preset)));
+  result = preset.VisitPresetAfterInherit(graph.GetVersion(preset),
+                                          &graph.parseState);
+  if (!result) {
+    cmCMakePresetErrors::INVALID_PRESET_NAMED(preset.Name, &graph.parseState);
+    return false;
+  }
 
   cycleStatus[preset.Name] = CycleStatus::Verified;
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
 template <class T>
-ReadFileResult ComputePresetInheritance(
+bool ComputePresetInheritance(
   std::map<std::string, cmCMakePresetsGraph::PresetPair<T>>& presets,
-  const cmCMakePresetsGraph& graph)
+  cmCMakePresetsGraph& graph)
 {
   std::map<std::string, CycleStatus> cycleStatus;
   for (auto const& it : presets) {
@@ -153,13 +172,12 @@
 
   for (auto& it : presets) {
     auto& preset = it.second.Unexpanded;
-    auto result = VisitPreset<T>(preset, presets, cycleStatus, graph);
-    if (result != ReadFileResult::READ_OK) {
-      return result;
+    if (!VisitPreset<T>(preset, presets, cycleStatus, graph)) {
+      return false;
     }
   }
 
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
 constexpr const char* ValidPrefixes[] = {
@@ -186,14 +204,6 @@
 ExpandMacroResult VisitEnv(std::string& value, CycleStatus& status,
                            const std::vector<MacroExpander>& macroExpanders,
                            int version);
-ExpandMacroResult ExpandMacros(
-  std::string& out, const std::vector<MacroExpander>& macroExpanders,
-  int version);
-ExpandMacroResult ExpandMacro(std::string& out,
-                              const std::string& macroNamespace,
-                              const std::string& macroName,
-                              const std::vector<MacroExpander>& macroExpanders,
-                              int version);
 
 bool ExpandMacros(const cmCMakePresetsGraph& graph,
                   const ConfigurePreset& preset,
@@ -338,7 +348,7 @@
 }
 
 template <class T>
-bool ExpandMacros(const cmCMakePresetsGraph& graph, const T& preset,
+bool ExpandMacros(cmCMakePresetsGraph& graph, const T& preset,
                   cm::optional<T>& out)
 {
   out.emplace(preset);
@@ -430,9 +440,9 @@
       if (macroName.empty()) {
         return ExpandMacroResult::Error;
       }
-      const char* value = std::getenv(macroName.c_str());
-      if (value) {
-        result += value;
+      if (cm::optional<std::string> value =
+            cmSystemTools::GetEnvVar(macroName)) {
+        result += *value;
       }
       return ExpandMacroResult::Ok;
     }
@@ -448,6 +458,8 @@
       switch (VisitEnv(*v.second, envCycles[v.first], macroExpanders,
                        graph.GetVersion(preset))) {
         case ExpandMacroResult::Error:
+          cmCMakePresetErrors::INVALID_PRESET_NAMED(preset.Name,
+                                                    &graph.parseState);
           return false;
         case ExpandMacroResult::Ignore:
           out.reset();
@@ -462,6 +474,8 @@
     cm::optional<bool> result;
     if (!preset.ConditionEvaluator->Evaluate(
           macroExpanders, graph.GetVersion(preset), result)) {
+      cmCMakePresetErrors::INVALID_PRESET_NAMED(preset.Name,
+                                                &graph.parseState);
       return false;
     }
     if (!result) {
@@ -493,8 +507,9 @@
   status = CycleStatus::Verified;
   return ExpandMacroResult::Ok;
 }
+}
 
-ExpandMacroResult ExpandMacros(
+ExpandMacroResult cmCMakePresetsGraphInternal::ExpandMacros(
   std::string& out, const std::vector<MacroExpander>& macroExpanders,
   int version)
 {
@@ -573,11 +588,10 @@
   return ExpandMacroResult::Ok;
 }
 
-ExpandMacroResult ExpandMacro(std::string& out,
-                              const std::string& macroNamespace,
-                              const std::string& macroName,
-                              const std::vector<MacroExpander>& macroExpanders,
-                              int version)
+ExpandMacroResult cmCMakePresetsGraphInternal::ExpandMacro(
+  std::string& out, const std::string& macroNamespace,
+  const std::string& macroName,
+  const std::vector<MacroExpander>& macroExpanders, int version)
 {
   for (auto const& macroExpander : macroExpanders) {
     auto result = macroExpander(macroNamespace, macroName, out, version);
@@ -593,40 +607,46 @@
   return ExpandMacroResult::Error;
 }
 
+namespace {
 template <typename T>
-ReadFileResult SetupWorkflowConfigurePreset(
-  const T& preset, const ConfigurePreset*& configurePreset)
+bool SetupWorkflowConfigurePreset(const T& preset,
+                                  const ConfigurePreset*& configurePreset,
+                                  cmJSONState* state)
 {
   if (preset.ConfigurePreset != configurePreset->Name) {
-    return ReadFileResult::INVALID_WORKFLOW_STEPS;
+    cmCMakePresetErrors::INVALID_WORKFLOW_STEPS(configurePreset->Name, state);
+    return false;
   }
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
 template <>
-ReadFileResult SetupWorkflowConfigurePreset<ConfigurePreset>(
-  const ConfigurePreset& preset, const ConfigurePreset*& configurePreset)
+bool SetupWorkflowConfigurePreset<ConfigurePreset>(
+  const ConfigurePreset& preset, const ConfigurePreset*& configurePreset,
+  cmJSONState*)
 {
   configurePreset = &preset;
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
 template <typename T>
-ReadFileResult TryReachPresetFromWorkflow(
+bool TryReachPresetFromWorkflow(
   const WorkflowPreset& origin,
   const std::map<std::string, PresetPair<T>>& presets, const std::string& name,
-  const ConfigurePreset*& configurePreset)
+  const ConfigurePreset*& configurePreset, cmJSONState* state)
 {
   auto it = presets.find(name);
   if (it == presets.end()) {
-    return ReadFileResult::INVALID_WORKFLOW_STEPS;
+    cmCMakePresetErrors::INVALID_WORKFLOW_STEPS(name, state);
+    return false;
   }
   if (!origin.OriginFile->ReachableFiles.count(
         it->second.Unexpanded.OriginFile)) {
-    return ReadFileResult::WORKFLOW_STEP_UNREACHABLE_FROM_FILE;
+    cmCMakePresetErrors::WORKFLOW_STEP_UNREACHABLE_FROM_FILE(name, state);
+    return false;
   }
   return SetupWorkflowConfigurePreset<T>(it->second.Unexpanded,
-                                         configurePreset);
+                                         configurePreset, state);
 }
 }
 
@@ -722,8 +742,7 @@
   return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult
-cmCMakePresetsGraph::ConfigurePreset::VisitPresetInherit(
+bool cmCMakePresetsGraph::ConfigurePreset::VisitPresetInherit(
   const cmCMakePresetsGraph::Preset& parentPreset)
 {
   auto& preset = *this;
@@ -753,50 +772,52 @@
     preset.CacheVariables.insert(v);
   }
 
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult
-cmCMakePresetsGraph::ConfigurePreset::VisitPresetBeforeInherit()
+bool cmCMakePresetsGraph::ConfigurePreset::VisitPresetBeforeInherit()
 {
   auto& preset = *this;
   if (preset.Environment.count("") != 0) {
-    return ReadFileResult::INVALID_PRESET;
+    return false;
   }
 
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult
-cmCMakePresetsGraph::ConfigurePreset::VisitPresetAfterInherit(int version)
+bool cmCMakePresetsGraph::ConfigurePreset::VisitPresetAfterInherit(
+  int version, cmJSONState* state)
 {
   auto& preset = *this;
   if (!preset.Hidden) {
     if (version < 3) {
       if (preset.Generator.empty()) {
-        return ReadFileResult::INVALID_PRESET;
+        cmCMakePresetErrors::PRESET_MISSING_FIELD(preset.Name, "generator",
+                                                  state);
+        return false;
       }
       if (preset.BinaryDir.empty()) {
-        return ReadFileResult::INVALID_PRESET;
+        cmCMakePresetErrors::PRESET_MISSING_FIELD(preset.Name, "binaryDir",
+                                                  state);
+        return false;
       }
     }
 
     if (preset.WarnDev == false && preset.ErrorDev == true) {
-      return ReadFileResult::INVALID_PRESET;
+      return false;
     }
     if (preset.WarnDeprecated == false && preset.ErrorDeprecated == true) {
-      return ReadFileResult::INVALID_PRESET;
+      return false;
     }
     if (preset.CacheVariables.count("") != 0) {
-      return ReadFileResult::INVALID_PRESET;
+      return false;
     }
   }
 
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult
-cmCMakePresetsGraph::BuildPreset::VisitPresetInherit(
+bool cmCMakePresetsGraph::BuildPreset::VisitPresetInherit(
   const cmCMakePresetsGraph::Preset& parentPreset)
 {
   auto& preset = *this;
@@ -815,21 +836,20 @@
     preset.ResolvePackageReferences = parent.ResolvePackageReferences;
   }
 
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult
-cmCMakePresetsGraph::BuildPreset::VisitPresetAfterInherit(int /* version */)
+bool cmCMakePresetsGraph::BuildPreset::VisitPresetAfterInherit(
+  int /* version */, cmJSONState* /*stat*/)
 {
   auto& preset = *this;
   if (!preset.Hidden && preset.ConfigurePreset.empty()) {
-    return ReadFileResult::INVALID_PRESET;
+    return false;
   }
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult
-cmCMakePresetsGraph::TestPreset::VisitPresetInherit(
+bool cmCMakePresetsGraph::TestPreset::VisitPresetInherit(
   const cmCMakePresetsGraph::Preset& parentPreset)
 {
   auto& preset = *this;
@@ -928,21 +948,20 @@
     }
   }
 
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult
-cmCMakePresetsGraph::TestPreset::VisitPresetAfterInherit(int /* version */)
+bool cmCMakePresetsGraph::TestPreset::VisitPresetAfterInherit(
+  int /* version */, cmJSONState* /*state*/)
 {
   auto& preset = *this;
   if (!preset.Hidden && preset.ConfigurePreset.empty()) {
-    return ReadFileResult::INVALID_PRESET;
+    return false;
   }
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult
-cmCMakePresetsGraph::PackagePreset::VisitPresetInherit(
+bool cmCMakePresetsGraph::PackagePreset::VisitPresetInherit(
   const cmCMakePresetsGraph::Preset& parentPreset)
 {
   auto& preset = *this;
@@ -966,30 +985,29 @@
   InheritString(preset.PackageDirectory, parent.PackageDirectory);
   InheritString(preset.VendorName, parent.VendorName);
 
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult
-cmCMakePresetsGraph::PackagePreset::VisitPresetAfterInherit(int /* version */)
+bool cmCMakePresetsGraph::PackagePreset::VisitPresetAfterInherit(
+  int /* version */, cmJSONState* /*state*/)
 {
   auto& preset = *this;
   if (!preset.Hidden && preset.ConfigurePreset.empty()) {
-    return ReadFileResult::INVALID_PRESET;
+    return false;
   }
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult
-cmCMakePresetsGraph::WorkflowPreset::VisitPresetInherit(
+bool cmCMakePresetsGraph::WorkflowPreset::VisitPresetInherit(
   const cmCMakePresetsGraph::Preset& /*parentPreset*/)
 {
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult
-cmCMakePresetsGraph::WorkflowPreset::VisitPresetAfterInherit(int /* version */)
+bool cmCMakePresetsGraph::WorkflowPreset::VisitPresetAfterInherit(
+  int /* version */, cmJSONState* /*state*/)
 {
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
 std::string cmCMakePresetsGraph::GetFilename(const std::string& sourceDir)
@@ -1002,22 +1020,21 @@
   return cmStrCat(sourceDir, "/CMakeUserPresets.json");
 }
 
-cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadProjectPresets(
-  const std::string& sourceDir, bool allowNoFiles)
+bool cmCMakePresetsGraph::ReadProjectPresets(const std::string& sourceDir,
+                                             bool allowNoFiles)
 {
   this->SourceDir = sourceDir;
   this->ClearPresets();
 
-  auto result = this->ReadProjectPresetsInternal(allowNoFiles);
-  if (result != ReadFileResult::READ_OK) {
+  if (!this->ReadProjectPresetsInternal(allowNoFiles)) {
     this->ClearPresets();
+    return false;
   }
 
-  return result;
+  return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult
-cmCMakePresetsGraph::ReadProjectPresetsInternal(bool allowNoFiles)
+bool cmCMakePresetsGraph::ReadProjectPresetsInternal(bool allowNoFiles)
 {
   bool haveOneFile = false;
 
@@ -1025,21 +1042,17 @@
   std::string filename = GetUserFilename(this->SourceDir);
   std::vector<File*> inProgressFiles;
   if (cmSystemTools::FileExists(filename)) {
-    auto result =
-      this->ReadJSONFile(filename, RootType::User, ReadReason::Root,
-                         inProgressFiles, file, this->errors);
-    if (result != ReadFileResult::READ_OK) {
-      return result;
+    if (!this->ReadJSONFile(filename, RootType::User, ReadReason::Root,
+                            inProgressFiles, file, this->errors)) {
+      return false;
     }
     haveOneFile = true;
   } else {
     filename = GetFilename(this->SourceDir);
     if (cmSystemTools::FileExists(filename)) {
-      auto result =
-        this->ReadJSONFile(filename, RootType::Project, ReadReason::Root,
-                           inProgressFiles, file, this->errors);
-      if (result != ReadFileResult::READ_OK) {
-        return result;
+      if (!this->ReadJSONFile(filename, RootType::Project, ReadReason::Root,
+                              inProgressFiles, file, this->errors)) {
+        return false;
       }
       haveOneFile = true;
     }
@@ -1047,19 +1060,28 @@
   assert(inProgressFiles.empty());
 
   if (!haveOneFile) {
-    return allowNoFiles ? ReadFileResult::READ_OK
-                        : ReadFileResult::FILE_NOT_FOUND;
+    if (allowNoFiles) {
+      return true;
+    }
+    cmCMakePresetErrors::FILE_NOT_FOUND(filename, &this->parseState);
+    return false;
   }
 
-  CHECK_OK(ComputePresetInheritance(this->ConfigurePresets, *this));
-  CHECK_OK(ComputePresetInheritance(this->BuildPresets, *this));
-  CHECK_OK(ComputePresetInheritance(this->TestPresets, *this));
-  CHECK_OK(ComputePresetInheritance(this->PackagePresets, *this));
-  CHECK_OK(ComputePresetInheritance(this->WorkflowPresets, *this));
+  bool result = ComputePresetInheritance(this->ConfigurePresets, *this) &&
+    ComputePresetInheritance(this->ConfigurePresets, *this) &&
+    ComputePresetInheritance(this->BuildPresets, *this) &&
+    ComputePresetInheritance(this->TestPresets, *this) &&
+    ComputePresetInheritance(this->PackagePresets, *this) &&
+    ComputePresetInheritance(this->WorkflowPresets, *this);
+  if (!result) {
+    return false;
+  }
 
   for (auto& it : this->ConfigurePresets) {
     if (!ExpandMacros(*this, it.second.Unexpanded, it.second.Expanded)) {
-      return ReadFileResult::INVALID_MACRO_EXPANSION;
+      cmCMakePresetErrors::INVALID_MACRO_EXPANSION(it.first,
+                                                   &this->parseState);
+      return false;
     }
   }
 
@@ -1068,11 +1090,15 @@
       const auto configurePreset =
         this->ConfigurePresets.find(it.second.Unexpanded.ConfigurePreset);
       if (configurePreset == this->ConfigurePresets.end()) {
-        return ReadFileResult::INVALID_CONFIGURE_PRESET;
+        cmCMakePresetErrors::INVALID_CONFIGURE_PRESET(it.first,
+                                                      &this->parseState);
+        return false;
       }
       if (!it.second.Unexpanded.OriginFile->ReachableFiles.count(
             configurePreset->second.Unexpanded.OriginFile)) {
-        return ReadFileResult::CONFIGURE_PRESET_UNREACHABLE_FROM_FILE;
+        cmCMakePresetErrors::CONFIGURE_PRESET_UNREACHABLE_FROM_FILE(
+          it.first, &this->parseState);
+        return false;
       }
 
       if (it.second.Unexpanded.InheritConfigureEnvironment.value_or(true)) {
@@ -1083,7 +1109,9 @@
     }
 
     if (!ExpandMacros(*this, it.second.Unexpanded, it.second.Expanded)) {
-      return ReadFileResult::INVALID_MACRO_EXPANSION;
+      cmCMakePresetErrors::INVALID_MACRO_EXPANSION(it.first,
+                                                   &this->parseState);
+      return false;
     }
   }
 
@@ -1092,11 +1120,15 @@
       const auto configurePreset =
         this->ConfigurePresets.find(it.second.Unexpanded.ConfigurePreset);
       if (configurePreset == this->ConfigurePresets.end()) {
-        return ReadFileResult::INVALID_CONFIGURE_PRESET;
+        cmCMakePresetErrors::INVALID_CONFIGURE_PRESET(it.first,
+                                                      &this->parseState);
+        return false;
       }
       if (!it.second.Unexpanded.OriginFile->ReachableFiles.count(
             configurePreset->second.Unexpanded.OriginFile)) {
-        return ReadFileResult::CONFIGURE_PRESET_UNREACHABLE_FROM_FILE;
+        cmCMakePresetErrors::CONFIGURE_PRESET_UNREACHABLE_FROM_FILE(
+          it.first, &this->parseState);
+        return false;
       }
 
       if (it.second.Unexpanded.InheritConfigureEnvironment.value_or(true)) {
@@ -1107,7 +1139,9 @@
     }
 
     if (!ExpandMacros(*this, it.second.Unexpanded, it.second.Expanded)) {
-      return ReadFileResult::INVALID_MACRO_EXPANSION;
+      cmCMakePresetErrors::INVALID_MACRO_EXPANSION(it.first,
+                                                   &this->parseState);
+      return false;
     }
   }
 
@@ -1116,11 +1150,15 @@
       const auto configurePreset =
         this->ConfigurePresets.find(it.second.Unexpanded.ConfigurePreset);
       if (configurePreset == this->ConfigurePresets.end()) {
-        return ReadFileResult::INVALID_CONFIGURE_PRESET;
+        cmCMakePresetErrors::INVALID_CONFIGURE_PRESET(it.first,
+                                                      &this->parseState);
+        return false;
       }
       if (!it.second.Unexpanded.OriginFile->ReachableFiles.count(
             configurePreset->second.Unexpanded.OriginFile)) {
-        return ReadFileResult::CONFIGURE_PRESET_UNREACHABLE_FROM_FILE;
+        cmCMakePresetErrors::CONFIGURE_PRESET_UNREACHABLE_FROM_FILE(
+          it.first, &this->parseState);
+        return false;
       }
 
       if (it.second.Unexpanded.InheritConfigureEnvironment.value_or(true)) {
@@ -1131,7 +1169,9 @@
     }
 
     if (!ExpandMacros(*this, it.second.Unexpanded, it.second.Expanded)) {
-      return ReadFileResult::INVALID_MACRO_EXPANSION;
+      cmCMakePresetErrors::INVALID_MACRO_EXPANSION(it.first,
+                                                   &this->parseState);
+      return false;
     }
   }
 
@@ -1141,126 +1181,56 @@
     const ConfigurePreset* configurePreset = nullptr;
     for (auto const& step : it.second.Unexpanded.Steps) {
       if (configurePreset == nullptr && step.PresetType != Type::Configure) {
-        return ReadFileResult::INVALID_WORKFLOW_STEPS;
+        cmCMakePresetErrors::FIRST_WORKFLOW_STEP_NOT_CONFIGURE(
+          step.PresetName, &this->parseState);
+        return false;
       }
       if (configurePreset != nullptr && step.PresetType == Type::Configure) {
-        return ReadFileResult::INVALID_WORKFLOW_STEPS;
+        cmCMakePresetErrors::CONFIGURE_WORKFLOW_STEP_NOT_FIRST(
+          step.PresetName, &this->parseState);
+        return false;
       }
 
-      ReadFileResult result;
       switch (step.PresetType) {
         case Type::Configure:
           result = TryReachPresetFromWorkflow(
             it.second.Unexpanded, this->ConfigurePresets, step.PresetName,
-            configurePreset);
+            configurePreset, &this->parseState);
           break;
         case Type::Build:
           result = TryReachPresetFromWorkflow(
             it.second.Unexpanded, this->BuildPresets, step.PresetName,
-            configurePreset);
+            configurePreset, &this->parseState);
           break;
         case Type::Test:
-          result =
-            TryReachPresetFromWorkflow(it.second.Unexpanded, this->TestPresets,
-                                       step.PresetName, configurePreset);
+          result = TryReachPresetFromWorkflow(
+            it.second.Unexpanded, this->TestPresets, step.PresetName,
+            configurePreset, &this->parseState);
           break;
         case Type::Package:
           result = TryReachPresetFromWorkflow(
             it.second.Unexpanded, this->PackagePresets, step.PresetName,
-            configurePreset);
+            configurePreset, &this->parseState);
           break;
       }
-      if (result != ReadFileResult::READ_OK) {
-        return result;
+      if (!result) {
+        return false;
       }
     }
 
     if (configurePreset == nullptr) {
-      return ReadFileResult::INVALID_WORKFLOW_STEPS;
+      cmCMakePresetErrors::NO_WORKFLOW_STEPS(it.first, &this->parseState);
+      return false;
     }
 
     if (!ExpandMacros(*this, it.second.Unexpanded, it.second.Expanded)) {
-      return ReadFileResult::INVALID_MACRO_EXPANSION;
+      cmCMakePresetErrors::INVALID_MACRO_EXPANSION(it.first,
+                                                   &this->parseState);
+      return false;
     }
   }
 
-  return ReadFileResult::READ_OK;
-}
-
-const char* cmCMakePresetsGraph::ResultToString(ReadFileResult result)
-{
-  switch (result) {
-    case ReadFileResult::READ_OK:
-      return "OK";
-    case ReadFileResult::FILE_NOT_FOUND:
-      return "File not found";
-    case ReadFileResult::JSON_PARSE_ERROR:
-      return "JSON parse error";
-    case ReadFileResult::INVALID_ROOT:
-      return "Invalid root object";
-    case ReadFileResult::NO_VERSION:
-      return "No \"version\" field";
-    case ReadFileResult::INVALID_VERSION:
-      return "Invalid \"version\" field";
-    case ReadFileResult::UNRECOGNIZED_VERSION:
-      return "Unrecognized \"version\" field";
-    case ReadFileResult::INVALID_CMAKE_VERSION:
-      return "Invalid \"cmakeMinimumRequired\" field";
-    case ReadFileResult::UNRECOGNIZED_CMAKE_VERSION:
-      return "\"cmakeMinimumRequired\" version too new";
-    case ReadFileResult::INVALID_PRESETS:
-      return "Invalid \"configurePresets\" field";
-    case ReadFileResult::INVALID_PRESET:
-      return "Invalid preset";
-    case ReadFileResult::INVALID_VARIABLE:
-      return "Invalid CMake variable definition";
-    case ReadFileResult::DUPLICATE_PRESETS:
-      return "Duplicate presets";
-    case ReadFileResult::CYCLIC_PRESET_INHERITANCE:
-      return "Cyclic preset inheritance";
-    case ReadFileResult::INHERITED_PRESET_UNREACHABLE_FROM_FILE:
-      return "Inherited preset is unreachable from preset's file";
-    case ReadFileResult::CONFIGURE_PRESET_UNREACHABLE_FROM_FILE:
-      return "Configure preset is unreachable from preset's file";
-    case ReadFileResult::INVALID_MACRO_EXPANSION:
-      return "Invalid macro expansion";
-    case ReadFileResult::BUILD_TEST_PRESETS_UNSUPPORTED:
-      return "File version must be 2 or higher for build and test preset "
-             "support.";
-    case ReadFileResult::PACKAGE_PRESETS_UNSUPPORTED:
-      return "File version must be 6 or higher for package preset support";
-    case ReadFileResult::WORKFLOW_PRESETS_UNSUPPORTED:
-      return "File version must be 6 or higher for workflow preset support";
-    case ReadFileResult::INCLUDE_UNSUPPORTED:
-      return "File version must be 4 or higher for include support";
-    case ReadFileResult::INVALID_INCLUDE:
-      return "Invalid \"include\" field";
-    case ReadFileResult::INVALID_CONFIGURE_PRESET:
-      return "Invalid \"configurePreset\" field";
-    case ReadFileResult::INSTALL_PREFIX_UNSUPPORTED:
-      return "File version must be 3 or higher for installDir preset "
-             "support.";
-    case ReadFileResult::INVALID_CONDITION:
-      return "Invalid preset condition";
-    case ReadFileResult::CONDITION_UNSUPPORTED:
-      return "File version must be 3 or higher for condition support";
-    case ReadFileResult::TOOLCHAIN_FILE_UNSUPPORTED:
-      return "File version must be 3 or higher for toolchainFile preset "
-             "support.";
-    case ReadFileResult::CYCLIC_INCLUDE:
-      return "Cyclic include among preset files";
-    case ReadFileResult::TEST_OUTPUT_TRUNCATION_UNSUPPORTED:
-      return "File version must be 5 or higher for testOutputTruncation "
-             "preset support.";
-    case ReadFileResult::INVALID_WORKFLOW_STEPS:
-      return "Invalid workflow steps";
-    case ReadFileResult::WORKFLOW_STEP_UNREACHABLE_FROM_FILE:
-      return "Workflow step is unreachable from preset's file";
-    case ReadFileResult::CTEST_JUNIT_UNSUPPORTED:
-      return "File version must be 6 or higher for CTest JUnit output support";
-  }
-
-  return "Unknown error";
+  return true;
 }
 
 void cmCMakePresetsGraph::ClearPresets()
diff --git a/Source/cmCMakePresetsGraph.h b/Source/cmCMakePresetsGraph.h
index 17c902b..7844624 100644
--- a/Source/cmCMakePresetsGraph.h
+++ b/Source/cmCMakePresetsGraph.h
@@ -14,6 +14,9 @@
 
 #include <cm/optional>
 
+#include "cmJSONState.h"
+#include "cmStateTypes.h"
+
 #include "CTest/cmCTestTypes.h"
 
 enum class PackageResolveMode;
@@ -21,49 +24,22 @@
 class cmCMakePresetsGraph
 {
 public:
-  enum class ReadFileResult
-  {
-    READ_OK,
-    FILE_NOT_FOUND,
-    JSON_PARSE_ERROR,
-    INVALID_ROOT,
-    NO_VERSION,
-    INVALID_VERSION,
-    UNRECOGNIZED_VERSION,
-    INVALID_CMAKE_VERSION,
-    UNRECOGNIZED_CMAKE_VERSION,
-    INVALID_PRESETS,
-    INVALID_PRESET,
-    INVALID_VARIABLE,
-    DUPLICATE_PRESETS,
-    CYCLIC_PRESET_INHERITANCE,
-    INHERITED_PRESET_UNREACHABLE_FROM_FILE,
-    CONFIGURE_PRESET_UNREACHABLE_FROM_FILE,
-    INVALID_MACRO_EXPANSION,
-    BUILD_TEST_PRESETS_UNSUPPORTED,
-    PACKAGE_PRESETS_UNSUPPORTED,
-    WORKFLOW_PRESETS_UNSUPPORTED,
-    INCLUDE_UNSUPPORTED,
-    INVALID_INCLUDE,
-    INVALID_CONFIGURE_PRESET,
-    INSTALL_PREFIX_UNSUPPORTED,
-    INVALID_CONDITION,
-    CONDITION_UNSUPPORTED,
-    TOOLCHAIN_FILE_UNSUPPORTED,
-    CYCLIC_INCLUDE,
-    TEST_OUTPUT_TRUNCATION_UNSUPPORTED,
-    INVALID_WORKFLOW_STEPS,
-    WORKFLOW_STEP_UNREACHABLE_FROM_FILE,
-    CTEST_JUNIT_UNSUPPORTED,
-  };
-
   std::string errors;
+  cmJSONState parseState;
+
   enum class ArchToolsetStrategy
   {
     Set,
     External,
   };
 
+  enum class TraceEnableMode
+  {
+    Disable,
+    Default,
+    Expand,
+  };
+
   class CacheVariable
   {
   public:
@@ -111,15 +87,13 @@
 
     std::map<std::string, cm::optional<std::string>> Environment;
 
-    virtual ReadFileResult VisitPresetInherit(const Preset& parent) = 0;
-    virtual ReadFileResult VisitPresetBeforeInherit()
-    {
-      return ReadFileResult::READ_OK;
-    }
+    virtual bool VisitPresetInherit(const Preset& parent) = 0;
+    virtual bool VisitPresetBeforeInherit() { return true; }
 
-    virtual ReadFileResult VisitPresetAfterInherit(int /* version */)
+    virtual bool VisitPresetAfterInherit(int /* version */,
+                                         cmJSONState* /*state*/)
     {
-      return ReadFileResult::READ_OK;
+      return true;
     }
   };
 
@@ -163,9 +137,14 @@
     cm::optional<bool> DebugTryCompile;
     cm::optional<bool> DebugFind;
 
-    ReadFileResult VisitPresetInherit(const Preset& parent) override;
-    ReadFileResult VisitPresetBeforeInherit() override;
-    ReadFileResult VisitPresetAfterInherit(int version) override;
+    cm::optional<TraceEnableMode> TraceMode;
+    cm::optional<cmTraceEnums::TraceOutputFormat> TraceFormat;
+    std::vector<std::string> TraceSource;
+    std::string TraceRedirect;
+
+    bool VisitPresetInherit(const Preset& parent) override;
+    bool VisitPresetBeforeInherit() override;
+    bool VisitPresetAfterInherit(int version, cmJSONState* state) override;
   };
 
   class BuildPreset : public Preset
@@ -195,8 +174,9 @@
     std::vector<std::string> NativeToolOptions;
     cm::optional<PackageResolveMode> ResolvePackageReferences;
 
-    ReadFileResult VisitPresetInherit(const Preset& parent) override;
-    ReadFileResult VisitPresetAfterInherit(int /* version */) override;
+    bool VisitPresetInherit(const Preset& parent) override;
+    bool VisitPresetAfterInherit(int /* version */,
+                                 cmJSONState* /*state*/) override;
   };
 
   class TestPreset : public Preset
@@ -328,8 +308,9 @@
     cm::optional<FilterOptions> Filter;
     cm::optional<ExecutionOptions> Execution;
 
-    ReadFileResult VisitPresetInherit(const Preset& parent) override;
-    ReadFileResult VisitPresetAfterInherit(int /* version */) override;
+    bool VisitPresetInherit(const Preset& parent) override;
+    bool VisitPresetAfterInherit(int /* version */,
+                                 cmJSONState* /*state*/) override;
   };
 
   class PackagePreset : public Preset
@@ -364,8 +345,9 @@
     std::string PackageDirectory;
     std::string VendorName;
 
-    ReadFileResult VisitPresetInherit(const Preset& parent) override;
-    ReadFileResult VisitPresetAfterInherit(int /* version */) override;
+    bool VisitPresetInherit(const Preset& parent) override;
+    bool VisitPresetAfterInherit(int /* version */,
+                                 cmJSONState* /*state*/) override;
   };
 
   class WorkflowPreset : public Preset
@@ -401,8 +383,9 @@
 
     std::vector<WorkflowStep> Steps;
 
-    ReadFileResult VisitPresetInherit(const Preset& parent) override;
-    ReadFileResult VisitPresetAfterInherit(int /* version */) override;
+    bool VisitPresetInherit(const Preset& parent) override;
+    bool VisitPresetAfterInherit(int /* version */,
+                                 cmJSONState* /* state */) override;
   };
 
   template <class T>
@@ -435,9 +418,8 @@
 
   static std::string GetFilename(const std::string& sourceDir);
   static std::string GetUserFilename(const std::string& sourceDir);
-  ReadFileResult ReadProjectPresets(const std::string& sourceDir,
-                                    bool allowNoFiles = false);
-  static const char* ResultToString(ReadFileResult result);
+  bool ReadProjectPresets(const std::string& sourceDir,
+                          bool allowNoFiles = false);
 
   std::string GetGeneratorForPreset(const std::string& presetName) const
   {
@@ -502,10 +484,9 @@
     Included,
   };
 
-  ReadFileResult ReadProjectPresetsInternal(bool allowNoFiles);
-  ReadFileResult ReadJSONFile(const std::string& filename, RootType rootType,
-                              ReadReason readReason,
-                              std::vector<File*>& inProgressFiles, File*& file,
-                              std::string& errMsg);
+  bool ReadProjectPresetsInternal(bool allowNoFiles);
+  bool ReadJSONFile(const std::string& filename, RootType rootType,
+                    ReadReason readReason, std::vector<File*>& inProgressFiles,
+                    File*& file, std::string& errMsg);
   void ClearPresets();
 };
diff --git a/Source/cmCMakePresetsGraphInternal.h b/Source/cmCMakePresetsGraphInternal.h
index 2726e92..f133efb 100644
--- a/Source/cmCMakePresetsGraphInternal.h
+++ b/Source/cmCMakePresetsGraphInternal.h
@@ -14,7 +14,7 @@
 #define CHECK_OK(expr)                                                        \
   do {                                                                        \
     auto _result = expr;                                                      \
-    if (_result != ReadFileResult::READ_OK)                                   \
+    if (_result != true)                                                      \
       return _result;                                                         \
   } while (false)
 
@@ -28,6 +28,16 @@
 
 using MacroExpander = std::function<ExpandMacroResult(
   const std::string&, const std::string&, std::string&, int version)>;
+
+ExpandMacroResult ExpandMacros(
+  std::string& out, const std::vector<MacroExpander>& macroExpanders,
+  int version);
+
+ExpandMacroResult ExpandMacro(std::string& out,
+                              const std::string& macroNamespace,
+                              const std::string& macroName,
+                              const std::vector<MacroExpander>& macroExpanders,
+                              int version);
 }
 
 class cmCMakePresetsGraph::Condition
@@ -117,57 +127,56 @@
   std::unique_ptr<Condition> SubCondition;
 };
 
-cmCMakePresetsGraph::ReadFileResult PresetStringHelper(
-  std::string& out, const Json::Value* value);
+bool PresetStringHelper(std::string& out, const Json::Value* value,
+                        cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult PresetVectorStringHelper(
-  std::vector<std::string>& out, const Json::Value* value);
+bool PresetNameHelper(std::string& out, const Json::Value* value,
+                      cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult PresetBoolHelper(bool& out,
-                                                     const Json::Value* value);
+bool PresetVectorStringHelper(std::vector<std::string>& out,
+                              const Json::Value* value, cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult PresetOptionalBoolHelper(
-  cm::optional<bool>& out, const Json::Value* value);
+bool PresetBoolHelper(bool& out, const Json::Value* value, cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult PresetIntHelper(int& out,
-                                                    const Json::Value* value);
+bool PresetOptionalBoolHelper(cm::optional<bool>& out,
+                              const Json::Value* value, cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult PresetOptionalIntHelper(
-  cm::optional<int>& out, const Json::Value* value);
+bool PresetIntHelper(int& out, const Json::Value* value, cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult PresetVectorIntHelper(
-  std::vector<int>& out, const Json::Value* value);
+bool PresetOptionalIntHelper(cm::optional<int>& out, const Json::Value* value,
+                             cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult ConfigurePresetsHelper(
+bool PresetVectorIntHelper(std::vector<int>& out, const Json::Value* value,
+                           cmJSONState* state);
+
+bool ConfigurePresetsHelper(
   std::vector<cmCMakePresetsGraph::ConfigurePreset>& out,
-  const Json::Value* value);
+  const Json::Value* value, cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult BuildPresetsHelper(
-  std::vector<cmCMakePresetsGraph::BuildPreset>& out,
-  const Json::Value* value);
+bool BuildPresetsHelper(std::vector<cmCMakePresetsGraph::BuildPreset>& out,
+                        const Json::Value* value, cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult TestPresetsHelper(
-  std::vector<cmCMakePresetsGraph::TestPreset>& out, const Json::Value* value);
+bool TestPresetsHelper(std::vector<cmCMakePresetsGraph::TestPreset>& out,
+                       const Json::Value* value, cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult PackagePresetsHelper(
-  std::vector<cmCMakePresetsGraph::PackagePreset>& out,
-  const Json::Value* value);
+bool PackagePresetsHelper(std::vector<cmCMakePresetsGraph::PackagePreset>& out,
+                          const Json::Value* value, cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult WorkflowPresetsHelper(
+bool WorkflowPresetsHelper(
   std::vector<cmCMakePresetsGraph::WorkflowPreset>& out,
-  const Json::Value* value);
+  const Json::Value* value, cmJSONState* state);
 
-cmJSONHelper<std::nullptr_t, cmCMakePresetsGraph::ReadFileResult> VendorHelper(
-  cmCMakePresetsGraph::ReadFileResult error);
+cmJSONHelper<std::nullptr_t> VendorHelper(const ErrorGenerator& error);
 
-cmCMakePresetsGraph::ReadFileResult PresetConditionHelper(
+bool PresetConditionHelper(
   std::shared_ptr<cmCMakePresetsGraph::Condition>& out,
-  const Json::Value* value);
+  const Json::Value* value, cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult PresetVectorOneOrMoreStringHelper(
-  std::vector<std::string>& out, const Json::Value* value);
+bool PresetVectorOneOrMoreStringHelper(std::vector<std::string>& out,
+                                       const Json::Value* value,
+                                       cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult EnvironmentMapHelper(
+bool EnvironmentMapHelper(
   std::map<std::string, cm::optional<std::string>>& out,
-  const Json::Value* value);
+  const Json::Value* value, cmJSONState* state);
 }
diff --git a/Source/cmCMakePresetsGraphReadJSON.cxx b/Source/cmCMakePresetsGraphReadJSON.cxx
index a96ab58..54fea40 100644
--- a/Source/cmCMakePresetsGraphReadJSON.cxx
+++ b/Source/cmCMakePresetsGraphReadJSON.cxx
@@ -1,6 +1,7 @@
 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include <algorithm>
+#include <fstream>
 #include <functional>
 #include <map>
 #include <string>
@@ -12,20 +13,18 @@
 #include <cm/optional>
 #include <cmext/string_view>
 
-#include <cm3p/json/reader.h>
 #include <cm3p/json/value.h>
 
-#include "cmsys/FStream.hxx"
-
+#include "cmCMakePresetErrors.h"
 #include "cmCMakePresetsGraph.h"
 #include "cmCMakePresetsGraphInternal.h"
 #include "cmJSONHelpers.h"
+#include "cmJSONState.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmVersion.h"
 
 namespace {
-using ReadFileResult = cmCMakePresetsGraph::ReadFileResult;
 using CacheVariable = cmCMakePresetsGraph::CacheVariable;
 using ConfigurePreset = cmCMakePresetsGraph::ConfigurePreset;
 using BuildPreset = cmCMakePresetsGraph::BuildPreset;
@@ -33,10 +32,13 @@
 using PackagePreset = cmCMakePresetsGraph::PackagePreset;
 using WorkflowPreset = cmCMakePresetsGraph::WorkflowPreset;
 using ArchToolsetStrategy = cmCMakePresetsGraph::ArchToolsetStrategy;
-using JSONHelperBuilder = cmJSONHelperBuilder<ReadFileResult>;
+using JSONHelperBuilder = cmJSONHelperBuilder;
+using ExpandMacroResult = cmCMakePresetsGraphInternal::ExpandMacroResult;
+using MacroExpander = cmCMakePresetsGraphInternal::MacroExpander;
+using cmCMakePresetsGraphInternal::ExpandMacros;
 
 constexpr int MIN_VERSION = 1;
-constexpr int MAX_VERSION = 6;
+constexpr int MAX_VERSION = 7;
 
 struct CMakeVersion
 {
@@ -64,26 +66,23 @@
   return retval;
 }
 
-auto const ConditionStringHelper = JSONHelperBuilder::String(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION);
+auto const ConditionStringHelper = JSONHelperBuilder::String();
 
-auto const ConditionBoolHelper = JSONHelperBuilder::Bool(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION);
+auto const ConditionBoolHelper = JSONHelperBuilder::Bool();
 
 auto const ConditionStringListHelper = JSONHelperBuilder::Vector<std::string>(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION,
-  ConditionStringHelper);
+  cmCMakePresetErrors::INVALID_CONDITION, ConditionStringHelper);
 
 auto const ConstConditionHelper =
   JSONHelperBuilder::Object<cmCMakePresetsGraphInternal::ConstCondition>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false)
+    cmCMakePresetErrors::INVALID_CONDITION_OBJECT, false)
     .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
     .Bind("value"_s, &cmCMakePresetsGraphInternal::ConstCondition::Value,
           ConditionBoolHelper, true);
 
 auto const EqualsConditionHelper =
   JSONHelperBuilder::Object<cmCMakePresetsGraphInternal::EqualsCondition>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false)
+    cmCMakePresetErrors::INVALID_CONDITION_OBJECT, false)
     .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
     .Bind("lhs"_s, &cmCMakePresetsGraphInternal::EqualsCondition::Lhs,
           ConditionStringHelper, true)
@@ -92,7 +91,7 @@
 
 auto const InListConditionHelper =
   JSONHelperBuilder::Object<cmCMakePresetsGraphInternal::InListCondition>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false)
+    cmCMakePresetErrors::INVALID_CONDITION_OBJECT, false)
     .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
     .Bind("string"_s, &cmCMakePresetsGraphInternal::InListCondition::String,
           ConditionStringHelper, true)
@@ -101,24 +100,22 @@
 
 auto const MatchesConditionHelper =
   JSONHelperBuilder::Object<cmCMakePresetsGraphInternal::MatchesCondition>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false)
+    cmCMakePresetErrors::INVALID_CONDITION_OBJECT, false)
     .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
     .Bind("string"_s, &cmCMakePresetsGraphInternal::MatchesCondition::String,
           ConditionStringHelper, true)
     .Bind("regex"_s, &cmCMakePresetsGraphInternal::MatchesCondition::Regex,
           ConditionStringHelper, true);
 
-ReadFileResult SubConditionHelper(
-  std::unique_ptr<cmCMakePresetsGraph::Condition>& out,
-  const Json::Value* value);
+bool SubConditionHelper(std::unique_ptr<cmCMakePresetsGraph::Condition>& out,
+                        const Json::Value* value, cmJSONState* state);
 
 auto const ListConditionVectorHelper =
   JSONHelperBuilder::Vector<std::unique_ptr<cmCMakePresetsGraph::Condition>>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION,
-    SubConditionHelper);
+    cmCMakePresetErrors::INVALID_CONDITION, SubConditionHelper);
 auto const AnyAllOfConditionHelper =
   JSONHelperBuilder::Object<cmCMakePresetsGraphInternal::AnyAllOfCondition>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false)
+    cmCMakePresetErrors::INVALID_CONDITION_OBJECT, false)
     .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
     .Bind("conditions"_s,
           &cmCMakePresetsGraphInternal::AnyAllOfCondition::Conditions,
@@ -126,158 +123,160 @@
 
 auto const NotConditionHelper =
   JSONHelperBuilder::Object<cmCMakePresetsGraphInternal::NotCondition>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false)
+    cmCMakePresetErrors::INVALID_CONDITION_OBJECT, false)
     .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
     .Bind("condition"_s,
           &cmCMakePresetsGraphInternal::NotCondition::SubCondition,
           SubConditionHelper);
 
-ReadFileResult ConditionHelper(
-  std::unique_ptr<cmCMakePresetsGraph::Condition>& out,
-  const Json::Value* value)
+bool ConditionHelper(std::unique_ptr<cmCMakePresetsGraph::Condition>& out,
+                     const Json::Value* value, cmJSONState* state)
 {
   if (!value) {
     out.reset();
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->isBool()) {
     auto c = cm::make_unique<cmCMakePresetsGraphInternal::ConstCondition>();
     c->Value = value->asBool();
     out = std::move(c);
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->isNull()) {
     out = cm::make_unique<cmCMakePresetsGraphInternal::NullCondition>();
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->isObject()) {
     if (!value->isMember("type")) {
-      return ReadFileResult::INVALID_CONDITION;
+      cmCMakePresetErrors::INVALID_CONDITION(value, state);
+      return false;
     }
 
     if (!(*value)["type"].isString()) {
-      return ReadFileResult::INVALID_CONDITION;
+      cmCMakePresetErrors::INVALID_CONDITION(value, state);
+      return false;
     }
     auto type = (*value)["type"].asString();
 
     if (type == "const") {
       auto c = cm::make_unique<cmCMakePresetsGraphInternal::ConstCondition>();
-      CHECK_OK(ConstConditionHelper(*c, value));
+      CHECK_OK(ConstConditionHelper(*c, value, state));
       out = std::move(c);
-      return ReadFileResult::READ_OK;
+      return true;
     }
 
     if (type == "equals" || type == "notEquals") {
       auto c = cm::make_unique<cmCMakePresetsGraphInternal::EqualsCondition>();
-      CHECK_OK(EqualsConditionHelper(*c, value));
+      CHECK_OK(EqualsConditionHelper(*c, value, state));
       out = std::move(c);
       if (type == "notEquals") {
         out = InvertCondition(std::move(out));
       }
-      return ReadFileResult::READ_OK;
+      return true;
     }
 
     if (type == "inList" || type == "notInList") {
       auto c = cm::make_unique<cmCMakePresetsGraphInternal::InListCondition>();
-      CHECK_OK(InListConditionHelper(*c, value));
+      CHECK_OK(InListConditionHelper(*c, value, state));
       out = std::move(c);
       if (type == "notInList") {
         out = InvertCondition(std::move(out));
       }
-      return ReadFileResult::READ_OK;
+      return true;
     }
 
     if (type == "matches" || type == "notMatches") {
       auto c =
         cm::make_unique<cmCMakePresetsGraphInternal::MatchesCondition>();
-      CHECK_OK(MatchesConditionHelper(*c, value));
+      CHECK_OK(MatchesConditionHelper(*c, value, state));
       out = std::move(c);
       if (type == "notMatches") {
         out = InvertCondition(std::move(out));
       }
-      return ReadFileResult::READ_OK;
+      return true;
     }
 
     if (type == "anyOf" || type == "allOf") {
       auto c =
         cm::make_unique<cmCMakePresetsGraphInternal::AnyAllOfCondition>();
       c->StopValue = (type == "anyOf");
-      CHECK_OK(AnyAllOfConditionHelper(*c, value));
+      CHECK_OK(AnyAllOfConditionHelper(*c, value, state));
       out = std::move(c);
-      return ReadFileResult::READ_OK;
+      return true;
     }
 
     if (type == "not") {
       auto c = cm::make_unique<cmCMakePresetsGraphInternal::NotCondition>();
-      CHECK_OK(NotConditionHelper(*c, value));
+      CHECK_OK(NotConditionHelper(*c, value, state));
       out = std::move(c);
-      return ReadFileResult::READ_OK;
+      return true;
     }
   }
 
-  return ReadFileResult::INVALID_CONDITION;
+  cmCMakePresetErrors::INVALID_CONDITION(value, state);
+  return false;
 }
 
-ReadFileResult SubConditionHelper(
-  std::unique_ptr<cmCMakePresetsGraph::Condition>& out,
-  const Json::Value* value)
+bool SubConditionHelper(std::unique_ptr<cmCMakePresetsGraph::Condition>& out,
+                        const Json::Value* value, cmJSONState* state)
 {
   std::unique_ptr<cmCMakePresetsGraph::Condition> ptr;
-  auto result = ConditionHelper(ptr, value);
+  auto result = ConditionHelper(ptr, value, state);
   if (ptr && ptr->IsNull()) {
-    return ReadFileResult::INVALID_CONDITION;
+    cmCMakePresetErrors::INVALID_CONDITION(value, state);
+    return false;
   }
   out = std::move(ptr);
   return result;
 }
 
-ReadFileResult EnvironmentHelper(cm::optional<std::string>& out,
-                                 const Json::Value* value)
+bool EnvironmentHelper(cm::optional<std::string>& out,
+                       const Json::Value* value, cmJSONState* state)
 {
   if (!value || value->isNull()) {
     out = cm::nullopt;
-    return ReadFileResult::READ_OK;
+    return true;
   }
   if (value->isString()) {
     out = value->asString();
-    return ReadFileResult::READ_OK;
+    return true;
   }
-  return ReadFileResult::INVALID_PRESET;
+  cmCMakePresetErrors::INVALID_PRESET(value, state);
+  return false;
 }
 
-auto const VersionIntHelper = JSONHelperBuilder::Int(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_VERSION);
+auto const VersionIntHelper =
+  JSONHelperBuilder::Int(cmCMakePresetErrors::INVALID_VERSION);
 
 auto const VersionHelper = JSONHelperBuilder::Required<int>(
-  ReadFileResult::NO_VERSION, VersionIntHelper);
+  cmCMakePresetErrors::NO_VERSION, VersionIntHelper);
 
 auto const RootVersionHelper =
-  JSONHelperBuilder::Object<int>(ReadFileResult::READ_OK,
-                                 ReadFileResult::INVALID_ROOT)
+  JSONHelperBuilder::Object<int>(cmCMakePresetErrors::INVALID_ROOT_OBJECT)
     .Bind("version"_s, VersionHelper, false);
 
-auto const CMakeVersionUIntHelper = JSONHelperBuilder::UInt(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_VERSION);
+auto const CMakeVersionUIntHelper =
+  JSONHelperBuilder::UInt(cmCMakePresetErrors::INVALID_VERSION);
 
 auto const CMakeVersionHelper =
-  JSONHelperBuilder::Object<CMakeVersion>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_CMAKE_VERSION, false)
+  JSONHelperBuilder::Object<CMakeVersion>(JsonErrors::INVALID_NAMED_OBJECT_KEY,
+                                          false)
     .Bind("major"_s, &CMakeVersion::Major, CMakeVersionUIntHelper, false)
     .Bind("minor"_s, &CMakeVersion::Minor, CMakeVersionUIntHelper, false)
     .Bind("patch"_s, &CMakeVersion::Patch, CMakeVersionUIntHelper, false);
 
-auto const IncludeHelper = JSONHelperBuilder::String(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_INCLUDE);
+auto const IncludeHelper =
+  JSONHelperBuilder::String(cmCMakePresetErrors::INVALID_INCLUDE);
 
 auto const IncludeVectorHelper = JSONHelperBuilder::Vector<std::string>(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_INCLUDE, IncludeHelper);
+  cmCMakePresetErrors::INVALID_INCLUDE, IncludeHelper);
 
 auto const RootPresetsHelper =
-  JSONHelperBuilder::Object<RootPresets>(ReadFileResult::READ_OK,
-                                         ReadFileResult::INVALID_ROOT, false)
+  JSONHelperBuilder::Object<RootPresets>(
+    cmCMakePresetErrors::INVALID_ROOT_OBJECT, false)
     .Bind<int>("version"_s, nullptr, VersionHelper)
     .Bind("configurePresets"_s, &RootPresets::ConfigurePresets,
           cmCMakePresetsGraphInternal::ConfigurePresetsHelper, false)
@@ -292,136 +291,137 @@
     .Bind("cmakeMinimumRequired"_s, &RootPresets::CMakeMinimumRequired,
           CMakeVersionHelper, false)
     .Bind("include"_s, &RootPresets::Include, IncludeVectorHelper, false)
-    .Bind<std::nullptr_t>(
-      "vendor"_s, nullptr,
-      cmCMakePresetsGraphInternal::VendorHelper(ReadFileResult::INVALID_ROOT),
-      false);
+    .Bind<std::nullptr_t>("vendor"_s, nullptr,
+                          cmCMakePresetsGraphInternal::VendorHelper(
+                            cmCMakePresetErrors::INVALID_ROOT),
+                          false);
 }
 
 namespace cmCMakePresetsGraphInternal {
-cmCMakePresetsGraph::ReadFileResult PresetStringHelper(
-  std::string& out, const Json::Value* value)
+bool PresetStringHelper(std::string& out, const Json::Value* value,
+                        cmJSONState* state)
 {
-  static auto const helper = JSONHelperBuilder::String(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET);
-
-  return helper(out, value);
+  static auto const helper = JSONHelperBuilder::String();
+  return helper(out, value, state);
 }
 
-cmCMakePresetsGraph::ReadFileResult PresetVectorStringHelper(
-  std::vector<std::string>& out, const Json::Value* value)
+bool PresetNameHelper(std::string& out, const Json::Value* value,
+                      cmJSONState* state)
+{
+  if (!value || !value->isString() || value->asString().empty()) {
+    cmCMakePresetErrors::INVALID_PRESET_NAME(value, state);
+    return false;
+  }
+  out = value->asString();
+  return true;
+}
+
+bool PresetVectorStringHelper(std::vector<std::string>& out,
+                              const Json::Value* value, cmJSONState* state)
 {
   static auto const helper = JSONHelperBuilder::Vector<std::string>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET,
+    cmCMakePresetErrors::INVALID_PRESET,
     cmCMakePresetsGraphInternal::PresetStringHelper);
-
-  return helper(out, value);
+  return helper(out, value, state);
 }
 
-cmCMakePresetsGraph::ReadFileResult PresetBoolHelper(bool& out,
-                                                     const Json::Value* value)
+bool PresetBoolHelper(bool& out, const Json::Value* value, cmJSONState* state)
 {
-  static auto const helper = JSONHelperBuilder::Bool(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET);
-
-  return helper(out, value);
+  static auto const helper = JSONHelperBuilder::Bool();
+  return helper(out, value, state);
 }
 
-cmCMakePresetsGraph::ReadFileResult PresetOptionalBoolHelper(
-  cm::optional<bool>& out, const Json::Value* value)
-{
-  static auto const helper = JSONHelperBuilder::Optional<bool>(
-    ReadFileResult::READ_OK, PresetBoolHelper);
-
-  return helper(out, value);
-}
-
-cmCMakePresetsGraph::ReadFileResult PresetIntHelper(int& out,
-                                                    const Json::Value* value)
-{
-  static auto const helper = JSONHelperBuilder::Int(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET);
-
-  return helper(out, value);
-}
-
-cmCMakePresetsGraph::ReadFileResult PresetOptionalIntHelper(
-  cm::optional<int>& out, const Json::Value* value)
+bool PresetOptionalBoolHelper(cm::optional<bool>& out,
+                              const Json::Value* value, cmJSONState* state)
 {
   static auto const helper =
-    JSONHelperBuilder::Optional<int>(ReadFileResult::READ_OK, PresetIntHelper);
-
-  return helper(out, value);
+    JSONHelperBuilder::Optional<bool>(PresetBoolHelper);
+  return helper(out, value, state);
 }
 
-cmCMakePresetsGraph::ReadFileResult PresetVectorIntHelper(
-  std::vector<int>& out, const Json::Value* value)
+bool PresetIntHelper(int& out, const Json::Value* value, cmJSONState* state)
+{
+  static auto const helper = JSONHelperBuilder::Int();
+  return helper(out, value, state);
+}
+
+bool PresetOptionalIntHelper(cm::optional<int>& out, const Json::Value* value,
+                             cmJSONState* state)
+{
+  static auto const helper = JSONHelperBuilder::Optional<int>(PresetIntHelper);
+  return helper(out, value, state);
+}
+
+bool PresetVectorIntHelper(std::vector<int>& out, const Json::Value* value,
+                           cmJSONState* state)
 {
   static auto const helper = JSONHelperBuilder::Vector<int>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, PresetIntHelper);
-
-  return helper(out, value);
+    cmCMakePresetErrors::INVALID_PRESET, PresetIntHelper);
+  return helper(out, value, state);
 }
 
-cmJSONHelper<std::nullptr_t, ReadFileResult> VendorHelper(ReadFileResult error)
+cmJSONHelper<std::nullptr_t> VendorHelper(const ErrorGenerator& error)
 {
-  return [error](std::nullptr_t& /*out*/,
-                 const Json::Value* value) -> ReadFileResult {
+  return [error](std::nullptr_t& /*out*/, const Json::Value* value,
+                 cmJSONState* state) -> bool {
     if (!value) {
-      return ReadFileResult::READ_OK;
+      return true;
     }
 
     if (!value->isObject()) {
-      return error;
+      error(value, state);
+      return false;
     }
 
-    return ReadFileResult::READ_OK;
+    return true;
   };
 }
 
-ReadFileResult PresetConditionHelper(
+bool PresetConditionHelper(
   std::shared_ptr<cmCMakePresetsGraph::Condition>& out,
-  const Json::Value* value)
+  const Json::Value* value, cmJSONState* state)
 {
   std::unique_ptr<cmCMakePresetsGraph::Condition> ptr;
-  auto result = ConditionHelper(ptr, value);
+  auto result = ConditionHelper(ptr, value, state);
   out = std::move(ptr);
   return result;
 }
 
-ReadFileResult PresetVectorOneOrMoreStringHelper(std::vector<std::string>& out,
-                                                 const Json::Value* value)
+bool PresetVectorOneOrMoreStringHelper(std::vector<std::string>& out,
+                                       const Json::Value* value,
+                                       cmJSONState* state)
 {
   out.clear();
   if (!value) {
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->isString()) {
     out.push_back(value->asString());
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
-  return PresetVectorStringHelper(out, value);
+  return PresetVectorStringHelper(out, value, state);
 }
 
-cmCMakePresetsGraph::ReadFileResult EnvironmentMapHelper(
+bool EnvironmentMapHelper(
   std::map<std::string, cm::optional<std::string>>& out,
-  const Json::Value* value)
+  const Json::Value* value, cmJSONState* state)
 {
   static auto const helper = JSONHelperBuilder::Map<cm::optional<std::string>>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET,
-    EnvironmentHelper);
+    cmCMakePresetErrors::INVALID_PRESET, EnvironmentHelper);
 
-  return helper(out, value);
+  return helper(out, value, state);
 }
 }
 
-cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile(
-  const std::string& filename, RootType rootType, ReadReason readReason,
-  std::vector<File*>& inProgressFiles, File*& file, std::string& errMsg)
+bool cmCMakePresetsGraph::ReadJSONFile(const std::string& filename,
+                                       RootType rootType,
+                                       ReadReason readReason,
+                                       std::vector<File*>& inProgressFiles,
+                                       File*& file, std::string& errMsg)
 {
-  ReadFileResult result;
+  bool result;
 
   for (auto const& f : this->Files) {
     if (cmSystemTools::SameFile(filename, f->Filename)) {
@@ -429,61 +429,67 @@
       auto fileIt =
         std::find(inProgressFiles.begin(), inProgressFiles.end(), file);
       if (fileIt != inProgressFiles.end()) {
-        return cmCMakePresetsGraph::ReadFileResult::CYCLIC_INCLUDE;
+        cmCMakePresetErrors::CYCLIC_INCLUDE(filename, &this->parseState);
+        return false;
       }
 
-      return cmCMakePresetsGraph::ReadFileResult::READ_OK;
+      return true;
     }
   }
 
-  cmsys::ifstream fin(filename.c_str());
-  if (!fin) {
-    errMsg = cmStrCat(filename, ": Failed to read file\n", errMsg);
-    return ReadFileResult::FILE_NOT_FOUND;
-  }
-  // If there's a BOM, toss it.
-  cmsys::FStream::ReadBOM(fin);
-
   Json::Value root;
-  Json::CharReaderBuilder builder;
-  Json::CharReaderBuilder::strictMode(&builder.settings_);
-  if (!Json::parseFromStream(builder, fin, &root, &errMsg)) {
-    errMsg = cmStrCat(filename, ":\n", errMsg);
-    return ReadFileResult::JSON_PARSE_ERROR;
+  this->parseState = cmJSONState(filename, &root);
+  if (!this->parseState.errors.empty()) {
+    return false;
   }
 
   int v = 0;
-  if ((result = RootVersionHelper(v, &root)) != ReadFileResult::READ_OK) {
+  if ((result = RootVersionHelper(v, &root, &parseState)) != true) {
     return result;
   }
   if (v < MIN_VERSION || v > MAX_VERSION) {
-    return ReadFileResult::UNRECOGNIZED_VERSION;
+    cmCMakePresetErrors::UNRECOGNIZED_VERSION(&root["version"],
+                                              &this->parseState);
+    return false;
   }
 
   // Support for build and test presets added in version 2.
-  if (v < 2 &&
-      (root.isMember("buildPresets") || root.isMember("testPresets"))) {
-    return ReadFileResult::BUILD_TEST_PRESETS_UNSUPPORTED;
+  if (v < 2) {
+    if (root.isMember("buildPresets")) {
+      cmCMakePresetErrors::BUILD_TEST_PRESETS_UNSUPPORTED(
+        &root["buildPresets"], &this->parseState);
+      return false;
+    }
+    if (root.isMember("testPresets")) {
+      cmCMakePresetErrors::BUILD_TEST_PRESETS_UNSUPPORTED(&root["testPresets"],
+                                                          &this->parseState);
+      return false;
+    }
   }
 
   // Support for package presets added in version 6.
   if (v < 6 && root.isMember("packagePresets")) {
-    return ReadFileResult::PACKAGE_PRESETS_UNSUPPORTED;
+    cmCMakePresetErrors::PACKAGE_PRESETS_UNSUPPORTED(&root["packagePresets"],
+                                                     &this->parseState);
+    return false;
   }
 
   // Support for workflow presets added in version 6.
   if (v < 6 && root.isMember("workflowPresets")) {
-    return ReadFileResult::WORKFLOW_PRESETS_UNSUPPORTED;
+    cmCMakePresetErrors::WORKFLOW_PRESETS_UNSUPPORTED(&root["workflowPresets"],
+                                                      &this->parseState);
+    return false;
   }
 
   // Support for include added in version 4.
   if (v < 4 && root.isMember("include")) {
-    return ReadFileResult::INCLUDE_UNSUPPORTED;
+    cmCMakePresetErrors::INCLUDE_UNSUPPORTED(&root["include"],
+                                             &this->parseState);
+    return false;
   }
 
   RootPresets presets;
-  if ((result = RootPresetsHelper(presets, &root)) !=
-      ReadFileResult::READ_OK) {
+  if ((result = RootPresetsHelper(presets, &root, &parseState)) != true) {
     return result;
   }
 
@@ -491,12 +497,25 @@
   unsigned int currentMinor = cmVersion::GetMinorVersion();
   unsigned int currentPatch = cmVersion::GetPatchVersion();
   auto const& required = presets.CMakeMinimumRequired;
-  if (required.Major > currentMajor ||
-      (required.Major == currentMajor &&
-       (required.Minor > currentMinor ||
-        (required.Minor == currentMinor &&
-         (required.Patch > currentPatch))))) {
-    return ReadFileResult::UNRECOGNIZED_CMAKE_VERSION;
+  if (required.Major > currentMajor) {
+    ErrorGenerator error = cmCMakePresetErrors::UNRECOGNIZED_CMAKE_VERSION(
+      "major", currentMajor, required.Major);
+    error(&root["cmakeMinimumRequired"]["major"], &this->parseState);
+    return false;
+  }
+  if (required.Major == currentMajor) {
+    if (required.Minor > currentMinor) {
+      ErrorGenerator error = cmCMakePresetErrors::UNRECOGNIZED_CMAKE_VERSION(
+        "minor", currentMinor, required.Minor);
+      error(&root["cmakeMinimumRequired"]["minor"], &this->parseState);
+      return false;
+    }
+    if (required.Minor == currentMinor && required.Patch > currentPatch) {
+      ErrorGenerator error = cmCMakePresetErrors::UNRECOGNIZED_CMAKE_VERSION(
+        "patch", currentPatch, required.Patch);
+      error(&root["cmakeMinimumRequired"]["patch"], &this->parseState);
+      return false;
+    }
   }
 
   auto filePtr = cm::make_unique<File>();
@@ -510,31 +529,43 @@
   for (auto& preset : presets.ConfigurePresets) {
     preset.OriginFile = file;
     if (preset.Name.empty()) {
-      errMsg += R"(\n\t)";
-      errMsg += filename;
-      return ReadFileResult::INVALID_PRESET;
+      // No error, already handled by PresetNameHelper
+      return false;
     }
 
     PresetPair<ConfigurePreset> presetPair;
     presetPair.Unexpanded = preset;
     presetPair.Expanded = cm::nullopt;
     if (!this->ConfigurePresets.emplace(preset.Name, presetPair).second) {
-      return ReadFileResult::DUPLICATE_PRESETS;
+      cmCMakePresetErrors::DUPLICATE_PRESETS(preset.Name, &this->parseState);
+      return false;
     }
 
     // Support for installDir presets added in version 3.
     if (v < 3 && !preset.InstallDir.empty()) {
-      return ReadFileResult::INSTALL_PREFIX_UNSUPPORTED;
+      cmCMakePresetErrors::INSTALL_PREFIX_UNSUPPORTED(&root["installDir"],
+                                                      &this->parseState);
+      return false;
     }
 
     // Support for conditions added in version 3.
     if (v < 3 && preset.ConditionEvaluator) {
-      return ReadFileResult::CONDITION_UNSUPPORTED;
+      cmCMakePresetErrors::CONDITION_UNSUPPORTED(&this->parseState);
+      return false;
     }
 
     // Support for toolchainFile presets added in version 3.
     if (v < 3 && !preset.ToolchainFile.empty()) {
-      return ReadFileResult::TOOLCHAIN_FILE_UNSUPPORTED;
+      cmCMakePresetErrors::TOOLCHAIN_FILE_UNSUPPORTED(&this->parseState);
+      return false;
+    }
+
+    // Support for trace presets added in version 7.
+    if (v < 7 &&
+        (preset.TraceMode.has_value() || preset.TraceFormat.has_value() ||
+         !preset.TraceRedirect.empty() || !preset.TraceSource.empty())) {
+      cmCMakePresetErrors::TRACE_UNSUPPORTED(&this->parseState);
+      return false;
     }
 
     this->ConfigurePresetOrder.push_back(preset.Name);
@@ -543,21 +574,22 @@
   for (auto& preset : presets.BuildPresets) {
     preset.OriginFile = file;
     if (preset.Name.empty()) {
-      errMsg += R"(\n\t)";
-      errMsg += filename;
-      return ReadFileResult::INVALID_PRESET;
+      // No error, already handled by PresetNameHelper
+      return false;
     }
 
     PresetPair<BuildPreset> presetPair;
     presetPair.Unexpanded = preset;
     presetPair.Expanded = cm::nullopt;
     if (!this->BuildPresets.emplace(preset.Name, presetPair).second) {
-      return ReadFileResult::DUPLICATE_PRESETS;
+      cmCMakePresetErrors::DUPLICATE_PRESETS(preset.Name, &this->parseState);
+      return false;
     }
 
     // Support for conditions added in version 3.
     if (v < 3 && preset.ConditionEvaluator) {
-      return ReadFileResult::CONDITION_UNSUPPORTED;
+      cmCMakePresetErrors::CONDITION_UNSUPPORTED(&this->parseState);
+      return false;
     }
 
     this->BuildPresetOrder.push_back(preset.Name);
@@ -566,29 +598,35 @@
   for (auto& preset : presets.TestPresets) {
     preset.OriginFile = file;
     if (preset.Name.empty()) {
-      return ReadFileResult::INVALID_PRESET;
+      // No error, already handled by PresetNameHelper
+      return false;
     }
 
     PresetPair<TestPreset> presetPair;
     presetPair.Unexpanded = preset;
     presetPair.Expanded = cm::nullopt;
     if (!this->TestPresets.emplace(preset.Name, presetPair).second) {
-      return ReadFileResult::DUPLICATE_PRESETS;
+      cmCMakePresetErrors::DUPLICATE_PRESETS(preset.Name, &this->parseState);
+      return false;
     }
 
     // Support for conditions added in version 3.
     if (v < 3 && preset.ConditionEvaluator) {
-      return ReadFileResult::CONDITION_UNSUPPORTED;
+      cmCMakePresetErrors::CONDITION_UNSUPPORTED(&this->parseState);
+      return false;
     }
 
     // Support for TestOutputTruncation added in version 5.
     if (v < 5 && preset.Output && preset.Output->TestOutputTruncation) {
-      return ReadFileResult::TEST_OUTPUT_TRUNCATION_UNSUPPORTED;
+      cmCMakePresetErrors::TEST_OUTPUT_TRUNCATION_UNSUPPORTED(
+        &this->parseState);
+      return false;
     }
 
     // Support for outputJUnitFile added in version 6.
     if (v < 6 && preset.Output && !preset.Output->OutputJUnitFile.empty()) {
-      return ReadFileResult::CTEST_JUNIT_UNSUPPORTED;
+      cmCMakePresetErrors::CTEST_JUNIT_UNSUPPORTED(&this->parseState);
+      return false;
     }
 
     this->TestPresetOrder.push_back(preset.Name);
@@ -597,14 +635,16 @@
   for (auto& preset : presets.PackagePresets) {
     preset.OriginFile = file;
     if (preset.Name.empty()) {
-      return ReadFileResult::INVALID_PRESET;
+      // No error, already handled by PresetNameHelper
+      return false;
     }
 
     PresetPair<PackagePreset> presetPair;
     presetPair.Unexpanded = preset;
     presetPair.Expanded = cm::nullopt;
     if (!this->PackagePresets.emplace(preset.Name, presetPair).second) {
-      return ReadFileResult::DUPLICATE_PRESETS;
+      cmCMakePresetErrors::DUPLICATE_PRESETS(preset.Name, &this->parseState);
+      return false;
     }
 
     // Support for conditions added in version 3, but this requires version 5
@@ -616,14 +656,16 @@
   for (auto& preset : presets.WorkflowPresets) {
     preset.OriginFile = file;
     if (preset.Name.empty()) {
-      return ReadFileResult::INVALID_PRESET;
+      // No error, already handled by PresetNameHelper
+      return false;
     }
 
     PresetPair<WorkflowPreset> presetPair;
     presetPair.Unexpanded = preset;
     presetPair.Expanded = cm::nullopt;
     if (!this->WorkflowPresets.emplace(preset.Name, presetPair).second) {
-      return ReadFileResult::DUPLICATE_PRESETS;
+      cmCMakePresetErrors::DUPLICATE_PRESETS(preset.Name, &this->parseState);
+      return false;
     }
 
     // Support for conditions added in version 3, but this requires version 6
@@ -632,31 +674,63 @@
     this->WorkflowPresetOrder.push_back(preset.Name);
   }
 
-  auto const includeFile = [this, &inProgressFiles, file](
-                             const std::string& include, RootType rootType2,
-                             ReadReason readReason2,
-                             std::string& FailureMessage) -> ReadFileResult {
-    ReadFileResult r;
+  auto const includeFile = [this, &inProgressFiles,
+                            file](const std::string& include,
+                                  RootType rootType2, ReadReason readReason2,
+                                  std::string& FailureMessage) -> bool {
+    bool r;
     File* includedFile;
-    if ((r = this->ReadJSONFile(include, rootType2, readReason2,
-                                inProgressFiles, includedFile,
-                                FailureMessage)) != ReadFileResult::READ_OK) {
+    if ((r =
+           this->ReadJSONFile(include, rootType2, readReason2, inProgressFiles,
+                              includedFile, FailureMessage)) != true) {
       return r;
     }
 
     file->ReachableFiles.insert(includedFile->ReachableFiles.begin(),
                                 includedFile->ReachableFiles.end());
-    return ReadFileResult::READ_OK;
+    return true;
   };
 
-  for (auto include : presets.Include) {
+  std::vector<MacroExpander> macroExpanders;
+
+  MacroExpander environmentMacroExpander =
+    [](const std::string& macroNamespace, const std::string& macroName,
+       std::string& expanded, int /*version*/) -> ExpandMacroResult {
+    if (macroNamespace == "penv") {
+      if (macroName.empty()) {
+        return ExpandMacroResult::Error;
+      }
+      if (cm::optional<std::string> value =
+            cmSystemTools::GetEnvVar(macroName)) {
+        expanded += *value;
+      }
+      return ExpandMacroResult::Ok;
+    }
+
+    return ExpandMacroResult::Ignore;
+  };
+
+  macroExpanders.push_back(environmentMacroExpander);
+
+  for (Json::ArrayIndex i = 0; i < presets.Include.size(); ++i) {
+    auto include = presets.Include[i];
+
+    // Support for macro expansion in includes added in version 7
+    if (v >= 7) {
+      if (ExpandMacros(include, macroExpanders, v) != ExpandMacroResult::Ok) {
+        cmCMakePresetErrors::INVALID_INCLUDE(&root["include"][i],
+                                             &this->parseState);
+        return false;
+      }
+    }
+
     if (!cmSystemTools::FileIsFullPath(include)) {
       auto directory = cmSystemTools::GetFilenamePath(filename);
       include = cmStrCat(directory, '/', include);
     }
 
     if ((result = includeFile(include, rootType, ReadReason::Included,
-                              errMsg)) != ReadFileResult::READ_OK) {
+                              errMsg)) != true) {
       return result;
     }
   }
@@ -665,13 +739,12 @@
     auto cmakePresetsFilename = GetFilename(this->SourceDir);
     if (cmSystemTools::FileExists(cmakePresetsFilename)) {
       if ((result = includeFile(cmakePresetsFilename, RootType::Project,
-                                ReadReason::Root, errMsg)) !=
-          ReadFileResult::READ_OK) {
+                                ReadReason::Root, errMsg)) != true) {
         return result;
       }
     }
   }
 
   inProgressFiles.pop_back();
-  return ReadFileResult::READ_OK;
+  return true;
 }
diff --git a/Source/cmCMakePresetsGraphReadJSONBuildPresets.cxx b/Source/cmCMakePresetsGraphReadJSONBuildPresets.cxx
index 430d7ee..07f2bc3 100644
--- a/Source/cmCMakePresetsGraphReadJSONBuildPresets.cxx
+++ b/Source/cmCMakePresetsGraphReadJSONBuildPresets.cxx
@@ -13,25 +13,27 @@
 #include <cm3p/json/value.h>
 
 #include "cmBuildOptions.h"
+#include "cmCMakePresetErrors.h"
 #include "cmCMakePresetsGraph.h"
 #include "cmCMakePresetsGraphInternal.h"
 #include "cmJSONHelpers.h"
 
+class cmJSONState;
 namespace {
-using ReadFileResult = cmCMakePresetsGraph::ReadFileResult;
 using BuildPreset = cmCMakePresetsGraph::BuildPreset;
-using JSONHelperBuilder = cmJSONHelperBuilder<ReadFileResult>;
+using JSONHelperBuilder = cmJSONHelperBuilder;
 
-ReadFileResult PackageResolveModeHelper(cm::optional<PackageResolveMode>& out,
-                                        const Json::Value* value)
+bool PackageResolveModeHelper(cm::optional<PackageResolveMode>& out,
+                              const Json::Value* value, cmJSONState* state)
 {
   if (!value) {
     out = cm::nullopt;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (!value->isString()) {
-    return ReadFileResult::INVALID_PRESET;
+    cmCMakePresetErrors::INVALID_PRESET(value, state);
+    return false;
   }
 
   if (value->asString() == "on") {
@@ -41,23 +43,25 @@
   } else if (value->asString() == "only") {
     out = PackageResolveMode::OnlyResolve;
   } else {
-    return ReadFileResult::INVALID_PRESET;
+    cmCMakePresetErrors::INVALID_PRESET(value, state);
+    return false;
   }
 
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
-std::function<ReadFileResult(BuildPreset&, const Json::Value*)> const
-  ResolvePackageReferencesHelper =
-    [](BuildPreset& out, const Json::Value* value) -> ReadFileResult {
-  return PackageResolveModeHelper(out.ResolvePackageReferences, value);
+std::function<bool(BuildPreset&, const Json::Value*, cmJSONState*)> const
+  ResolvePackageReferencesHelper = [](BuildPreset& out,
+                                      const Json::Value* value,
+                                      cmJSONState* state) -> bool {
+  return PackageResolveModeHelper(out.ResolvePackageReferences, value, state);
 };
 
 auto const BuildPresetHelper =
-  JSONHelperBuilder::Object<BuildPreset>(ReadFileResult::READ_OK,
-                                         ReadFileResult::INVALID_PRESET, false)
+  JSONHelperBuilder::Object<BuildPreset>(
+    cmCMakePresetErrors::INVALID_PRESET_OBJECT, false)
     .Bind("name"_s, &BuildPreset::Name,
-          cmCMakePresetsGraphInternal::PresetStringHelper)
+          cmCMakePresetsGraphInternal::PresetNameHelper)
     .Bind("inherits"_s, &BuildPreset::Inherits,
           cmCMakePresetsGraphInternal::PresetVectorOneOrMoreStringHelper,
           false)
@@ -65,7 +69,7 @@
           cmCMakePresetsGraphInternal::PresetBoolHelper, false)
     .Bind<std::nullptr_t>("vendor"_s, nullptr,
                           cmCMakePresetsGraphInternal::VendorHelper(
-                            ReadFileResult::INVALID_PRESET),
+                            cmCMakePresetErrors::INVALID_PRESET),
                           false)
     .Bind("displayName"_s, &BuildPreset::DisplayName,
           cmCMakePresetsGraphInternal::PresetStringHelper, false)
@@ -97,13 +101,12 @@
 }
 
 namespace cmCMakePresetsGraphInternal {
-ReadFileResult BuildPresetsHelper(std::vector<BuildPreset>& out,
-                                  const Json::Value* value)
+bool BuildPresetsHelper(std::vector<BuildPreset>& out,
+                        const Json::Value* value, cmJSONState* state)
 {
   static auto const helper = JSONHelperBuilder::Vector<BuildPreset>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESETS,
-    BuildPresetHelper);
+    cmCMakePresetErrors::INVALID_PRESETS, BuildPresetHelper);
 
-  return helper(out, value);
+  return helper(out, value, state);
 }
 }
diff --git a/Source/cmCMakePresetsGraphReadJSONConfigurePresets.cxx b/Source/cmCMakePresetsGraphReadJSONConfigurePresets.cxx
index 7cff55a..66ec6a4 100644
--- a/Source/cmCMakePresetsGraphReadJSONConfigurePresets.cxx
+++ b/Source/cmCMakePresetsGraphReadJSONConfigurePresets.cxx
@@ -12,72 +12,80 @@
 
 #include <cm3p/json/value.h>
 
+#include "cmCMakePresetErrors.h"
 #include "cmCMakePresetsGraph.h"
 #include "cmCMakePresetsGraphInternal.h"
 #include "cmJSONHelpers.h"
+#include "cmJSONState.h"
+#include "cmStateTypes.h"
 
 namespace {
-using ReadFileResult = cmCMakePresetsGraph::ReadFileResult;
 using CacheVariable = cmCMakePresetsGraph::CacheVariable;
 using ConfigurePreset = cmCMakePresetsGraph::ConfigurePreset;
 using ArchToolsetStrategy = cmCMakePresetsGraph::ArchToolsetStrategy;
-using JSONHelperBuilder = cmJSONHelperBuilder<ReadFileResult>;
+using JSONHelperBuilder = cmJSONHelperBuilder;
+using TraceEnableMode = cmCMakePresetsGraph::TraceEnableMode;
+using TraceOutputFormat = cmTraceEnums::TraceOutputFormat;
 
-ReadFileResult ArchToolsetStrategyHelper(
-  cm::optional<ArchToolsetStrategy>& out, const Json::Value* value)
+bool ArchToolsetStrategyHelper(cm::optional<ArchToolsetStrategy>& out,
+                               const Json::Value* value, cmJSONState* state)
 {
   if (!value) {
     out = cm::nullopt;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (!value->isString()) {
-    return ReadFileResult::INVALID_PRESET;
+    cmCMakePresetErrors::INVALID_PRESET(value, state);
+    return false;
   }
 
   if (value->asString() == "set") {
     out = ArchToolsetStrategy::Set;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "external") {
     out = ArchToolsetStrategy::External;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
-  return ReadFileResult::INVALID_PRESET;
+  cmCMakePresetErrors::INVALID_PRESET(value, state);
+  return false;
 }
 
-std::function<ReadFileResult(ConfigurePreset&, const Json::Value*)>
+std::function<bool(ConfigurePreset&, const Json::Value*, cmJSONState*)>
 ArchToolsetHelper(
   std::string ConfigurePreset::*valueField,
   cm::optional<ArchToolsetStrategy> ConfigurePreset::*strategyField)
 {
   auto const objectHelper =
-    JSONHelperBuilder::Object<ConfigurePreset>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+    JSONHelperBuilder::Object<ConfigurePreset>(JsonErrors::INVALID_OBJECT,
+                                               false)
       .Bind("value", valueField,
             cmCMakePresetsGraphInternal::PresetStringHelper, false)
       .Bind("strategy", strategyField, ArchToolsetStrategyHelper, false);
-  return [valueField, strategyField, objectHelper](
-           ConfigurePreset& out, const Json::Value* value) -> ReadFileResult {
+  return [valueField, strategyField,
+          objectHelper](ConfigurePreset& out, const Json::Value* value,
+                        cmJSONState* state) -> bool {
     if (!value) {
       (out.*valueField).clear();
       out.*strategyField = cm::nullopt;
-      return ReadFileResult::READ_OK;
+      return true;
     }
 
     if (value->isString()) {
       out.*valueField = value->asString();
       out.*strategyField = cm::nullopt;
-      return ReadFileResult::READ_OK;
+      return true;
     }
 
     if (value->isObject()) {
-      return objectHelper(out, value);
+      return objectHelper(out, value, state);
     }
 
-    return ReadFileResult::INVALID_PRESET;
+    cmCMakePresetErrors::INVALID_PRESET(value, state);
+    return false;
   };
 }
 
@@ -86,65 +94,118 @@
 auto const ToolsetHelper = ArchToolsetHelper(
   &ConfigurePreset::Toolset, &ConfigurePreset::ToolsetStrategy);
 
-auto const VariableStringHelper = JSONHelperBuilder::String(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_VARIABLE);
+bool TraceEnableModeHelper(cm::optional<TraceEnableMode>& out,
+                           const Json::Value* value, cmJSONState* state)
+{
+  if (!value) {
+    out = cm::nullopt;
+    return true;
+  }
 
-ReadFileResult VariableValueHelper(std::string& out, const Json::Value* value)
+  if (!value->isString()) {
+    cmCMakePresetErrors::INVALID_PRESET(value, state);
+    return false;
+  }
+
+  if (value->asString() == "on") {
+    out = TraceEnableMode::Default;
+  } else if (value->asString() == "off") {
+    out = TraceEnableMode::Disable;
+  } else if (value->asString() == "expand") {
+    out = TraceEnableMode::Expand;
+  } else {
+    cmCMakePresetErrors::INVALID_PRESET(value, state);
+    return false;
+  }
+
+  return true;
+}
+
+bool TraceOutputFormatHelper(cm::optional<TraceOutputFormat>& out,
+                             const Json::Value* value, cmJSONState* state)
+{
+  if (!value) {
+    out = cm::nullopt;
+    return true;
+  }
+
+  if (!value->isString()) {
+    cmCMakePresetErrors::INVALID_PRESET(value, state);
+    return false;
+  }
+
+  if (value->asString() == "human") {
+    out = TraceOutputFormat::Human;
+  } else if (value->asString() == "json-v1") {
+    out = TraceOutputFormat::JSONv1;
+  } else {
+    cmCMakePresetErrors::INVALID_PRESET(value, state);
+    return false;
+  }
+
+  return true;
+}
+
+auto const VariableStringHelper = JSONHelperBuilder::String();
+
+bool VariableValueHelper(std::string& out, const Json::Value* value,
+                         cmJSONState* state)
 {
   if (!value) {
     out.clear();
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->isBool()) {
     out = value->asBool() ? "TRUE" : "FALSE";
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
-  return VariableStringHelper(out, value);
+  return VariableStringHelper(out, value, state);
 }
 
 auto const VariableObjectHelper =
   JSONHelperBuilder::Object<CacheVariable>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_VARIABLE, false)
+    cmCMakePresetErrors::INVALID_VARIABLE_OBJECT, false)
     .Bind("type"_s, &CacheVariable::Type, VariableStringHelper, false)
     .Bind("value"_s, &CacheVariable::Value, VariableValueHelper);
 
-ReadFileResult VariableHelper(cm::optional<CacheVariable>& out,
-                              const Json::Value* value)
+bool VariableHelper(cm::optional<CacheVariable>& out, const Json::Value* value,
+                    cmJSONState* state)
 {
   if (value->isBool()) {
     out = CacheVariable{
       /*Type=*/"BOOL",
       /*Value=*/value->asBool() ? "TRUE" : "FALSE",
     };
-    return ReadFileResult::READ_OK;
+    return true;
   }
   if (value->isString()) {
     out = CacheVariable{
       /*Type=*/"",
       /*Value=*/value->asString(),
     };
-    return ReadFileResult::READ_OK;
+    return true;
   }
   if (value->isObject()) {
     out.emplace();
-    return VariableObjectHelper(*out, value);
+    return VariableObjectHelper(*out, value, state);
   }
   if (value->isNull()) {
     out = cm::nullopt;
-    return ReadFileResult::READ_OK;
+    return true;
   }
-  return ReadFileResult::INVALID_VARIABLE;
+  cmCMakePresetErrors::INVALID_VARIABLE(value, state);
+  return false;
 }
 
 auto const VariablesHelper =
   JSONHelperBuilder::Map<cm::optional<CacheVariable>>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, VariableHelper);
+    cmCMakePresetErrors::INVALID_PRESET, VariableHelper);
 
 auto const PresetWarningsHelper =
   JSONHelperBuilder::Object<ConfigurePreset>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+    JsonErrors::INVALID_NAMED_OBJECT_KEY, false)
     .Bind("dev"_s, &ConfigurePreset::WarnDev,
           cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
     .Bind("deprecated"_s, &ConfigurePreset::WarnDeprecated,
@@ -158,7 +219,7 @@
 
 auto const PresetErrorsHelper =
   JSONHelperBuilder::Object<ConfigurePreset>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+    JsonErrors::INVALID_NAMED_OBJECT_KEY, false)
     .Bind("dev"_s, &ConfigurePreset::ErrorDev,
           cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
     .Bind("deprecated"_s, &ConfigurePreset::ErrorDeprecated,
@@ -166,7 +227,7 @@
 
 auto const PresetDebugHelper =
   JSONHelperBuilder::Object<ConfigurePreset>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+    JsonErrors::INVALID_NAMED_OBJECT_KEY, false)
     .Bind("output"_s, &ConfigurePreset::DebugOutput,
           cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
     .Bind("tryCompile"_s, &ConfigurePreset::DebugTryCompile,
@@ -174,11 +235,23 @@
     .Bind("find"_s, &ConfigurePreset::DebugFind,
           cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false);
 
+auto const PresetTraceHelper =
+  JSONHelperBuilder::Object<ConfigurePreset>(
+    cmCMakePresetErrors::INVALID_PRESET_OBJECT, false)
+    .Bind("mode"_s, &ConfigurePreset::TraceMode, TraceEnableModeHelper, false)
+    .Bind("format"_s, &ConfigurePreset::TraceFormat, TraceOutputFormatHelper,
+          false)
+    .Bind("source"_s, &ConfigurePreset::TraceSource,
+          cmCMakePresetsGraphInternal::PresetVectorOneOrMoreStringHelper,
+          false)
+    .Bind("redirect"_s, &ConfigurePreset::TraceRedirect,
+          cmCMakePresetsGraphInternal::PresetStringHelper, false);
+
 auto const ConfigurePresetHelper =
   JSONHelperBuilder::Object<ConfigurePreset>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+    cmCMakePresetErrors::INVALID_PRESET_OBJECT, false)
     .Bind("name"_s, &ConfigurePreset::Name,
-          cmCMakePresetsGraphInternal::PresetStringHelper)
+          cmCMakePresetsGraphInternal::PresetNameHelper)
     .Bind("inherits"_s, &ConfigurePreset::Inherits,
           cmCMakePresetsGraphInternal::PresetVectorOneOrMoreStringHelper,
           false)
@@ -186,7 +259,7 @@
           cmCMakePresetsGraphInternal::PresetBoolHelper, false)
     .Bind<std::nullptr_t>("vendor"_s, nullptr,
                           cmCMakePresetsGraphInternal::VendorHelper(
-                            ReadFileResult::INVALID_PRESET),
+                            cmCMakePresetErrors::INVALID_PRESET),
                           false)
     .Bind("displayName"_s, &ConfigurePreset::DisplayName,
           cmCMakePresetsGraphInternal::PresetStringHelper, false)
@@ -211,18 +284,18 @@
     .Bind("warnings"_s, PresetWarningsHelper, false)
     .Bind("errors"_s, PresetErrorsHelper, false)
     .Bind("debug"_s, PresetDebugHelper, false)
+    .Bind("trace"_s, PresetTraceHelper, false)
     .Bind("condition"_s, &ConfigurePreset::ConditionEvaluator,
           cmCMakePresetsGraphInternal::PresetConditionHelper, false);
 }
 
 namespace cmCMakePresetsGraphInternal {
-ReadFileResult ConfigurePresetsHelper(std::vector<ConfigurePreset>& out,
-                                      const Json::Value* value)
+bool ConfigurePresetsHelper(std::vector<ConfigurePreset>& out,
+                            const Json::Value* value, cmJSONState* state)
 {
   static auto const helper = JSONHelperBuilder::Vector<ConfigurePreset>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESETS,
-    ConfigurePresetHelper);
+    cmCMakePresetErrors::INVALID_PRESETS, ConfigurePresetHelper);
 
-  return helper(out, value);
+  return helper(out, value, state);
 }
 }
diff --git a/Source/cmCMakePresetsGraphReadJSONPackagePresets.cxx b/Source/cmCMakePresetsGraphReadJSONPackagePresets.cxx
index 4ae51b1..7290d4d 100644
--- a/Source/cmCMakePresetsGraphReadJSONPackagePresets.cxx
+++ b/Source/cmCMakePresetsGraphReadJSONPackagePresets.cxx
@@ -12,34 +12,34 @@
 
 #include <cm3p/json/value.h>
 
+#include "cmCMakePresetErrors.h"
 #include "cmCMakePresetsGraph.h"
 #include "cmCMakePresetsGraphInternal.h"
 #include "cmJSONHelpers.h"
+class cmJSONState;
 
 namespace {
-using ReadFileResult = cmCMakePresetsGraph::ReadFileResult;
 using PackagePreset = cmCMakePresetsGraph::PackagePreset;
 
 auto const OutputHelper =
-  cmJSONHelperBuilder<ReadFileResult>::Object<PackagePreset>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+  cmJSONHelperBuilder::Object<PackagePreset>(
+    JsonErrors::INVALID_NAMED_OBJECT_KEY, false)
     .Bind("debug"_s, &PackagePreset::DebugOutput,
           cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
     .Bind("verbose"_s, &PackagePreset::VerboseOutput,
           cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false);
 
-auto const VariableHelper = cmJSONHelperBuilder<ReadFileResult>::String(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_VARIABLE);
+auto const VariableHelper =
+  cmJSONHelperBuilder::String(cmCMakePresetErrors::INVALID_VARIABLE);
 
-auto const VariablesHelper =
-  cmJSONHelperBuilder<ReadFileResult>::Map<std::string>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, VariableHelper);
+auto const VariablesHelper = cmJSONHelperBuilder::Map<std::string>(
+  cmCMakePresetErrors::INVALID_VARIABLE, VariableHelper);
 
 auto const PackagePresetHelper =
-  cmJSONHelperBuilder<ReadFileResult>::Object<PackagePreset>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+  cmJSONHelperBuilder::Object<PackagePreset>(
+    cmCMakePresetErrors::INVALID_PRESET_OBJECT, false)
     .Bind("name"_s, &PackagePreset::Name,
-          cmCMakePresetsGraphInternal::PresetStringHelper)
+          cmCMakePresetsGraphInternal::PresetNameHelper)
     .Bind("inherits"_s, &PackagePreset::Inherits,
           cmCMakePresetsGraphInternal::PresetVectorOneOrMoreStringHelper,
           false)
@@ -47,7 +47,7 @@
           cmCMakePresetsGraphInternal::PresetBoolHelper, false)
     .Bind<std::nullptr_t>("vendor"_s, nullptr,
                           cmCMakePresetsGraphInternal::VendorHelper(
-                            ReadFileResult::INVALID_PRESET),
+                            cmCMakePresetErrors::INVALID_PRESET),
                           false)
     .Bind("displayName"_s, &PackagePreset::DisplayName,
           cmCMakePresetsGraphInternal::PresetStringHelper, false)
@@ -81,15 +81,12 @@
 }
 
 namespace cmCMakePresetsGraphInternal {
-cmCMakePresetsGraph::ReadFileResult PackagePresetsHelper(
-  std::vector<cmCMakePresetsGraph::PackagePreset>& out,
-  const Json::Value* value)
+bool PackagePresetsHelper(std::vector<cmCMakePresetsGraph::PackagePreset>& out,
+                          const Json::Value* value, cmJSONState* state)
 {
-  static auto const helper =
-    cmJSONHelperBuilder<ReadFileResult>::Vector<PackagePreset>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESETS,
-      PackagePresetHelper);
+  static auto const helper = cmJSONHelperBuilder::Vector<PackagePreset>(
+    cmCMakePresetErrors::INVALID_PRESETS, PackagePresetHelper);
 
-  return helper(out, value);
+  return helper(out, value, state);
 }
 }
diff --git a/Source/cmCMakePresetsGraphReadJSONTestPresets.cxx b/Source/cmCMakePresetsGraphReadJSONTestPresets.cxx
index 3856f63..791be04 100644
--- a/Source/cmCMakePresetsGraphReadJSONTestPresets.cxx
+++ b/Source/cmCMakePresetsGraphReadJSONTestPresets.cxx
@@ -12,86 +12,93 @@
 
 #include <cm3p/json/value.h>
 
+#include "cmCMakePresetErrors.h"
 #include "cmCMakePresetsGraph.h"
 #include "cmCMakePresetsGraphInternal.h"
 #include "cmJSONHelpers.h"
 
 #include "CTest/cmCTestTypes.h"
 
-namespace {
-using ReadFileResult = cmCMakePresetsGraph::ReadFileResult;
-using TestPreset = cmCMakePresetsGraph::TestPreset;
-using JSONHelperBuilder = cmJSONHelperBuilder<ReadFileResult>;
+class cmJSONState;
 
-ReadFileResult TestPresetOutputVerbosityHelper(
-  TestPreset::OutputOptions::VerbosityEnum& out, const Json::Value* value)
+namespace {
+using TestPreset = cmCMakePresetsGraph::TestPreset;
+using JSONHelperBuilder = cmJSONHelperBuilder;
+
+bool TestPresetOutputVerbosityHelper(
+  TestPreset::OutputOptions::VerbosityEnum& out, const Json::Value* value,
+  cmJSONState* state)
 {
   if (!value) {
     out = TestPreset::OutputOptions::VerbosityEnum::Default;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (!value->isString()) {
-    return ReadFileResult::INVALID_PRESET;
+    cmCMakePresetErrors::INVALID_PRESET(value, state);
+    return false;
   }
 
   if (value->asString() == "default") {
     out = TestPreset::OutputOptions::VerbosityEnum::Default;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "verbose") {
     out = TestPreset::OutputOptions::VerbosityEnum::Verbose;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "extra") {
     out = TestPreset::OutputOptions::VerbosityEnum::Extra;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
-  return ReadFileResult::INVALID_PRESET;
+  cmCMakePresetErrors::INVALID_PRESET(value, state);
+  return false;
 }
 
 auto const TestPresetOptionalOutputVerbosityHelper =
   JSONHelperBuilder::Optional<TestPreset::OutputOptions::VerbosityEnum>(
-    ReadFileResult::READ_OK, TestPresetOutputVerbosityHelper);
+    TestPresetOutputVerbosityHelper);
 
-ReadFileResult TestPresetOutputTruncationHelper(
-  cm::optional<cmCTestTypes::TruncationMode>& out, const Json::Value* value)
+bool TestPresetOutputTruncationHelper(
+  cm::optional<cmCTestTypes::TruncationMode>& out, const Json::Value* value,
+  cmJSONState* state)
 {
   if (!value) {
     out = cm::nullopt;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (!value->isString()) {
-    return ReadFileResult::INVALID_PRESET;
+    cmCMakePresetErrors::INVALID_PRESET(value, state);
+    return false;
   }
 
   if (value->asString() == "tail") {
     out = cmCTestTypes::TruncationMode::Tail;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "middle") {
     out = cmCTestTypes::TruncationMode::Middle;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "head") {
     out = cmCTestTypes::TruncationMode::Head;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
-  return ReadFileResult::INVALID_PRESET;
+  cmCMakePresetErrors::INVALID_PRESET(value, state);
+  return false;
 }
 
 auto const TestPresetOptionalOutputHelper =
   JSONHelperBuilder::Optional<TestPreset::OutputOptions>(
-    ReadFileResult::READ_OK,
     JSONHelperBuilder::Object<TestPreset::OutputOptions>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+      JsonErrors::INVALID_OBJECT, false)
       .Bind("shortProgress"_s, &TestPreset::OutputOptions::ShortProgress,
             cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
       .Bind("verbosity"_s, &TestPreset::OutputOptions::Verbosity,
@@ -125,9 +132,7 @@
 
 auto const TestPresetOptionalFilterIncludeIndexObjectHelper =
   JSONHelperBuilder::Optional<TestPreset::IncludeOptions::IndexOptions>(
-    ReadFileResult::READ_OK,
-    JSONHelperBuilder::Object<TestPreset::IncludeOptions::IndexOptions>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET)
+    JSONHelperBuilder::Object<TestPreset::IncludeOptions::IndexOptions>()
       .Bind("start"_s, &TestPreset::IncludeOptions::IndexOptions::Start,
             cmCMakePresetsGraphInternal::PresetOptionalIntHelper, false)
       .Bind("end"_s, &TestPreset::IncludeOptions::IndexOptions::End,
@@ -138,33 +143,31 @@
             &TestPreset::IncludeOptions::IndexOptions::SpecificTests,
             cmCMakePresetsGraphInternal::PresetVectorIntHelper, false));
 
-ReadFileResult TestPresetOptionalFilterIncludeIndexHelper(
+bool TestPresetOptionalFilterIncludeIndexHelper(
   cm::optional<TestPreset::IncludeOptions::IndexOptions>& out,
-  const Json::Value* value)
+  const Json::Value* value, cmJSONState* state)
 {
   if (!value) {
     out = cm::nullopt;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->isString()) {
     out.emplace();
     out->IndexFile = value->asString();
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->isObject()) {
-    return TestPresetOptionalFilterIncludeIndexObjectHelper(out, value);
+    return TestPresetOptionalFilterIncludeIndexObjectHelper(out, value, state);
   }
 
-  return ReadFileResult::INVALID_PRESET;
+  return false;
 }
 
 auto const TestPresetOptionalFilterIncludeHelper =
   JSONHelperBuilder::Optional<TestPreset::IncludeOptions>(
-    ReadFileResult::READ_OK,
-    JSONHelperBuilder::Object<TestPreset::IncludeOptions>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET)
+    JSONHelperBuilder::Object<TestPreset::IncludeOptions>()
       .Bind("name"_s, &TestPreset::IncludeOptions::Name,
             cmCMakePresetsGraphInternal::PresetStringHelper, false)
       .Bind("label"_s, &TestPreset::IncludeOptions::Label,
@@ -176,9 +179,7 @@
 
 auto const TestPresetOptionalFilterExcludeFixturesHelper =
   JSONHelperBuilder::Optional<TestPreset::ExcludeOptions::FixturesOptions>(
-    ReadFileResult::READ_OK,
-    JSONHelperBuilder::Object<TestPreset::ExcludeOptions::FixturesOptions>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET)
+    JSONHelperBuilder::Object<TestPreset::ExcludeOptions::FixturesOptions>()
       .Bind("any"_s, &TestPreset::ExcludeOptions::FixturesOptions::Any,
             cmCMakePresetsGraphInternal::PresetStringHelper, false)
       .Bind("setup"_s, &TestPreset::ExcludeOptions::FixturesOptions::Setup,
@@ -188,9 +189,7 @@
 
 auto const TestPresetOptionalFilterExcludeHelper =
   JSONHelperBuilder::Optional<TestPreset::ExcludeOptions>(
-    ReadFileResult::READ_OK,
-    JSONHelperBuilder::Object<TestPreset::ExcludeOptions>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET)
+    JSONHelperBuilder::Object<TestPreset::ExcludeOptions>()
       .Bind("name"_s, &TestPreset::ExcludeOptions::Name,
             cmCMakePresetsGraphInternal::PresetStringHelper, false)
       .Bind("label"_s, &TestPreset::ExcludeOptions::Label,
@@ -198,110 +197,113 @@
       .Bind("fixtures"_s, &TestPreset::ExcludeOptions::Fixtures,
             TestPresetOptionalFilterExcludeFixturesHelper, false));
 
-ReadFileResult TestPresetExecutionShowOnlyHelper(
-  TestPreset::ExecutionOptions::ShowOnlyEnum& out, const Json::Value* value)
+bool TestPresetExecutionShowOnlyHelper(
+  TestPreset::ExecutionOptions::ShowOnlyEnum& out, const Json::Value* value,
+  cmJSONState* state)
 {
   if (!value || !value->isString()) {
-    return ReadFileResult::INVALID_PRESET;
+    cmCMakePresetErrors::INVALID_PRESET(value, state);
+    return false;
   }
 
   if (value->asString() == "human") {
     out = TestPreset::ExecutionOptions::ShowOnlyEnum::Human;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "json-v1") {
     out = TestPreset::ExecutionOptions::ShowOnlyEnum::JsonV1;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
-  return ReadFileResult::INVALID_PRESET;
+  cmCMakePresetErrors::INVALID_PRESET(value, state);
+  return false;
 }
 
 auto const TestPresetOptionalExecutionShowOnlyHelper =
   JSONHelperBuilder::Optional<TestPreset::ExecutionOptions::ShowOnlyEnum>(
-    ReadFileResult::READ_OK, TestPresetExecutionShowOnlyHelper);
+    TestPresetExecutionShowOnlyHelper);
 
-ReadFileResult TestPresetExecutionModeHelper(
+bool TestPresetExecutionModeHelper(
   TestPreset::ExecutionOptions::RepeatOptions::ModeEnum& out,
-  const Json::Value* value)
+  const Json::Value* value, cmJSONState* state)
 {
   if (!value) {
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (!value->isString()) {
-    return ReadFileResult::INVALID_PRESET;
+    cmCMakePresetErrors::INVALID_PRESET(value, state);
+    return false;
   }
 
   if (value->asString() == "until-fail") {
     out = TestPreset::ExecutionOptions::RepeatOptions::ModeEnum::UntilFail;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "until-pass") {
     out = TestPreset::ExecutionOptions::RepeatOptions::ModeEnum::UntilPass;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "after-timeout") {
     out = TestPreset::ExecutionOptions::RepeatOptions::ModeEnum::AfterTimeout;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
-  return ReadFileResult::INVALID_PRESET;
+  cmCMakePresetErrors::INVALID_PRESET(value, state);
+  return false;
 }
 
 auto const TestPresetOptionalExecutionRepeatHelper =
   JSONHelperBuilder::Optional<TestPreset::ExecutionOptions::RepeatOptions>(
-    ReadFileResult::READ_OK,
-    JSONHelperBuilder::Object<TestPreset::ExecutionOptions::RepeatOptions>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET)
+    JSONHelperBuilder::Object<TestPreset::ExecutionOptions::RepeatOptions>()
       .Bind("mode"_s, &TestPreset::ExecutionOptions::RepeatOptions::Mode,
             TestPresetExecutionModeHelper, true)
       .Bind("count"_s, &TestPreset::ExecutionOptions::RepeatOptions::Count,
             cmCMakePresetsGraphInternal::PresetIntHelper, true));
 
-ReadFileResult TestPresetExecutionNoTestsActionHelper(
+bool TestPresetExecutionNoTestsActionHelper(
   TestPreset::ExecutionOptions::NoTestsActionEnum& out,
-  const Json::Value* value)
+  const Json::Value* value, cmJSONState* state)
 {
   if (!value) {
     out = TestPreset::ExecutionOptions::NoTestsActionEnum::Default;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (!value->isString()) {
-    return ReadFileResult::INVALID_PRESET;
+    cmCMakePresetErrors::INVALID_PRESET(value, state);
+    return false;
   }
 
   if (value->asString() == "default") {
     out = TestPreset::ExecutionOptions::NoTestsActionEnum::Default;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "error") {
     out = TestPreset::ExecutionOptions::NoTestsActionEnum::Error;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "ignore") {
     out = TestPreset::ExecutionOptions::NoTestsActionEnum::Ignore;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
-  return ReadFileResult::INVALID_PRESET;
+  cmCMakePresetErrors::INVALID_PRESET(value, state);
+  return false;
 }
 
 auto const TestPresetOptionalExecutionNoTestsActionHelper =
   JSONHelperBuilder::Optional<TestPreset::ExecutionOptions::NoTestsActionEnum>(
-    ReadFileResult::READ_OK, TestPresetExecutionNoTestsActionHelper);
+    TestPresetExecutionNoTestsActionHelper);
 
 auto const TestPresetExecutionHelper =
   JSONHelperBuilder::Optional<TestPreset::ExecutionOptions>(
-    ReadFileResult::READ_OK,
-    JSONHelperBuilder::Object<TestPreset::ExecutionOptions>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET)
+    JSONHelperBuilder::Object<TestPreset::ExecutionOptions>()
       .Bind("stopOnFailure"_s, &TestPreset::ExecutionOptions::StopOnFailure,
             cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
       .Bind("enableFailover"_s, &TestPreset::ExecutionOptions::EnableFailover,
@@ -329,19 +331,17 @@
 
 auto const TestPresetFilterHelper =
   JSONHelperBuilder::Optional<TestPreset::FilterOptions>(
-    ReadFileResult::READ_OK,
-    JSONHelperBuilder::Object<TestPreset::FilterOptions>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET)
+    JSONHelperBuilder::Object<TestPreset::FilterOptions>()
       .Bind("include"_s, &TestPreset::FilterOptions::Include,
             TestPresetOptionalFilterIncludeHelper, false)
       .Bind("exclude"_s, &TestPreset::FilterOptions::Exclude,
             TestPresetOptionalFilterExcludeHelper, false));
 
 auto const TestPresetHelper =
-  JSONHelperBuilder::Object<TestPreset>(ReadFileResult::READ_OK,
-                                        ReadFileResult::INVALID_PRESET, false)
+  JSONHelperBuilder::Object<TestPreset>(
+    cmCMakePresetErrors::INVALID_PRESET_OBJECT, false)
     .Bind("name"_s, &TestPreset::Name,
-          cmCMakePresetsGraphInternal::PresetStringHelper)
+          cmCMakePresetsGraphInternal::PresetNameHelper)
     .Bind("inherits"_s, &TestPreset::Inherits,
           cmCMakePresetsGraphInternal::PresetVectorOneOrMoreStringHelper,
           false)
@@ -349,7 +349,7 @@
           cmCMakePresetsGraphInternal::PresetBoolHelper, false)
     .Bind<std::nullptr_t>("vendor"_s, nullptr,
                           cmCMakePresetsGraphInternal::VendorHelper(
-                            ReadFileResult::INVALID_PRESET),
+                            cmCMakePresetErrors::INVALID_PRESET),
                           false)
     .Bind("displayName"_s, &TestPreset::DisplayName,
           cmCMakePresetsGraphInternal::PresetStringHelper, false)
@@ -377,13 +377,12 @@
 }
 
 namespace cmCMakePresetsGraphInternal {
-cmCMakePresetsGraph::ReadFileResult TestPresetsHelper(
-  std::vector<cmCMakePresetsGraph::TestPreset>& out, const Json::Value* value)
+bool TestPresetsHelper(std::vector<cmCMakePresetsGraph::TestPreset>& out,
+                       const Json::Value* value, cmJSONState* state)
 {
   static auto const helper = JSONHelperBuilder::Vector<TestPreset>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESETS,
-    TestPresetHelper);
+    cmCMakePresetErrors::INVALID_PRESETS, TestPresetHelper);
 
-  return helper(out, value);
+  return helper(out, value, state);
 }
 }
diff --git a/Source/cmCMakePresetsGraphReadJSONWorkflowPresets.cxx b/Source/cmCMakePresetsGraphReadJSONWorkflowPresets.cxx
index 33680a1..7224e17 100644
--- a/Source/cmCMakePresetsGraphReadJSONWorkflowPresets.cxx
+++ b/Source/cmCMakePresetsGraphReadJSONWorkflowPresets.cxx
@@ -9,69 +9,72 @@
 
 #include <cm3p/json/value.h>
 
+#include "cmCMakePresetErrors.h"
 #include "cmCMakePresetsGraph.h"
 #include "cmCMakePresetsGraphInternal.h"
 #include "cmJSONHelpers.h"
 
+class cmJSONState;
+
 namespace {
-using ReadFileResult = cmCMakePresetsGraph::ReadFileResult;
 using WorkflowPreset = cmCMakePresetsGraph::WorkflowPreset;
 
-ReadFileResult WorkflowStepTypeHelper(WorkflowPreset::WorkflowStep::Type& out,
-                                      const Json::Value* value)
+bool WorkflowStepTypeHelper(WorkflowPreset::WorkflowStep::Type& out,
+                            const Json::Value* value, cmJSONState* state)
 {
   if (!value) {
-    return ReadFileResult::INVALID_PRESET;
+    cmCMakePresetErrors::INVALID_PRESET(value, state);
+    return false;
   }
 
   if (!value->isString()) {
-    return ReadFileResult::INVALID_PRESET;
+    return false;
   }
 
   if (value->asString() == "configure") {
     out = WorkflowPreset::WorkflowStep::Type::Configure;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "build") {
     out = WorkflowPreset::WorkflowStep::Type::Build;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "test") {
     out = WorkflowPreset::WorkflowStep::Type::Test;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "package") {
     out = WorkflowPreset::WorkflowStep::Type::Package;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
-  return ReadFileResult::INVALID_PRESET;
+  cmCMakePresetErrors::INVALID_PRESET(value, state);
+  return false;
 }
 
 auto const WorkflowStepHelper =
-  cmJSONHelperBuilder<ReadFileResult>::Object<WorkflowPreset::WorkflowStep>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+  cmJSONHelperBuilder::Object<WorkflowPreset::WorkflowStep>(
+    JsonErrors::INVALID_OBJECT, false)
     .Bind("type"_s, &WorkflowPreset::WorkflowStep::PresetType,
           WorkflowStepTypeHelper)
     .Bind("name"_s, &WorkflowPreset::WorkflowStep::PresetName,
           cmCMakePresetsGraphInternal::PresetStringHelper);
 
 auto const WorkflowStepsHelper =
-  cmJSONHelperBuilder<ReadFileResult>::Vector<WorkflowPreset::WorkflowStep>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET,
-    WorkflowStepHelper);
+  cmJSONHelperBuilder::Vector<WorkflowPreset::WorkflowStep>(
+    cmCMakePresetErrors::INVALID_PRESET, WorkflowStepHelper);
 
 auto const WorkflowPresetHelper =
-  cmJSONHelperBuilder<ReadFileResult>::Object<WorkflowPreset>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+  cmJSONHelperBuilder::Object<WorkflowPreset>(
+    cmCMakePresetErrors::INVALID_PRESET_OBJECT, false)
     .Bind("name"_s, &WorkflowPreset::Name,
-          cmCMakePresetsGraphInternal::PresetStringHelper)
+          cmCMakePresetsGraphInternal::PresetNameHelper)
     .Bind<std::nullptr_t>("vendor"_s, nullptr,
                           cmCMakePresetsGraphInternal::VendorHelper(
-                            ReadFileResult::INVALID_PRESET),
+                            cmCMakePresetErrors::INVALID_PRESET),
                           false)
     .Bind("displayName"_s, &WorkflowPreset::DisplayName,
           cmCMakePresetsGraphInternal::PresetStringHelper, false)
@@ -81,15 +84,13 @@
 }
 
 namespace cmCMakePresetsGraphInternal {
-cmCMakePresetsGraph::ReadFileResult WorkflowPresetsHelper(
+bool WorkflowPresetsHelper(
   std::vector<cmCMakePresetsGraph::WorkflowPreset>& out,
-  const Json::Value* value)
+  const Json::Value* value, cmJSONState* state)
 {
-  static auto const helper =
-    cmJSONHelperBuilder<ReadFileResult>::Vector<WorkflowPreset>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESETS,
-      WorkflowPresetHelper);
+  static auto const helper = cmJSONHelperBuilder::Vector<WorkflowPreset>(
+    cmCMakePresetErrors::INVALID_PRESETS, WorkflowPresetHelper);
 
-  return helper(out, value);
+  return helper(out, value, state);
 }
 }
diff --git a/Source/cmCPluginAPI.cxx b/Source/cmCPluginAPI.cxx
index abec968..13ccf4f 100644
--- a/Source/cmCPluginAPI.cxx
+++ b/Source/cmCPluginAPI.cxx
@@ -14,6 +14,7 @@
 #include "cmMakefile.h"
 #include "cmSourceFile.h"
 #include "cmState.h"
+#include "cmValue.h"
 #include "cmVersion.h"
 
 #ifdef __QNX__
@@ -78,25 +79,37 @@
                                        int type)
 {
   cmMakefile* mf = static_cast<cmMakefile*>(arg);
+  std::string valueString;
+  std::string docString;
+  cmValue v;
+  cmValue d;
+  if (value != nullptr) {
+    valueString = value;
+    v = cmValue{ valueString };
+  }
+  if (doc != nullptr) {
+    docString = doc;
+    d = cmValue{ docString };
+  }
 
   switch (type) {
     case CM_CACHE_BOOL:
-      mf->AddCacheDefinition(name, value, doc, cmStateEnums::BOOL);
+      mf->AddCacheDefinition(name, v, d, cmStateEnums::BOOL);
       break;
     case CM_CACHE_PATH:
-      mf->AddCacheDefinition(name, value, doc, cmStateEnums::PATH);
+      mf->AddCacheDefinition(name, v, d, cmStateEnums::PATH);
       break;
     case CM_CACHE_FILEPATH:
-      mf->AddCacheDefinition(name, value, doc, cmStateEnums::FILEPATH);
+      mf->AddCacheDefinition(name, v, d, cmStateEnums::FILEPATH);
       break;
     case CM_CACHE_STRING:
-      mf->AddCacheDefinition(name, value, doc, cmStateEnums::STRING);
+      mf->AddCacheDefinition(name, v, d, cmStateEnums::STRING);
       break;
     case CM_CACHE_INTERNAL:
-      mf->AddCacheDefinition(name, value, doc, cmStateEnums::INTERNAL);
+      mf->AddCacheDefinition(name, v, d, cmStateEnums::INTERNAL);
       break;
     case CM_CACHE_STATIC:
-      mf->AddCacheDefinition(name, value, doc, cmStateEnums::STATIC);
+      mf->AddCacheDefinition(name, v, d, cmStateEnums::STATIC);
       break;
   }
 }
@@ -615,7 +628,11 @@
 {
   cmCPluginAPISourceFile* sf = static_cast<cmCPluginAPISourceFile*>(arg);
   if (cmSourceFile* rsf = sf->RealSourceFile) {
-    rsf->SetProperty(prop, value);
+    if (value == nullptr) {
+      rsf->SetProperty(prop, nullptr);
+    } else {
+      rsf->SetProperty(prop, value);
+    }
   } else if (prop) {
     if (!value) {
       value = "NOTFOUND";
diff --git a/Source/cmCPluginAPI.h b/Source/cmCPluginAPI.h
index 13a93b7..92dff57 100644
--- a/Source/cmCPluginAPI.h
+++ b/Source/cmCPluginAPI.h
@@ -32,7 +32,7 @@
 typedef struct
 {
   /*=========================================================================
-  Here we define the set of functions that a plugin may call. The first goup
+  Here we define the set of functions that a plugin may call. The first group
   of functions are utility functions that are specific to the plugin API
   =========================================================================*/
   /* set/Get the ClientData in the cmLoadedCommandInfo structure, this is how
diff --git a/Source/cmCTest.cxx b/Source/cmCTest.cxx
index 5899a61..a311041 100644
--- a/Source/cmCTest.cxx
+++ b/Source/cmCTest.cxx
@@ -54,6 +54,8 @@
 #include "cmDynamicLoader.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGlobalGenerator.h"
+#include "cmJSONState.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmProcessOutput.h"
 #include "cmState.h"
@@ -1467,7 +1469,7 @@
       ch->GetCMake()->GetState()->GetGlobalProperty("SubProjectLabels");
     if (labels) {
       xml.StartElement("Labels");
-      std::vector<std::string> args = cmExpandedList(*labels);
+      cmList args{ *labels };
       for (std::string const& i : args) {
         xml.Element("Label", i);
       }
@@ -1499,7 +1501,7 @@
 {
   std::string labelsForSubprojects =
     this->GetCTestConfiguration("LabelsForSubprojects");
-  std::vector<std::string> subprojects = cmExpandedList(labelsForSubprojects);
+  cmList subprojects{ labelsForSubprojects };
 
   // sort the array
   std::sort(subprojects.begin(), subprojects.end());
@@ -1507,7 +1509,7 @@
   auto new_end = std::unique(subprojects.begin(), subprojects.end());
   subprojects.erase(new_end, subprojects.end());
 
-  return subprojects;
+  return std::move(subprojects.data());
 }
 
 void cmCTest::EndXML(cmXMLWriter& xml)
@@ -2336,10 +2338,10 @@
 
   cmCMakePresetsGraph settingsFile;
   auto result = settingsFile.ReadProjectPresets(workingDirectory);
-  if (result != cmCMakePresetsGraph::ReadFileResult::READ_OK) {
-    cmSystemTools::Error(
-      cmStrCat("Could not read presets from ", workingDirectory, ": ",
-               cmCMakePresetsGraph::ResultToString(result)));
+  if (result != true) {
+    cmSystemTools::Error(cmStrCat("Could not read presets from ",
+                                  workingDirectory, ":",
+                                  settingsFile.parseState.GetErrorMessage()));
     return false;
   }
 
@@ -3097,8 +3099,7 @@
   }
   cmCTestLog(this, DEBUG, "PopulateCustomVector: " << def << std::endl);
 
-  vec.clear();
-  cmExpandList(*dval, vec);
+  cmList::assign(vec, *dval);
 
   for (std::string const& it : vec) {
     cmCTestLog(this, DEBUG, "  -- " << it << std::endl);
diff --git a/Source/cmCacheManager.cxx b/Source/cmCacheManager.cxx
index b9d1f66..0c6b225 100644
--- a/Source/cmCacheManager.cxx
+++ b/Source/cmCacheManager.cxx
@@ -12,6 +12,7 @@
 #include "cmsys/Glob.hxx"
 
 #include "cmGeneratedFileStream.h"
+#include "cmList.h"
 #include "cmMessageType.h"
 #include "cmMessenger.h"
 #include "cmState.h"
@@ -83,7 +84,7 @@
         continue;
       }
     }
-    e.SetProperty("HELPSTRING", helpString.c_str());
+    e.SetProperty("HELPSTRING", helpString);
     if (cmState::ParseCacheEntry(realbuffer, entryKey, e.Value, e.Type)) {
       if (excludes.find(entryKey) == excludes.end()) {
         // Load internal values if internal is set.
@@ -101,7 +102,7 @@
                                   " loaded from external file.  "
                                   "To change this value edit this file: ",
                                   path, "/CMakeCache.txt");
-            e.SetProperty("HELPSTRING", helpString.c_str());
+            e.SetProperty("HELPSTRING", helpString);
           }
           if (!this->ReadPropertyEntry(entryKey, e)) {
             e.Initialized = true;
@@ -185,11 +186,11 @@
       std::string key = entryKey.substr(0, entryKey.size() - plen);
       if (auto* entry = this->GetCacheEntry(key)) {
         // Store this property on its entry.
-        entry->SetProperty(p, e.Value.c_str());
+        entry->SetProperty(p, e.Value);
       } else {
         // Create an entry and store the property.
         CacheEntry& ne = this->Cache[key];
-        ne.SetProperty(p, e.Value.c_str());
+        ne.SetProperty(p, e.Value);
       }
       return true;
     }
@@ -522,7 +523,7 @@
 }
 
 void cmCacheManager::AddCacheEntry(const std::string& key, cmValue value,
-                                   const char* helpString,
+                                   cmValue helpString,
                                    cmStateEnums::CacheEntryType type)
 {
   CacheEntry& e = this->Cache[key];
@@ -531,23 +532,20 @@
   // make sure we only use unix style paths
   if (type == cmStateEnums::FILEPATH || type == cmStateEnums::PATH) {
     if (e.Value.find(';') != std::string::npos) {
-      std::vector<std::string> paths = cmExpandedList(e.Value);
-      const char* sep = "";
-      e.Value = "";
+      cmList paths{ e.Value };
       for (std::string& i : paths) {
         cmSystemTools::ConvertToUnixSlashes(i);
-        e.Value += sep;
-        e.Value += i;
-        sep = ";";
       }
+      e.Value = paths.to_string();
     } else {
       cmSystemTools::ConvertToUnixSlashes(e.Value);
     }
   }
-  e.SetProperty("HELPSTRING",
-                helpString
-                  ? helpString
-                  : "(This variable does not exist and should not be used)");
+  e.SetProperty(
+    "HELPSTRING",
+    helpString ? *helpString
+               : std::string{
+                   "(This variable does not exist and should not be used)" });
 }
 
 void cmCacheManager::CacheEntry::SetValue(cmValue value)
@@ -583,12 +581,12 @@
 }
 
 void cmCacheManager::CacheEntry::SetProperty(const std::string& prop,
-                                             const char* value)
+                                             const std::string& value)
 {
   if (prop == "TYPE") {
-    this->Type = cmState::StringToCacheEntryType(value ? value : "STRING");
+    this->Type = cmState::StringToCacheEntryType(value);
   } else if (prop == "VALUE") {
-    this->Value = value ? value : "";
+    this->Value = value;
   } else {
     this->Properties.SetProperty(prop, value);
   }
@@ -596,7 +594,19 @@
 
 void cmCacheManager::CacheEntry::SetProperty(const std::string& p, bool v)
 {
-  this->SetProperty(p, v ? "ON" : "OFF");
+  this->SetProperty(p, v ? std::string{ "ON" } : std::string{ "OFF" });
+}
+
+void cmCacheManager::CacheEntry::SetProperty(const std::string& prop,
+                                             std::nullptr_t)
+{
+  if (prop == "TYPE") {
+    this->Type = cmState::StringToCacheEntryType("STRING");
+  } else if (prop == "VALUE") {
+    this->Value = "";
+  } else {
+    this->Properties.SetProperty(prop, cmValue{ nullptr });
+  }
 }
 
 void cmCacheManager::CacheEntry::AppendProperty(const std::string& prop,
diff --git a/Source/cmCacheManager.h b/Source/cmCacheManager.h
index bc3fb51..5268248 100644
--- a/Source/cmCacheManager.h
+++ b/Source/cmCacheManager.h
@@ -39,8 +39,9 @@
     std::vector<std::string> GetPropertyList() const;
     cmValue GetProperty(const std::string& property) const;
     bool GetPropertyAsBool(const std::string& property) const;
-    void SetProperty(const std::string& property, const char* value);
+    void SetProperty(const std::string& property, const std::string& value);
     void SetProperty(const std::string& property, bool value);
+    void SetProperty(const std::string& property, std::nullptr_t);
     void AppendProperty(const std::string& property, const std::string& value,
                         bool asString = false);
 
@@ -127,7 +128,7 @@
                              std::string const& value)
   {
     if (auto* entry = this->GetCacheEntry(key)) {
-      entry->SetProperty(propName, value.c_str());
+      entry->SetProperty(propName, value);
     }
   }
 
@@ -172,20 +173,19 @@
   unsigned int GetCacheMinorVersion() const { return this->CacheMinorVersion; }
 
   //! Add an entry into the cache
-  void AddCacheEntry(const std::string& key, const char* value,
-                     const char* helpString, cmStateEnums::CacheEntryType type)
-  {
-    this->AddCacheEntry(key,
-                        value ? cmValue(std::string(value)) : cmValue(nullptr),
-                        helpString, type);
-  }
   void AddCacheEntry(const std::string& key, const std::string& value,
-                     const char* helpString, cmStateEnums::CacheEntryType type)
+                     const std::string& helpString,
+                     cmStateEnums::CacheEntryType type)
   {
-    this->AddCacheEntry(key, cmValue(value), helpString, type);
+    this->AddCacheEntry(key, cmValue{ value }, cmValue{ helpString }, type);
   }
   void AddCacheEntry(const std::string& key, cmValue value,
-                     const char* helpString,
+                     const std::string& helpString,
+                     cmStateEnums::CacheEntryType type)
+  {
+    this->AddCacheEntry(key, value, cmValue{ helpString }, type);
+  }
+  void AddCacheEntry(const std::string& key, cmValue value, cmValue helpString,
                      cmStateEnums::CacheEntryType type);
 
   //! Remove an entry from the cache
diff --git a/Source/cmCommandArgumentParserHelper.cxx b/Source/cmCommandArgumentParserHelper.cxx
index 2ed04e5..a20f5a5 100644
--- a/Source/cmCommandArgumentParserHelper.cxx
+++ b/Source/cmCommandArgumentParserHelper.cxx
@@ -96,7 +96,8 @@
   }
   if (this->FileLine >= 0 && strcmp(var, "CMAKE_CURRENT_LIST_LINE") == 0) {
     std::string line;
-    cmListFileContext const& top = this->Makefile->GetBacktrace().Top();
+    cmListFileBacktrace bt = this->Makefile->GetBacktrace();
+    cmListFileContext const& top = bt.Top();
     if (top.DeferId) {
       line = cmStrCat("DEFERRED:"_s, *top.DeferId);
     } else {
diff --git a/Source/cmCommonTargetGenerator.cxx b/Source/cmCommonTargetGenerator.cxx
index 6be0fa3..e635dd9 100644
--- a/Source/cmCommonTargetGenerator.cxx
+++ b/Source/cmCommonTargetGenerator.cxx
@@ -10,9 +10,12 @@
 #include <cmext/string_view>
 
 #include "cmComputeLinkInformation.h"
+#include "cmGeneratorExpression.h"
+#include "cmGeneratorExpressionDAGChecker.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalCommonGenerator.h"
 #include "cmGlobalGenerator.h"
+#include "cmList.h"
 #include "cmLocalCommonGenerator.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
@@ -23,7 +26,7 @@
 #include "cmState.h"
 #include "cmStateTypes.h"
 #include "cmStringAlgorithms.h"
-#include "cmTarget.h"
+#include "cmSystemTools.h"
 #include "cmValue.h"
 
 cmCommonTargetGenerator::cmCommonTargetGenerator(cmGeneratorTarget* gt)
@@ -177,7 +180,9 @@
           // We can ignore the INTERFACE_LIBRARY items because
           // Target->GetLinkInformation already processed their
           // link interface and they don't have any output themselves.
-          && linkee->GetType() != cmStateEnums::INTERFACE_LIBRARY &&
+          && (linkee->GetType() != cmStateEnums::INTERFACE_LIBRARY
+              // Synthesized targets may have relevant rules.
+              || linkee->IsSynthetic()) &&
           ((lang == "CXX"_s && linkee->HaveCxx20ModuleSources()) ||
            (lang == "Fortran"_s && linkee->HaveFortranSources(config))) &&
           emitted.insert(linkee).second) {
@@ -231,7 +236,7 @@
   manifests.reserve(manifest_srcs.size());
 
   std::string lang = this->GeneratorTarget->GetLinkerLanguage(config);
-  std::string const& manifestFlag =
+  std::string manifestFlag =
     this->Makefile->GetDefinition("CMAKE_" + lang + "_LINKER_MANIFEST_FLAG");
   for (cmSourceFile const* manifest_src : manifest_srcs) {
     manifests.push_back(manifestFlag +
@@ -247,7 +252,7 @@
 std::string cmCommonTargetGenerator::GetAIXExports(std::string const&)
 {
   std::string aixExports;
-  if (this->GeneratorTarget->Target->IsAIX()) {
+  if (this->GeneratorTarget->IsAIX()) {
     if (cmValue exportAll =
           this->GeneratorTarget->GetProperty("AIX_EXPORT_ALL_SYMBOLS")) {
       if (cmIsOff(*exportAll)) {
@@ -287,15 +292,184 @@
   }
 }
 
+std::string cmCommonTargetGenerator::GetCompilerLauncher(
+  std::string const& lang, std::string const& config)
+{
+  std::string compilerLauncher;
+  if (lang == "C" || lang == "CXX" || lang == "Fortran" || lang == "CUDA" ||
+      lang == "HIP" || lang == "ISPC" || lang == "OBJC" || lang == "OBJCXX") {
+    std::string const clauncher_prop = cmStrCat(lang, "_COMPILER_LAUNCHER");
+    cmValue clauncher = this->GeneratorTarget->GetProperty(clauncher_prop);
+    std::string const evaluatedClauncher = cmGeneratorExpression::Evaluate(
+      *clauncher, this->GeneratorTarget->GetLocalGenerator(), config,
+      this->GeneratorTarget, nullptr, this->GeneratorTarget, lang);
+    if (!evaluatedClauncher.empty()) {
+      compilerLauncher = evaluatedClauncher;
+    }
+  }
+  return compilerLauncher;
+}
+
+std::string cmCommonTargetGenerator::GenerateCodeCheckRules(
+  cmSourceFile const& source, std::string& compilerLauncher,
+  std::string const& cmakeCmd, std::string const& config,
+  std::function<std::string(std::string const&)> const& pathConverter)
+{
+  auto const lang = source.GetLanguage();
+  std::string tidy;
+  std::string iwyu;
+  std::string cpplint;
+  std::string cppcheck;
+
+  auto evaluateProp = [&](std::string const& prop) -> std::string {
+    auto const value = this->GeneratorTarget->GetProperty(prop);
+    if (!value) {
+      return std::string{};
+    }
+    auto evaluatedProp = cmGeneratorExpression::Evaluate(
+      *value, this->GeneratorTarget->GetLocalGenerator(), config,
+      this->GeneratorTarget, nullptr, this->GeneratorTarget, lang);
+    if (!evaluatedProp.empty()) {
+      return evaluatedProp;
+    }
+    return *value;
+  };
+  std::string const tidy_prop = cmStrCat(lang, "_CLANG_TIDY");
+  tidy = evaluateProp(tidy_prop);
+
+  if (lang == "C" || lang == "CXX") {
+    std::string const iwyu_prop = cmStrCat(lang, "_INCLUDE_WHAT_YOU_USE");
+    iwyu = evaluateProp(iwyu_prop);
+
+    std::string const cpplint_prop = cmStrCat(lang, "_CPPLINT");
+    cpplint = evaluateProp(cpplint_prop);
+
+    std::string const cppcheck_prop = cmStrCat(lang, "_CPPCHECK");
+    cppcheck = evaluateProp(cppcheck_prop);
+  }
+  if (cmNonempty(iwyu) || cmNonempty(tidy) || cmNonempty(cpplint) ||
+      cmNonempty(cppcheck)) {
+    std::string code_check = cmakeCmd + " -E __run_co_compile";
+    if (!compilerLauncher.empty()) {
+      // In __run_co_compile case the launcher command is supplied
+      // via --launcher=<maybe-list> and consumed
+      code_check += " --launcher=";
+      code_check += this->GeneratorTarget->GetLocalGenerator()->EscapeForShell(
+        compilerLauncher);
+      compilerLauncher.clear();
+    }
+    if (cmNonempty(iwyu)) {
+      code_check += " --iwyu=";
+
+      // Only add --driver-mode if it is not already specified, as adding
+      // it unconditionally might override a user-specified driver-mode
+      if (iwyu.find("--driver-mode=") == std::string::npos) {
+        cmValue const p = this->Makefile->GetDefinition(
+          cmStrCat("CMAKE_", lang, "_INCLUDE_WHAT_YOU_USE_DRIVER_MODE"));
+        std::string driverMode;
+
+        if (cmNonempty(p)) {
+          driverMode = *p;
+        } else {
+          driverMode = lang == "C" ? "gcc" : "g++";
+        }
+
+        code_check +=
+          this->GeneratorTarget->GetLocalGenerator()->EscapeForShell(
+            cmStrCat(iwyu, ";--driver-mode=", driverMode));
+      } else {
+        code_check +=
+          this->GeneratorTarget->GetLocalGenerator()->EscapeForShell(iwyu);
+      }
+    }
+    if (cmNonempty(tidy)) {
+      code_check += " --tidy=";
+      cmValue const p = this->Makefile->GetDefinition(
+        "CMAKE_" + lang + "_CLANG_TIDY_DRIVER_MODE");
+      std::string driverMode;
+      if (cmNonempty(p)) {
+        driverMode = *p;
+      } else {
+        driverMode = lang == "C" ? "gcc" : "g++";
+      }
+
+      auto const generatorName = this->GeneratorTarget->GetLocalGenerator()
+                                   ->GetGlobalGenerator()
+                                   ->GetName();
+      auto const clangTidyExportFixedDir =
+        this->GeneratorTarget->GetClangTidyExportFixesDirectory(lang);
+      auto fixesFile = this->GetClangTidyReplacementsFilePath(
+        clangTidyExportFixedDir, source, config);
+      std::string exportFixes;
+      if (!clangTidyExportFixedDir.empty()) {
+        this->GlobalCommonGenerator->AddClangTidyExportFixesDir(
+          clangTidyExportFixedDir);
+      }
+      if (generatorName.find("Make") != std::string::npos) {
+        if (!clangTidyExportFixedDir.empty()) {
+          this->GlobalCommonGenerator->AddClangTidyExportFixesFile(fixesFile);
+          cmSystemTools::MakeDirectory(
+            cmSystemTools::GetFilenamePath(fixesFile));
+          fixesFile = this->GeneratorTarget->GetLocalGenerator()
+                        ->MaybeRelativeToCurBinDir(fixesFile);
+          exportFixes = cmStrCat(";--export-fixes=", fixesFile);
+        }
+        code_check +=
+          this->GeneratorTarget->GetLocalGenerator()->EscapeForShell(
+            cmStrCat(tidy, ";--extra-arg-before=--driver-mode=", driverMode,
+                     exportFixes));
+      } else if (generatorName.find("Ninja") != std::string::npos) {
+        if (!clangTidyExportFixedDir.empty()) {
+          this->GlobalCommonGenerator->AddClangTidyExportFixesFile(fixesFile);
+          cmSystemTools::MakeDirectory(
+            cmSystemTools::GetFilenamePath(fixesFile));
+          if (!pathConverter) {
+            fixesFile = pathConverter(fixesFile);
+          }
+          exportFixes = cmStrCat(";--export-fixes=", fixesFile);
+        }
+        code_check +=
+          this->GeneratorTarget->GetLocalGenerator()->EscapeForShell(
+            cmStrCat(tidy, ";--extra-arg-before=--driver-mode=", driverMode,
+                     exportFixes));
+      }
+    }
+    if (cmNonempty(cpplint)) {
+      code_check += " --cpplint=";
+      code_check +=
+        this->GeneratorTarget->GetLocalGenerator()->EscapeForShell(cpplint);
+    }
+    if (cmNonempty(cppcheck)) {
+      code_check += " --cppcheck=";
+      code_check +=
+        this->GeneratorTarget->GetLocalGenerator()->EscapeForShell(cppcheck);
+    }
+    if (cmNonempty(tidy) || (cmNonempty(cpplint)) || (cmNonempty(cppcheck))) {
+      code_check += " --source=";
+      code_check +=
+        this->GeneratorTarget->GetLocalGenerator()->ConvertToOutputFormat(
+          source.GetFullPath(), cmOutputConverter::SHELL);
+    }
+    code_check += " -- ";
+    return code_check;
+  }
+  return "";
+}
+
 std::string cmCommonTargetGenerator::GetLinkerLauncher(
   const std::string& config)
 {
   std::string lang = this->GeneratorTarget->GetLinkerLanguage(config);
-  cmValue launcherProp =
-    this->GeneratorTarget->GetProperty(lang + "_LINKER_LAUNCHER");
+  std::string propName = lang + "_LINKER_LAUNCHER";
+  cmValue launcherProp = this->GeneratorTarget->GetProperty(propName);
   if (cmNonempty(launcherProp)) {
+    cmGeneratorExpressionDAGChecker dagChecker(this->GeneratorTarget, propName,
+                                               nullptr, nullptr);
+    std::string evaluatedLinklauncher = cmGeneratorExpression::Evaluate(
+      *launcherProp, this->LocalCommonGenerator, config, this->GeneratorTarget,
+      &dagChecker, this->GeneratorTarget, lang);
     // Convert ;-delimited list to single string
-    std::vector<std::string> args = cmExpandedList(*launcherProp, true);
+    cmList args{ evaluatedLinklauncher, cmList::EmptyElements::Yes };
     if (!args.empty()) {
       args[0] = this->LocalCommonGenerator->ConvertToOutputFormat(
         args[0], cmOutputConverter::SHELL);
diff --git a/Source/cmCommonTargetGenerator.h b/Source/cmCommonTargetGenerator.h
index 2d23037..c9886d0 100644
--- a/Source/cmCommonTargetGenerator.h
+++ b/Source/cmCommonTargetGenerator.h
@@ -4,6 +4,7 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
+#include <functional>
 #include <map>
 #include <set>
 #include <string>
@@ -53,6 +54,9 @@
 
   virtual void AddIncludeFlags(std::string& flags, std::string const& lang,
                                const std::string& config) = 0;
+  virtual std::string GetClangTidyReplacementsFilePath(
+    std::string const& directory, cmSourceFile const& source,
+    std::string const& config) const = 0;
 
   void AppendOSXVerFlag(std::string& flags, const std::string& lang,
                         const char* name, bool so);
@@ -63,7 +67,13 @@
   std::string GetIncludes(std::string const& l, const std::string& config);
   std::string GetManifests(const std::string& config);
   std::string GetAIXExports(std::string const& config);
+  std::string GenerateCodeCheckRules(
+    cmSourceFile const& source, std::string& compilerLauncher,
+    std::string const& cmakeCmd, std::string const& config,
+    std::function<std::string(std::string const&)> const& pathConverter);
 
+  std::string GetCompilerLauncher(std::string const& lang,
+                                  std::string const& config);
   std::vector<std::string> GetLinkedTargetDirectories(
     const std::string& lang, const std::string& config) const;
   std::string ComputeTargetCompilePDB(const std::string& config) const;
diff --git a/Source/cmComputeLinkDepends.cxx b/Source/cmComputeLinkDepends.cxx
index 5f408d0..f51a1c8 100644
--- a/Source/cmComputeLinkDepends.cxx
+++ b/Source/cmComputeLinkDepends.cxx
@@ -20,6 +20,7 @@
 #include "cmGeneratorExpressionDAGChecker.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
+#include "cmList.h"
 #include "cmListFileCache.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
@@ -627,7 +628,7 @@
   // This is called to add the dependencies named by
   // <item>_LIB_DEPENDS.  The variable contains a semicolon-separated
   // list.  The list contains link-type;item pairs and just items.
-  std::vector<std::string> deplist = cmExpandedList(value);
+  cmList deplist{ value };
 
   // Look for entries meant for this configuration.
   std::vector<cmLinkItem> actual_libs;
diff --git a/Source/cmComputeLinkInformation.cxx b/Source/cmComputeLinkInformation.cxx
index ad8fb8b..5d44a6a 100644
--- a/Source/cmComputeLinkInformation.cxx
+++ b/Source/cmComputeLinkInformation.cxx
@@ -15,6 +15,7 @@
 #include "cmComputeLinkDepends.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
+#include "cmList.h"
 #include "cmListFileCache.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
@@ -289,28 +290,28 @@
 
   // Get options needed to link libraries.
   if (cmValue flag = this->Makefile->GetDefinition(
-        "CMAKE_" + this->LinkLanguage + "_LINK_LIBRARY_FLAG")) {
+        cmStrCat("CMAKE_", this->LinkLanguage, "_LINK_LIBRARY_FLAG"))) {
     this->LibLinkFlag = *flag;
   } else {
     this->LibLinkFlag =
       this->Makefile->GetSafeDefinition("CMAKE_LINK_LIBRARY_FLAG");
   }
   if (cmValue flag = this->Makefile->GetDefinition(
-        "CMAKE_" + this->LinkLanguage + "_LINK_LIBRARY_FILE_FLAG")) {
+        cmStrCat("CMAKE_", this->LinkLanguage, "_LINK_LIBRARY_FILE_FLAG"))) {
     this->LibLinkFileFlag = *flag;
   } else {
     this->LibLinkFileFlag =
       this->Makefile->GetSafeDefinition("CMAKE_LINK_LIBRARY_FILE_FLAG");
   }
   if (cmValue suffix = this->Makefile->GetDefinition(
-        "CMAKE_" + this->LinkLanguage + "_LINK_LIBRARY_SUFFIX")) {
+        cmStrCat("CMAKE_", this->LinkLanguage, "_LINK_LIBRARY_SUFFIX"))) {
     this->LibLinkSuffix = *suffix;
   } else {
     this->LibLinkSuffix =
       this->Makefile->GetSafeDefinition("CMAKE_LINK_LIBRARY_SUFFIX");
   }
   if (cmValue flag = this->Makefile->GetDefinition(
-        "CMAKE_" + this->LinkLanguage + "_LINK_OBJECT_FILE_FLAG")) {
+        cmStrCat("CMAKE_", this->LinkLanguage, "_LINK_OBJECT_FILE_FLAG"))) {
     this->ObjLinkFileFlag = *flag;
   } else {
     this->ObjLinkFileFlag =
@@ -325,7 +326,7 @@
                            : "SHARED_LIBRARY");
     std::string rtVar =
       cmStrCat("CMAKE_", tType, "_RUNTIME_", this->LinkLanguage, "_FLAG");
-    std::string rtSepVar = rtVar + "_SEP";
+    std::string rtSepVar = cmStrCat(rtVar, "_SEP");
     this->RuntimeFlag = this->Makefile->GetSafeDefinition(rtVar);
     this->RuntimeSep = this->Makefile->GetSafeDefinition(rtSepVar);
     this->RuntimeAlways = (this->Makefile->GetSafeDefinition(
@@ -858,8 +859,8 @@
     return false;
   }
 
-  auto items = cmExpandListWithBacktrace(*langFeature,
-                                         this->Target->GetBacktrace(), true);
+  auto items = cmExpandListWithBacktrace(
+    *langFeature, this->Target->GetBacktrace(), cmList::EmptyElements::Yes);
 
   if ((items.size() == 1 && !IsValidFeatureFormat(items.front().Value)) ||
       (items.size() == 3 && !IsValidFeatureFormat(items[1].Value))) {
@@ -1016,8 +1017,8 @@
       .first->second;
   }
 
-  auto items = cmExpandListWithBacktrace(*langFeature,
-                                         this->Target->GetBacktrace(), true);
+  auto items = cmExpandListWithBacktrace(
+    *langFeature, this->Target->GetBacktrace(), cmList::EmptyElements::Yes);
 
   // replace LINKER: pattern
   this->Target->ResolveLinkerWrapper(items, this->LinkLanguage, true);
@@ -1070,10 +1071,10 @@
   if (runtimeLibrary.empty()) {
     return;
   }
-  if (cmValue runtimeLinkOptions = this->Makefile->GetDefinition(
-        "CMAKE_" + lang + "_RUNTIME_LIBRARY_LINK_OPTIONS_" + runtimeLibrary)) {
-    std::vector<std::string> libsVec = cmExpandedList(*runtimeLinkOptions);
-    for (std::string const& i : libsVec) {
+  if (cmValue runtimeLinkOptions = this->Makefile->GetDefinition(cmStrCat(
+        "CMAKE_", lang, "_RUNTIME_LIBRARY_LINK_OPTIONS_", runtimeLibrary))) {
+    cmList libs{ *runtimeLinkOptions };
+    for (auto const& i : libs) {
       if (!cm::contains(this->ImplicitLinkLibs, i)) {
         this->AddItem({ i });
       }
@@ -1087,8 +1088,8 @@
   // linker language.
   std::string libVar = cmStrCat("CMAKE_", lang, "_IMPLICIT_LINK_LIBRARIES");
   if (cmValue libs = this->Makefile->GetDefinition(libVar)) {
-    std::vector<std::string> libsVec = cmExpandedList(*libs);
-    for (std::string const& i : libsVec) {
+    cmList libsList{ *libs };
+    for (auto const& i : libsList) {
       if (!cm::contains(this->ImplicitLinkLibs, i)) {
         this->AddItem({ i });
       }
@@ -1099,8 +1100,8 @@
   // implied by the linker language.
   std::string dirVar = cmStrCat("CMAKE_", lang, "_IMPLICIT_LINK_DIRECTORIES");
   if (cmValue dirs = this->Makefile->GetDefinition(dirVar)) {
-    std::vector<std::string> dirsVec = cmExpandedList(*dirs);
-    this->OrderLinkerSearchPath->AddLanguageDirectories(dirsVec);
+    cmList dirsList{ *dirs };
+    this->OrderLinkerSearchPath->AddLanguageDirectories(dirsList);
   }
 }
 
@@ -1157,7 +1158,7 @@
       // Pass the full path to the target file.
       BT<std::string> lib = BT<std::string>(
         tgt->GetFullPath(config, artifact, true), item.Backtrace);
-      if (tgt->Target->IsAIX() && cmHasLiteralSuffix(lib.Value, "-NOTFOUND") &&
+      if (tgt->IsAIX() && cmHasLiteralSuffix(lib.Value, "-NOTFOUND") &&
           artifact == cmStateEnums::ImportLibraryArtifact) {
         // This is an imported executable on AIX that has ENABLE_EXPORTS
         // but not IMPORTED_IMPLIB.  CMake used to produce and accept such
@@ -1185,7 +1186,7 @@
     if (cmHasSuffix(entry.Feature, "FRAMEWORK"_s) ||
         (entry.Feature == DEFAULT &&
          cmSystemTools::IsPathToFramework(item.Value) &&
-         this->Makefile->IsOn("APPLE"))) {
+         this->Target->IsApple())) {
       // This is a framework.
       this->AddFrameworkItem(entry);
     } else if (cmSystemTools::FileIsFullPath(item.Value)) {
@@ -1370,15 +1371,15 @@
                          LinkUnknown);
   if (cmValue linkSuffixes =
         mf->GetDefinition("CMAKE_EXTRA_LINK_EXTENSIONS")) {
-    std::vector<std::string> linkSuffixVec = cmExpandedList(*linkSuffixes);
-    for (std::string const& i : linkSuffixVec) {
+    cmList linkSuffixList{ *linkSuffixes };
+    for (auto const& i : linkSuffixList) {
       this->AddLinkExtension(i, LinkUnknown);
     }
   }
   if (cmValue sharedSuffixes =
         mf->GetDefinition("CMAKE_EXTRA_SHARED_LIBRARY_SUFFIXES")) {
-    std::vector<std::string> sharedSuffixVec = cmExpandedList(*sharedSuffixes);
-    for (std::string const& i : sharedSuffixVec) {
+    cmList sharedSuffixList{ *sharedSuffixes };
+    for (std::string const& i : sharedSuffixList) {
       this->AddLinkExtension(i, LinkShared);
     }
   }
@@ -1399,10 +1400,9 @@
   reg = "^(";
   for (std::string const& p : this->LinkPrefixes) {
     reg += p;
-    reg += "|";
+    reg += '|';
   }
-  reg += ")";
-  reg += "([^/:]*)";
+  reg += ")([^/:]*)";
 
   // Create a regex to match any library name.
   std::string reg_any = cmStrCat(reg, libext);
@@ -1479,14 +1479,14 @@
   }
 
   // Finish the list.
-  libext += ")";
+  libext += ')';
 
   // Add an optional OpenBSD-style version or major.minor.version component.
   if (this->OpenBSD || type == LinkShared) {
     libext += "(\\.[0-9]+)*";
   }
 
-  libext += "$";
+  libext += '$';
   return libext;
 }
 
@@ -1564,7 +1564,7 @@
     this->OldLinkDirItems.push_back(item.Value);
   }
 
-  if (target->IsFrameworkOnApple() && !this->GlobalGenerator->IsXcode()) {
+  if (target->IsFrameworkOnApple()) {
     // Add the framework directory and the framework item itself
     auto fwDescriptor = this->GlobalGenerator->SplitFrameworkPath(
       item.Value, cmGlobalGenerator::FrameworkFormat::Extended);
@@ -1580,26 +1580,32 @@
       // Add the directory portion to the framework search path.
       this->AddFrameworkPath(fwDescriptor->Directory);
     }
-    if (cmHasSuffix(entry.Feature, "FRAMEWORK"_s)) {
-      this->Items.emplace_back(fwDescriptor->GetLinkName(), ItemIsPath::Yes,
-                               target,
-                               this->FindLibraryFeature(entry.Feature));
-    } else {
+
+    if (this->GlobalGenerator->IsXcode()) {
       this->Items.emplace_back(
         item, ItemIsPath::Yes, target,
-        this->FindLibraryFeature(
-          entry.Feature == DEFAULT ? "__CMAKE_LINK_LIBRARY" : entry.Feature));
+        this->FindLibraryFeature(entry.Feature == DEFAULT
+                                   ? "__CMAKE_LINK_FRAMEWORK"
+                                   : entry.Feature));
+    } else {
+      if (cmHasSuffix(entry.Feature, "FRAMEWORK"_s)) {
+        this->Items.emplace_back(fwDescriptor->GetLinkName(), ItemIsPath::Yes,
+                                 target,
+                                 this->FindLibraryFeature(entry.Feature));
+      } else {
+        this->Items.emplace_back(
+          item, ItemIsPath::Yes, target,
+          this->FindLibraryFeature(entry.Feature == DEFAULT
+                                     ? "__CMAKE_LINK_LIBRARY"
+                                     : entry.Feature));
+      }
     }
   } else {
     // Now add the full path to the library.
     this->Items.emplace_back(
       item, ItemIsPath::Yes, target,
       this->FindLibraryFeature(
-        entry.Feature == DEFAULT
-          ? (target->IsFrameworkOnApple() && this->GlobalGenerator->IsXcode()
-               ? "__CMAKE_LINK_FRAMEWORK"
-               : "__CMAKE_LINK_LIBRARY")
-          : entry.Feature));
+        entry.Feature == DEFAULT ? "__CMAKE_LINK_LIBRARY" : entry.Feature));
   }
 }
 
@@ -1697,7 +1703,8 @@
     case cmPolicies::WARN:
       if (this->CMP0060Warn) {
         // Print the warning at most once for this item.
-        std::string const& wid = "CMP0060-WARNING-GIVEN-" + item.Value;
+        std::string const& wid =
+          cmStrCat("CMP0060-WARNING-GIVEN-", item.Value);
         if (!this->CMakeInstance->GetPropertyAsBool(wid)) {
           this->CMakeInstance->SetProperty(wid, "1");
           this->CMP0060WarnItems.insert(item.Value);
@@ -1859,8 +1866,8 @@
                              : cmGlobalGenerator::FrameworkFormat::Extended);
   if (!fwDescriptor) {
     std::ostringstream e;
-    e << "Could not parse framework path \"" << item << "\" "
-      << "linked by target " << this->Target->GetName() << ".";
+    e << "Could not parse framework path \"" << item << "\" linked by target "
+      << this->Target->GetName() << '.';
     cmSystemTools::Error(e.str());
     return;
   }
@@ -1910,19 +1917,18 @@
 void cmComputeLinkInformation::ComputeFrameworkInfo()
 {
   // Avoid adding implicit framework paths.
-  std::vector<std::string> implicitDirVec;
+  cmList implicitDirs;
 
   // Get platform-wide implicit directories.
-  this->Makefile->GetDefExpandList(
-    "CMAKE_PLATFORM_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES", implicitDirVec);
+  implicitDirs.assign(this->Makefile->GetDefinition(
+    "CMAKE_PLATFORM_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES"));
 
   // Get language-specific implicit directories.
   std::string implicitDirVar = cmStrCat(
     "CMAKE_", this->LinkLanguage, "_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES");
-  this->Makefile->GetDefExpandList(implicitDirVar, implicitDirVec);
+  implicitDirs.append(this->Makefile->GetDefinition(implicitDirVar));
 
-  this->FrameworkPathsEmitted.insert(implicitDirVec.begin(),
-                                     implicitDirVec.end());
+  this->FrameworkPathsEmitted.insert(implicitDirs.begin(), implicitDirs.end());
 }
 
 void cmComputeLinkInformation::AddFrameworkPath(std::string const& p)
@@ -1994,9 +2000,9 @@
         std::ostringstream w;
         /* clang-format off */
         w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0008) << "\n"
-          << "Target \"" << this->Target->GetName() << "\" links to item\n"
-          << "  " << item << "\n"
-          << "which is a full-path but not a valid library file name.";
+             "Target \"" << this->Target->GetName() << "\" links to item\n"
+             "  " << item << "\n"
+             "which is a full-path but not a valid library file name.";
         /* clang-format on */
         this->CMakeInstance->IssueMessage(MessageType::AUTHOR_WARNING, w.str(),
                                           this->Target->GetBacktrace());
@@ -2014,9 +2020,9 @@
       std::ostringstream e;
       /* clang-format off */
       e << cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0008) << "\n"
-          << "Target \"" << this->Target->GetName() << "\" links to item\n"
-          << "  " << item << "\n"
-          << "which is a full-path but not a valid library file name.";
+             "Target \"" << this->Target->GetName() << "\" links to item\n"
+             "  " << item << "\n"
+             "which is a full-path but not a valid library file name.";
       /* clang-format on */
       this->CMakeInstance->IssueMessage(MessageType::FATAL_ERROR, e.str(),
                                         this->Target->GetBacktrace());
@@ -2055,7 +2061,7 @@
     case cmPolicies::REQUIRED_IF_USED:
     case cmPolicies::REQUIRED_ALWAYS: {
       std::ostringstream e;
-      e << cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0003) << "\n";
+      e << cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0003) << '\n';
       this->PrintLinkPolicyDiagnosis(e);
       this->CMakeInstance->IssueMessage(MessageType::FATAL_ERROR, e.str(),
                                         this->Target->GetBacktrace());
@@ -2075,18 +2081,17 @@
   // Tell the user what to do.
   /* clang-format off */
   os << "Policy CMP0003 should be set before this line.  "
-     << "Add code such as\n"
-     << "  if(COMMAND cmake_policy)\n"
-     << "    cmake_policy(SET CMP0003 NEW)\n"
-     << "  endif(COMMAND cmake_policy)\n"
-     << "as early as possible but after the most recent call to "
-     << "cmake_minimum_required or cmake_policy(VERSION).  ";
+        "Add code such as\n"
+        "  if(COMMAND cmake_policy)\n"
+        "    cmake_policy(SET CMP0003 NEW)\n"
+        "  endif(COMMAND cmake_policy)\n"
+        "as early as possible but after the most recent call to "
+        "cmake_minimum_required or cmake_policy(VERSION).  ";
   /* clang-format on */
 
   // List the items that might need the old-style paths.
   os << "This warning appears because target \"" << this->Target->GetName()
-     << "\" "
-     << "links to some libraries for which the linker must search:\n";
+     << "\" links to some libraries for which the linker must search:\n";
   {
     // Format the list of unknown items to be as short as possible while
     // still fitting in the allowed width (a true solution would be the
@@ -2099,7 +2104,7 @@
       // output the current line and reset it.  Note that the separator
       // is either " " or ", " which is always 2 characters.
       if (!line.empty() && (line.size() + i.size() + 2) > max_size) {
-        os << line << "\n";
+        os << line << '\n';
         sep = "  ";
         line.clear();
       }
@@ -2109,7 +2114,7 @@
       sep = ", ";
     }
     if (!line.empty()) {
-      os << line << "\n";
+      os << line << '\n';
     }
   }
 
@@ -2118,52 +2123,49 @@
   std::set<std::string> emitted;
   for (std::string const& i : this->OldLinkDirItems) {
     if (emitted.insert(cmSystemTools::GetFilenamePath(i)).second) {
-      os << "  " << i << "\n";
+      os << "  " << i << '\n';
     }
   }
 
   // Explain.
   os << "CMake is adding directories in the second list to the linker "
-     << "search path in case they are needed to find libraries from the "
-     << "first list (for backwards compatibility with CMake 2.4).  "
-     << "Set policy CMP0003 to OLD or NEW to enable or disable this "
-     << "behavior explicitly.  "
-     << "Run \"cmake --help-policy CMP0003\" for more information.";
+        "search path in case they are needed to find libraries from the "
+        "first list (for backwards compatibility with CMake 2.4).  "
+        "Set policy CMP0003 to OLD or NEW to enable or disable this "
+        "behavior explicitly.  "
+        "Run \"cmake --help-policy CMP0003\" for more information.";
 }
 
 void cmComputeLinkInformation::LoadImplicitLinkInfo()
 {
-  std::vector<std::string> implicitDirVec;
-
   // Get platform-wide implicit directories.
-  this->Makefile->GetDefExpandList("CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES",
-                                   implicitDirVec);
+  cmList implicitDirs{ this->Makefile->GetDefinition(
+    "CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES") };
 
   // Append library architecture to all implicit platform directories
   // and add them to the set
   if (cmValue libraryArch =
         this->Makefile->GetDefinition("CMAKE_LIBRARY_ARCHITECTURE")) {
-    for (std::string const& i : implicitDirVec) {
-      this->ImplicitLinkDirs.insert(i + "/" + *libraryArch);
+    for (auto const& i : implicitDirs) {
+      this->ImplicitLinkDirs.insert(cmStrCat(i, '/', *libraryArch));
     }
   }
 
   // Get language-specific implicit directories.
   std::string implicitDirVar =
     cmStrCat("CMAKE_", this->LinkLanguage, "_IMPLICIT_LINK_DIRECTORIES");
-  this->Makefile->GetDefExpandList(implicitDirVar, implicitDirVec);
+  implicitDirs.append(this->Makefile->GetDefinition(implicitDirVar));
 
   // Store implicit link directories.
-  this->ImplicitLinkDirs.insert(implicitDirVec.begin(), implicitDirVec.end());
+  this->ImplicitLinkDirs.insert(implicitDirs.begin(), implicitDirs.end());
 
   // Get language-specific implicit libraries.
-  std::vector<std::string> implicitLibVec;
   std::string implicitLibVar =
     cmStrCat("CMAKE_", this->LinkLanguage, "_IMPLICIT_LINK_LIBRARIES");
-  this->Makefile->GetDefExpandList(implicitLibVar, implicitLibVec);
+  cmList implicitLibs{ this->Makefile->GetDefinition(implicitLibVar) };
 
   // Store implicit link libraries.
-  for (std::string const& item : implicitLibVec) {
+  for (auto const& item : implicitLibs) {
     // Items starting in '-' but not '-l' are flags, not libraries,
     // and should not be filtered by this implicit list.
     if (item[0] != '-' || item[1] == 'l') {
@@ -2172,8 +2174,8 @@
   }
 
   // Get platform specific rpath link directories
-  this->Makefile->GetDefExpandList("CMAKE_PLATFORM_RUNTIME_PATH",
-                                   this->RuntimeLinkDirs);
+  cmList::append(this->RuntimeLinkDirs,
+                 this->Makefile->GetDefinition("CMAKE_PLATFORM_RUNTIME_PATH"));
 }
 
 std::vector<std::string> const&
@@ -2274,7 +2276,7 @@
                                    std::vector<std::string>& out,
                                    std::set<std::string>& emitted)
 {
-  std::vector<std::string> tmp = cmExpandedList(str);
+  cmList tmp{ str };
   for (std::string const& i : tmp) {
     if (emitted.insert(i).second) {
       out.push_back(i);
@@ -2400,10 +2402,11 @@
     cmGeneratorTarget::LinkClosure const* lc =
       this->Target->GetLinkClosure(this->Config);
     for (std::string const& li : lc->Languages) {
-      std::string useVar =
-        "CMAKE_" + li + "_USE_IMPLICIT_LINK_DIRECTORIES_IN_RUNTIME_PATH";
+      std::string useVar = cmStrCat(
+        "CMAKE_", li, "_USE_IMPLICIT_LINK_DIRECTORIES_IN_RUNTIME_PATH");
       if (this->Makefile->IsOn(useVar)) {
-        std::string dirVar = "CMAKE_" + li + "_IMPLICIT_LINK_DIRECTORIES";
+        std::string dirVar =
+          cmStrCat("CMAKE_", li, "_IMPLICIT_LINK_DIRECTORIES");
         if (cmValue dirs = this->Makefile->GetDefinition(dirVar)) {
           cmCLI_ExpandListUnique(*dirs, runtimeDirs, emitted);
         }
diff --git a/Source/cmConditionEvaluator.cxx b/Source/cmConditionEvaluator.cxx
index 5de012a..6f9f541 100644
--- a/Source/cmConditionEvaluator.cxx
+++ b/Source/cmConditionEvaluator.cxx
@@ -18,6 +18,7 @@
 
 #include "cmCMakePath.h"
 #include "cmExpandedCommandArgument.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
 #include "cmState.h"
@@ -740,8 +741,8 @@
                                 keyVERSION_LESS_EQUAL, keyVERSION_GREATER,
                                 keyVERSION_GREATER_EQUAL, keyVERSION_EQUAL))) {
       const auto op = MATCH2CMPOP[matchNo - 1];
-      const std::string& lhs = this->GetVariableOrString(*args.current);
-      const std::string& rhs = this->GetVariableOrString(*args.nextnext);
+      const cmValue lhs = this->GetVariableOrString(*args.current);
+      const cmValue rhs = this->GetVariableOrString(*args.nextnext);
       const auto result = cmSystemTools::VersionCompare(op, lhs, rhs);
       newArgs.ReduceTwoArgs(result, args);
     }
@@ -764,7 +765,9 @@
         cmValue rhs = this->Makefile.GetDefinition(args.nextnext->GetValue());
 
         newArgs.ReduceTwoArgs(
-          rhs && cm::contains(cmExpandedList(*rhs, true), *lhs), args);
+          rhs &&
+            cm::contains(cmList{ *rhs, cmList::EmptyElements::Yes }, *lhs),
+          args);
       }
 
       else if (this->Policy57Status == cmPolicies::WARN) {
diff --git a/Source/cmConfigure.cmake.h.in b/Source/cmConfigure.cmake.h.in
index 90f3de0..de74716 100644
--- a/Source/cmConfigure.cmake.h.in
+++ b/Source/cmConfigure.cmake.h.in
@@ -20,10 +20,11 @@
 
 #cmakedefine HAVE_ENVIRON_NOT_REQUIRE_PROTOTYPE
 #cmakedefine HAVE_UNSETENV
+#cmakedefine CMake_ENABLE_DEBUGGER
 #cmakedefine CMake_USE_MACH_PARSER
 #cmakedefine CMake_USE_XCOFF_PARSER
 #cmakedefine CMAKE_USE_WMAKE
-#define CMake_DEFAULT_RECURSION_LIMIT @CMake_DEFAULT_RECURSION_LIMIT@
+#cmakedefine CMake_DEFAULT_RECURSION_LIMIT @CMake_DEFAULT_RECURSION_LIMIT@
 #define CMAKE_BIN_DIR "/@CMAKE_BIN_DIR@"
 #define CMAKE_DATA_DIR "/@CMAKE_DATA_DIR@"
 #define CMAKE_DOC_DIR "/@CMAKE_DOC_DIR@"
diff --git a/Source/cmCoreTryCompile.cxx b/Source/cmCoreTryCompile.cxx
index acf1c20..7d4ab50 100644
--- a/Source/cmCoreTryCompile.cxx
+++ b/Source/cmCoreTryCompile.cxx
@@ -20,6 +20,7 @@
 #include "cmConfigureLog.h"
 #include "cmExportTryCompileFileGenerator.h"
 #include "cmGlobalGenerator.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
 #include "cmOutputConverter.h"
@@ -72,6 +73,10 @@
 std::string const kCMAKE_CUDA_ARCHITECTURES = "CMAKE_CUDA_ARCHITECTURES";
 std::string const kCMAKE_CUDA_RUNTIME_LIBRARY = "CMAKE_CUDA_RUNTIME_LIBRARY";
 std::string const kCMAKE_ENABLE_EXPORTS = "CMAKE_ENABLE_EXPORTS";
+std::string const kCMAKE_EXECUTABLE_ENABLE_EXPORTS =
+  "CMAKE_EXECUTABLE_ENABLE_EXPORTS";
+std::string const kCMAKE_SHARED_LIBRARY_ENABLE_EXPORTS =
+  "CMAKE_SHARED_LIBRARY_ENABLE_EXPORTS";
 std::string const kCMAKE_HIP_ARCHITECTURES = "CMAKE_HIP_ARCHITECTURES";
 std::string const kCMAKE_HIP_RUNTIME_LIBRARY = "CMAKE_HIP_RUNTIME_LIBRARY";
 std::string const kCMAKE_ISPC_INSTRUCTION_SETS = "CMAKE_ISPC_INSTRUCTION_SETS";
@@ -121,7 +126,7 @@
 ArgumentParser::Continue TryCompileCompileDefs(Arguments& args,
                                                cm::string_view val)
 {
-  cmExpandList(val, args.CompileDefs);
+  args.CompileDefs.append(val);
   return ArgumentParser::Continue::Yes;
 }
 
@@ -784,7 +789,7 @@
     if (!arguments.CompileDefs.empty()) {
       // Pass using bracket arguments to preserve content.
       fprintf(fout, "add_definitions([==[%s]==])\n",
-              cmJoin(arguments.CompileDefs, "]==] [==[").c_str());
+              arguments.CompileDefs.join("]==] [==[").c_str());
     }
 
     if (!targets.empty()) {
@@ -997,6 +1002,8 @@
     vars.insert(kCMAKE_CUDA_ARCHITECTURES);
     vars.insert(kCMAKE_CUDA_RUNTIME_LIBRARY);
     vars.insert(kCMAKE_ENABLE_EXPORTS);
+    vars.insert(kCMAKE_EXECUTABLE_ENABLE_EXPORTS);
+    vars.insert(kCMAKE_SHARED_LIBRARY_ENABLE_EXPORTS);
     vars.insert(kCMAKE_HIP_ARCHITECTURES);
     vars.insert(kCMAKE_HIP_RUNTIME_LIBRARY);
     vars.insert(kCMAKE_ISPC_INSTRUCTION_SETS);
@@ -1018,7 +1025,7 @@
 
     if (cmValue varListStr = this->Makefile->GetDefinition(
           kCMAKE_TRY_COMPILE_PLATFORM_VARIABLES)) {
-      std::vector<std::string> varList = cmExpandedList(*varListStr);
+      cmList varList{ *varListStr };
       vars.insert(varList.begin(), varList.end());
     }
 
diff --git a/Source/cmCoreTryCompile.h b/Source/cmCoreTryCompile.h
index ba38c19..c185c68 100644
--- a/Source/cmCoreTryCompile.h
+++ b/Source/cmCoreTryCompile.h
@@ -12,6 +12,7 @@
 
 #include "cmArgumentParser.h"
 #include "cmArgumentParserTypes.h"
+#include "cmList.h"
 #include "cmStateTypes.h"
 
 class cmConfigureLog;
@@ -65,7 +66,7 @@
     ArgumentParser::MaybeEmpty<std::vector<std::string>> CMakeFlags{
       1, "CMAKE_FLAGS"
     }; // fake argv[0]
-    std::vector<std::string> CompileDefs;
+    cmList CompileDefs;
     cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>>
       LinkLibraries;
     ArgumentParser::MaybeEmpty<std::vector<std::string>> LinkOptions;
diff --git a/Source/cmCurl.cxx b/Source/cmCurl.cxx
index fd6aee1..24ba368 100644
--- a/Source/cmCurl.cxx
+++ b/Source/cmCurl.cxx
@@ -131,12 +131,12 @@
   // Convert string from UTF-8 to ACP if this is a file:// URL.
   std::wstring wurl = cmsys::Encoding::ToWide(url);
   if (!wurl.empty()) {
-    int mblen =
-      WideCharToMultiByte(CP_ACP, 0, wurl.c_str(), -1, NULL, 0, NULL, NULL);
+    int mblen = WideCharToMultiByte(CP_ACP, 0, wurl.c_str(), -1, nullptr, 0,
+                                    nullptr, nullptr);
     if (mblen > 0) {
       std::vector<char> chars(mblen);
       mblen = WideCharToMultiByte(CP_ACP, 0, wurl.c_str(), -1, &chars[0],
-                                  mblen, NULL, NULL);
+                                  mblen, nullptr, nullptr);
       if (mblen > 0) {
         url = &chars[0];
       }
diff --git a/Source/cmCustomCommand.cxx b/Source/cmCustomCommand.cxx
index 68c65bb..e12cf70 100644
--- a/Source/cmCustomCommand.cxx
+++ b/Source/cmCustomCommand.cxx
@@ -7,6 +7,8 @@
 
 #include <cmext/algorithm>
 
+#include "cmStateSnapshot.h"
+
 const std::vector<std::string>& cmCustomCommand::GetOutputs() const
 {
   return this->Outputs;
@@ -162,6 +164,16 @@
   this->CommandExpandLists = b;
 }
 
+bool cmCustomCommand::GetDependsExplicitOnly() const
+{
+  return this->DependsExplicitOnly;
+}
+
+void cmCustomCommand::SetDependsExplicitOnly(bool b)
+{
+  this->DependsExplicitOnly = b;
+}
+
 const std::string& cmCustomCommand::GetDepfile() const
 {
   return this->Depfile;
@@ -182,14 +194,19 @@
   this->JobPool = job_pool;
 }
 
-cmPolicies::PolicyStatus cmCustomCommand::GetCMP0116Status() const
-{
-  return this->CMP0116Status;
-}
+#define DEFINE_CC_POLICY_ACCESSOR(P)                                          \
+  cmPolicies::PolicyStatus cmCustomCommand::Get##P##Status() const            \
+  {                                                                           \
+    return this->P##Status;                                                   \
+  }
+CM_FOR_EACH_CUSTOM_COMMAND_POLICY(DEFINE_CC_POLICY_ACCESSOR)
+#undef DEFINE_CC_POLICY_ACCESSOR
 
-void cmCustomCommand::SetCMP0116Status(cmPolicies::PolicyStatus cmp0116)
+void cmCustomCommand::RecordPolicyValues(const cmStateSnapshot& snapshot)
 {
-  this->CMP0116Status = cmp0116;
+#define SET_CC_POLICY(P) this->P##Status = snapshot.GetPolicy(cmPolicies::P);
+  CM_FOR_EACH_CUSTOM_COMMAND_POLICY(SET_CC_POLICY)
+#undef SET_CC_POLICY
 }
 
 const std::string& cmCustomCommand::GetTarget() const
diff --git a/Source/cmCustomCommand.h b/Source/cmCustomCommand.h
index 5533847..1e68dbf 100644
--- a/Source/cmCustomCommand.h
+++ b/Source/cmCustomCommand.h
@@ -17,6 +17,8 @@
 {
 };
 
+class cmStateSnapshot;
+
 /** \class cmCustomCommand
  * \brief A class to encapsulate a custom command
  *
@@ -100,6 +102,11 @@
   bool GetCommandExpandLists() const;
   void SetCommandExpandLists(bool b);
 
+  /** Set/Get whether to use additional dependencies coming from
+      users of OUTPUT of the custom command. */
+  bool GetDependsExplicitOnly() const;
+  void SetDependsExplicitOnly(bool b);
+
   /** Set/Get the depfile (used by the Ninja generator) */
   const std::string& GetDepfile() const;
   void SetDepfile(const std::string& depfile);
@@ -108,9 +115,13 @@
   const std::string& GetJobPool() const;
   void SetJobPool(const std::string& job_pool);
 
-  /** Set/Get the CMP0116 status (used by the Ninja generator) */
-  cmPolicies::PolicyStatus GetCMP0116Status() const;
-  void SetCMP0116Status(cmPolicies::PolicyStatus cmp0116);
+#define DECLARE_CC_POLICY_ACCESSOR(P)                                         \
+  cmPolicies::PolicyStatus Get##P##Status() const;
+  CM_FOR_EACH_CUSTOM_COMMAND_POLICY(DECLARE_CC_POLICY_ACCESSOR)
+#undef DECLARE_CC_POLICY_ACCESSOR
+
+  /** Record policy values from state snapshot */
+  void RecordPolicyValues(const cmStateSnapshot& snapshot);
 
   /** Set/Get the associated target */
   const std::string& GetTarget() const;
@@ -135,5 +146,12 @@
   bool CommandExpandLists = false;
   bool StdPipesUTF8 = false;
   bool HasMainDependency_ = false;
-  cmPolicies::PolicyStatus CMP0116Status = cmPolicies::WARN;
+  bool DependsExplicitOnly = false;
+
+// Policies are NEW for synthesized custom commands, and set by cmMakefile for
+// user-created custom commands.
+#define DECLARE_CC_POLICY_FIELD(P)                                            \
+  cmPolicies::PolicyStatus P##Status = cmPolicies::NEW;
+  CM_FOR_EACH_CUSTOM_COMMAND_POLICY(DECLARE_CC_POLICY_FIELD)
+#undef DECLARE_CC_POLICY_FIELD
 };
diff --git a/Source/cmCustomCommandGenerator.cxx b/Source/cmCustomCommandGenerator.cxx
index 14c22e3..2c1480a 100644
--- a/Source/cmCustomCommandGenerator.cxx
+++ b/Source/cmCustomCommandGenerator.cxx
@@ -17,6 +17,7 @@
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
+#include "cmList.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
 #include "cmStateTypes.h"
@@ -116,7 +117,7 @@
                                /*outputConfig=*/outputConfig,
                                /*commandConfig=*/commandConfig,
                                /*target=*/nullptr);
-    cm::append(depends, cmExpandedList(ep));
+    cm::append(depends, cmList{ ep });
   }
   for (std::string& p : depends) {
     if (cmSystemTools::FileIsFullPath(p)) {
@@ -196,7 +197,7 @@
         clarg, ge, this->LG, useOutputConfig, this->OutputConfig,
         this->CommandConfig, target, &this->Utilities);
       if (this->CC->GetCommandExpandLists()) {
-        cm::append(argv, cmExpandedList(parsed_arg));
+        cm::append(argv, cmList{ parsed_arg });
       } else {
         argv.push_back(std::move(parsed_arg));
       }
@@ -331,9 +332,9 @@
 
 bool cmCustomCommandGenerator::HasOnlyEmptyCommandLines() const
 {
-  for (size_t i = 0; i < this->CommandLines.size(); ++i) {
-    for (size_t j = 0; j < this->CommandLines[i].size(); ++j) {
-      if (!this->CommandLines[i][j].empty()) {
+  for (cmCustomCommandLine const& ccl : this->CommandLines) {
+    for (std::string const& cl : ccl) {
+      if (!cl.empty()) {
         return false;
       }
     }
diff --git a/Source/cmCxxModuleMapper.cxx b/Source/cmCxxModuleMapper.cxx
index 59bf4c7..e836a2a 100644
--- a/Source/cmCxxModuleMapper.cxx
+++ b/Source/cmCxxModuleMapper.cxx
@@ -17,13 +17,59 @@
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 
-cm::optional<std::string> CxxModuleLocations::BmiGeneratorPathForModule(
+CxxBmiLocation::CxxBmiLocation() = default;
+
+CxxBmiLocation::CxxBmiLocation(std::string path)
+  : BmiLocation(std::move(path))
+{
+}
+
+CxxBmiLocation CxxBmiLocation::Unknown()
+{
+  return {};
+}
+
+CxxBmiLocation CxxBmiLocation::Private()
+{
+  return { std::string{} };
+}
+
+CxxBmiLocation CxxBmiLocation::Known(std::string path)
+{
+  return { std::move(path) };
+}
+
+bool CxxBmiLocation::IsKnown() const
+{
+  return this->BmiLocation.has_value();
+}
+
+bool CxxBmiLocation::IsPrivate() const
+{
+  if (auto const& loc = this->BmiLocation) {
+    return loc->empty();
+  }
+  return false;
+}
+
+std::string const& CxxBmiLocation::Location() const
+{
+  if (auto const& loc = this->BmiLocation) {
+    return *loc;
+  }
+  static std::string empty;
+  return empty;
+}
+
+CxxBmiLocation CxxModuleLocations::BmiGeneratorPathForModule(
   std::string const& logical_name) const
 {
-  if (auto l = this->BmiLocationForModule(logical_name)) {
-    return this->PathForGenerator(std::move(*l));
+  auto bmi_loc = this->BmiLocationForModule(logical_name);
+  if (bmi_loc.IsKnown() && !bmi_loc.IsPrivate()) {
+    bmi_loc =
+      CxxBmiLocation::Known(this->PathForGenerator(bmi_loc.Location()));
   }
-  return {};
+  return bmi_loc;
 }
 
 namespace {
@@ -42,18 +88,21 @@
   // A series of flags which tell the compiler where to look for modules.
 
   for (auto const& p : obj.Provides) {
-    if (auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName)) {
+    auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName);
+    if (bmi_loc.IsKnown()) {
       // Force the TU to be considered a C++ module source file regardless of
       // extension.
       mm << "-x c++-module\n";
 
-      mm << "-fmodule-output=" << *bmi_loc << '\n';
+      mm << "-fmodule-output=" << bmi_loc.Location() << '\n';
       break;
     }
   }
   for (auto const& r : obj.Requires) {
-    if (auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName)) {
-      mm << "-fmodule-file=" << r.LogicalName << "=" << *bmi_loc << '\n';
+    auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName);
+    if (bmi_loc.IsKnown()) {
+      mm << "-fmodule-file=" << r.LogicalName << "=" << bmi_loc.Location()
+         << '\n';
     }
   }
 
@@ -76,13 +125,15 @@
   mm << "$root " << loc.RootDirectory << "\n";
 
   for (auto const& p : obj.Provides) {
-    if (auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName)) {
-      mm << p.LogicalName << ' ' << *bmi_loc << '\n';
+    auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName);
+    if (bmi_loc.IsKnown()) {
+      mm << p.LogicalName << ' ' << bmi_loc.Location() << '\n';
     }
   }
   for (auto const& r : obj.Requires) {
-    if (auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName)) {
-      mm << r.LogicalName << ' ' << *bmi_loc << '\n';
+    auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName);
+    if (bmi_loc.IsKnown()) {
+      mm << r.LogicalName << ' ' << bmi_loc.Location() << '\n';
     }
   }
 
@@ -123,8 +174,9 @@
       mm << "-internalPartition\n";
     }
 
-    if (auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName)) {
-      mm << "-ifcOutput " << *bmi_loc << '\n';
+    auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName);
+    if (bmi_loc.IsKnown()) {
+      mm << "-ifcOutput " << bmi_loc.Location() << '\n';
     }
   }
 
@@ -132,10 +184,11 @@
   std::set<std::string> transitive_usage_names;
 
   for (auto const& r : obj.Requires) {
-    if (auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName)) {
+    auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName);
+    if (bmi_loc.IsKnown()) {
       auto flag = flag_for_method(r.Method);
 
-      mm << flag << ' ' << r.LogicalName << '=' << *bmi_loc << "\n";
+      mm << flag << ' ' << r.LogicalName << '=' << bmi_loc.Location() << "\n";
       transitive_usage_directs.insert(r.LogicalName);
 
       // Insert transitive usages.
@@ -237,18 +290,28 @@
   for (cmScanDepInfo const& object : objects) {
     // Add references for each of the provided modules.
     for (auto const& p : object.Provides) {
-      if (auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName)) {
+      auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName);
+      if (bmi_loc.IsKnown()) {
         // XXX(cxx-modules): How to support header units?
-        usages.AddReference(p.LogicalName, *bmi_loc, LookupMethod::ByName);
+        usages.AddReference(p.LogicalName, bmi_loc.Location(),
+                            LookupMethod::ByName);
       }
     }
 
     // For each requires, pull in what is required.
     for (auto const& r : object.Requires) {
-      // Find transitive usages.
-      auto transitive_usages = usages.Usage.find(r.LogicalName);
       // Find the required name in the current target.
       auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName);
+      if (bmi_loc.IsPrivate()) {
+        cmSystemTools::Error(
+          cmStrCat("Unable to use module '", r.LogicalName,
+                   "' as it is 'PRIVATE' and therefore not accessible outside "
+                   "of its owning target."));
+        continue;
+      }
+
+      // Find transitive usages.
+      auto transitive_usages = usages.Usage.find(r.LogicalName);
 
       for (auto const& p : object.Provides) {
         auto& this_usages = usages.Usage[p.LogicalName];
@@ -260,14 +323,14 @@
         if (transitive_usages != usages.Usage.end()) {
           this_usages.insert(transitive_usages->second.begin(),
                              transitive_usages->second.end());
-        } else if (bmi_loc) {
+        } else if (bmi_loc.IsKnown()) {
           // Mark that we need to update transitive usages later.
           internal_usages[p.LogicalName].insert(r.LogicalName);
         }
       }
 
-      if (bmi_loc) {
-        usages.AddReference(r.LogicalName, *bmi_loc, r.Method);
+      if (bmi_loc.IsKnown()) {
+        usages.AddReference(r.LogicalName, bmi_loc.Location(), r.Method);
       }
     }
   }
diff --git a/Source/cmCxxModuleMapper.h b/Source/cmCxxModuleMapper.h
index 0f453b0..ef01e48 100644
--- a/Source/cmCxxModuleMapper.h
+++ b/Source/cmCxxModuleMapper.h
@@ -22,6 +22,23 @@
   Msvc,
 };
 
+struct CxxBmiLocation
+{
+  static CxxBmiLocation Unknown();
+  static CxxBmiLocation Private();
+  static CxxBmiLocation Known(std::string path);
+
+  bool IsKnown() const;
+  bool IsPrivate() const;
+  std::string const& Location() const;
+
+private:
+  CxxBmiLocation();
+  CxxBmiLocation(std::string path);
+
+  cm::optional<std::string> BmiLocation;
+};
+
 struct CxxModuleLocations
 {
   // The path from which all relative paths should be computed. If
@@ -33,12 +50,11 @@
   std::function<std::string(std::string)> PathForGenerator;
 
   // Lookup the BMI location of a logical module name.
-  std::function<cm::optional<std::string>(std::string const&)>
-    BmiLocationForModule;
+  std::function<CxxBmiLocation(std::string const&)> BmiLocationForModule;
 
   // Returns the generator path (if known) for the BMI given a
   // logical module name.
-  cm::optional<std::string> BmiGeneratorPathForModule(
+  CxxBmiLocation BmiGeneratorPathForModule(
     std::string const& logical_name) const;
 };
 
diff --git a/Source/cmDebuggerAdapter.cxx b/Source/cmDebuggerAdapter.cxx
new file mode 100644
index 0000000..d03f79d
--- /dev/null
+++ b/Source/cmDebuggerAdapter.cxx
@@ -0,0 +1,462 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cmDebuggerAdapter.h"
+
+#include <algorithm>
+#include <climits>
+#include <condition_variable>
+#include <cstdint>
+#include <functional>
+#include <iostream>
+#include <stdexcept>
+#include <utility>
+
+#include <cm/memory>
+#include <cm/optional>
+
+#include <cm3p/cppdap/io.h> // IWYU pragma: keep
+#include <cm3p/cppdap/protocol.h>
+#include <cm3p/cppdap/session.h>
+
+#include "cmDebuggerBreakpointManager.h"
+#include "cmDebuggerExceptionManager.h"
+#include "cmDebuggerProtocol.h"
+#include "cmDebuggerSourceBreakpoint.h" // IWYU pragma: keep
+#include "cmDebuggerStackFrame.h"
+#include "cmDebuggerThread.h"
+#include "cmDebuggerThreadManager.h"
+#include "cmListFileCache.h"
+#include "cmMakefile.h"
+#include "cmValue.h"
+#include "cmVersionConfig.h"
+#include <cmcppdap/include/dap/optional.h>
+#include <cmcppdap/include/dap/types.h>
+
+namespace cmDebugger {
+
+// Event provides a basic wait and signal synchronization primitive.
+class SyncEvent
+{
+public:
+  // Wait() blocks until the event is fired.
+  void Wait()
+  {
+    std::unique_lock<std::mutex> lock(Mutex);
+    Cv.wait(lock, [&] { return Fired; });
+  }
+
+  // Fire() sets signals the event, and unblocks any calls to Wait().
+  void Fire()
+  {
+    std::unique_lock<std::mutex> lock(Mutex);
+    Fired = true;
+    Cv.notify_all();
+  }
+
+private:
+  std::mutex Mutex;
+  std::condition_variable Cv;
+  bool Fired = false;
+};
+
+class Semaphore
+{
+public:
+  Semaphore(int count_ = 0)
+    : Count(count_)
+  {
+  }
+
+  inline void Notify()
+  {
+    std::unique_lock<std::mutex> lock(Mutex);
+    Count++;
+    // notify the waiting thread
+    Cv.notify_one();
+  }
+
+  inline void Wait()
+  {
+    std::unique_lock<std::mutex> lock(Mutex);
+    while (Count == 0) {
+      // wait on the mutex until notify is called
+      Cv.wait(lock);
+    }
+    Count--;
+  }
+
+private:
+  std::mutex Mutex;
+  std::condition_variable Cv;
+  int Count;
+};
+
+cmDebuggerAdapter::cmDebuggerAdapter(
+  std::shared_ptr<cmDebuggerConnection> connection,
+  std::string const& dapLogPath)
+  : cmDebuggerAdapter(std::move(connection),
+                      dapLogPath.empty()
+                        ? cm::nullopt
+                        : cm::optional<std::shared_ptr<dap::Writer>>(
+                            dap::file(dapLogPath.c_str())))
+{
+}
+
+cmDebuggerAdapter::cmDebuggerAdapter(
+  std::shared_ptr<cmDebuggerConnection> connection,
+  cm::optional<std::shared_ptr<dap::Writer>> logger)
+  : Connection(std::move(connection))
+  , SessionActive(true)
+  , DisconnectEvent(cm::make_unique<SyncEvent>())
+  , ConfigurationDoneEvent(cm::make_unique<SyncEvent>())
+  , ContinueSem(cm::make_unique<Semaphore>())
+  , ThreadManager(cm::make_unique<cmDebuggerThreadManager>())
+{
+  if (logger.has_value()) {
+    SessionLog = std::move(logger.value());
+  }
+  ClearStepRequests();
+
+  Session = dap::Session::create();
+  BreakpointManager =
+    cm::make_unique<cmDebuggerBreakpointManager>(Session.get());
+  ExceptionManager =
+    cm::make_unique<cmDebuggerExceptionManager>(Session.get());
+
+  // Handle errors reported by the Session. These errors include protocol
+  // parsing errors and receiving messages with no handler.
+  Session->onError([this](const char* msg) {
+    if (SessionLog) {
+      dap::writef(SessionLog, "dap::Session error: %s\n", msg);
+    }
+
+    std::cout << "[CMake Debugger] DAP session error: " << msg << std::endl;
+
+    BreakpointManager->ClearAll();
+    ExceptionManager->ClearAll();
+    ClearStepRequests();
+    ContinueSem->Notify();
+    DisconnectEvent->Fire();
+    SessionActive.store(false);
+  });
+
+  // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize
+  Session->registerHandler([this](const dap::CMakeInitializeRequest& req) {
+    SupportsVariableType = req.supportsVariableType.value(false);
+    dap::CMakeInitializeResponse response;
+    response.supportsConfigurationDoneRequest = true;
+    response.cmakeVersion.major = CMake_VERSION_MAJOR;
+    response.cmakeVersion.minor = CMake_VERSION_MINOR;
+    response.cmakeVersion.patch = CMake_VERSION_PATCH;
+    response.cmakeVersion.full = CMake_VERSION;
+    ExceptionManager->HandleInitializeRequest(response);
+    return response;
+  });
+
+  // https://microsoft.github.io/debug-adapter-protocol/specification#Events_Initialized
+  Session->registerSentHandler(
+    [&](const dap::ResponseOrError<dap::CMakeInitializeResponse>&) {
+      Session->send(dap::InitializedEvent());
+    });
+
+  // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Threads
+  Session->registerHandler([this](const dap::ThreadsRequest& req) {
+    (void)req;
+    std::unique_lock<std::mutex> lock(Mutex);
+    dap::ThreadsResponse response;
+    dap::Thread thread;
+    thread.id = DefaultThread->GetId();
+    thread.name = DefaultThread->GetName();
+    response.threads.push_back(thread);
+    return response;
+  });
+
+  // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_StackTrace
+  Session->registerHandler([this](const dap::StackTraceRequest& request)
+                             -> dap::ResponseOrError<dap::StackTraceResponse> {
+    std::unique_lock<std::mutex> lock(Mutex);
+
+    cm::optional<dap::StackTraceResponse> response =
+      ThreadManager->GetThreadStackTraceResponse(request.threadId);
+    if (response.has_value()) {
+      return response.value();
+    }
+
+    return dap::Error("Unknown threadId '%d'", int(request.threadId));
+  });
+
+  // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Scopes
+  Session->registerHandler([this](const dap::ScopesRequest& request)
+                             -> dap::ResponseOrError<dap::ScopesResponse> {
+    std::unique_lock<std::mutex> lock(Mutex);
+    return DefaultThread->GetScopesResponse(request.frameId,
+                                            SupportsVariableType);
+  });
+
+  // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Variables
+  Session->registerHandler([this](const dap::VariablesRequest& request)
+                             -> dap::ResponseOrError<dap::VariablesResponse> {
+    return DefaultThread->GetVariablesResponse(request);
+  });
+
+  // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Pause
+  Session->registerHandler([this](const dap::PauseRequest& req) {
+    (void)req;
+    PauseRequest.store(true);
+    return dap::PauseResponse();
+  });
+
+  // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Continue
+  Session->registerHandler([this](const dap::ContinueRequest& req) {
+    (void)req;
+    ContinueSem->Notify();
+    return dap::ContinueResponse();
+  });
+
+  // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Next
+  Session->registerHandler([this](const dap::NextRequest& req) {
+    (void)req;
+    NextStepFrom.store(DefaultThread->GetStackFrameSize());
+    ContinueSem->Notify();
+    return dap::NextResponse();
+  });
+
+  // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_StepIn
+  Session->registerHandler([this](const dap::StepInRequest& req) {
+    (void)req;
+    // This would stop after stepped in, single line stepped or stepped out.
+    StepInRequest.store(true);
+    ContinueSem->Notify();
+    return dap::StepInResponse();
+  });
+
+  // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_StepOut
+  Session->registerHandler([this](const dap::StepOutRequest& req) {
+    (void)req;
+    StepOutDepth.store(DefaultThread->GetStackFrameSize() - 1);
+    ContinueSem->Notify();
+    return dap::StepOutResponse();
+  });
+
+  // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Launch
+  Session->registerHandler([](const dap::LaunchRequest& req) {
+    (void)req;
+    return dap::LaunchResponse();
+  });
+
+  // Handler for disconnect requests
+  Session->registerHandler([this](const dap::DisconnectRequest& request) {
+    (void)request;
+    BreakpointManager->ClearAll();
+    ExceptionManager->ClearAll();
+    ClearStepRequests();
+    ContinueSem->Notify();
+    DisconnectEvent->Fire();
+    SessionActive.store(false);
+    return dap::DisconnectResponse();
+  });
+
+  Session->registerHandler([this](const dap::EvaluateRequest& request) {
+    dap::EvaluateResponse response;
+    if (request.frameId.has_value()) {
+      std::shared_ptr<cmDebuggerStackFrame> frame =
+        DefaultThread->GetStackFrame(request.frameId.value());
+
+      auto var = frame->GetMakefile()->GetDefinition(request.expression);
+      if (var) {
+        response.type = "string";
+        response.result = var;
+        return response;
+      }
+    }
+
+    return response;
+  });
+
+  // The ConfigurationDone request is made by the client once all configuration
+  // requests have been made.
+  // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_ConfigurationDone
+  Session->registerHandler([this](const dap::ConfigurationDoneRequest& req) {
+    (void)req;
+    ConfigurationDoneEvent->Fire();
+    return dap::ConfigurationDoneResponse();
+  });
+
+  std::string errorMessage;
+  if (!Connection->StartListening(errorMessage)) {
+    throw std::runtime_error(errorMessage);
+  }
+
+  // Connect to the client. Write a well-known message to stdout so that
+  // clients know it is safe to attempt to connect.
+  std::cout << "Waiting for debugger client to connect..." << std::endl;
+  Connection->WaitForConnection();
+  std::cout << "Debugger client connected." << std::endl;
+
+  if (SessionLog) {
+    Session->connect(spy(Connection->GetReader(), SessionLog),
+                     spy(Connection->GetWriter(), SessionLog));
+  } else {
+    Session->connect(Connection->GetReader(), Connection->GetWriter());
+  }
+
+  // Start the processing thread.
+  SessionThread = std::thread([this] {
+    while (SessionActive.load()) {
+      if (auto payload = Session->getPayload()) {
+        payload();
+      }
+    }
+  });
+
+  ConfigurationDoneEvent->Wait();
+
+  DefaultThread = ThreadManager->StartThread("CMake script");
+  dap::ThreadEvent threadEvent;
+  threadEvent.reason = "started";
+  threadEvent.threadId = DefaultThread->GetId();
+  Session->send(threadEvent);
+}
+
+cmDebuggerAdapter::~cmDebuggerAdapter()
+{
+  if (SessionThread.joinable()) {
+    SessionThread.join();
+  }
+
+  Session.reset(nullptr);
+
+  if (SessionLog) {
+    SessionLog->close();
+  }
+}
+
+void cmDebuggerAdapter::ReportExitCode(int exitCode)
+{
+  ThreadManager->EndThread(DefaultThread);
+  dap::ThreadEvent threadEvent;
+  threadEvent.reason = "exited";
+  threadEvent.threadId = DefaultThread->GetId();
+  DefaultThread.reset();
+
+  dap::ExitedEvent exitEvent;
+  exitEvent.exitCode = exitCode;
+
+  dap::TerminatedEvent terminatedEvent;
+
+  if (SessionActive.load()) {
+    Session->send(threadEvent);
+    Session->send(exitEvent);
+    Session->send(terminatedEvent);
+  }
+
+  // Wait until disconnected or error.
+  DisconnectEvent->Wait();
+}
+
+void cmDebuggerAdapter::OnFileParsedSuccessfully(
+  std::string const& sourcePath,
+  std::vector<cmListFileFunction> const& functions)
+{
+  BreakpointManager->SourceFileLoaded(sourcePath, functions);
+}
+
+void cmDebuggerAdapter::OnBeginFunctionCall(cmMakefile* mf,
+                                            std::string const& sourcePath,
+                                            cmListFileFunction const& lff)
+{
+  std::unique_lock<std::mutex> lock(Mutex);
+  DefaultThread->PushStackFrame(mf, sourcePath, lff);
+
+  if (lff.Line() == 0) {
+    // File just loaded, continue to first valid function call.
+    return;
+  }
+
+  auto hits = BreakpointManager->GetBreakpoints(sourcePath, lff.Line());
+  lock.unlock();
+
+  bool waitSem = false;
+  dap::StoppedEvent stoppedEvent;
+  stoppedEvent.allThreadsStopped = true;
+  stoppedEvent.threadId = DefaultThread->GetId();
+  if (!hits.empty()) {
+    ClearStepRequests();
+    waitSem = true;
+
+    dap::array<dap::integer> hitBreakpoints;
+    hitBreakpoints.resize(hits.size());
+    std::transform(hits.begin(), hits.end(), hitBreakpoints.begin(),
+                   [&](const int64_t& id) { return dap::integer(id); });
+    stoppedEvent.reason = "breakpoint";
+    stoppedEvent.hitBreakpointIds = hitBreakpoints;
+  }
+
+  if (long(DefaultThread->GetStackFrameSize()) <= NextStepFrom.load() ||
+      StepInRequest.load() ||
+      long(DefaultThread->GetStackFrameSize()) <= StepOutDepth.load()) {
+    ClearStepRequests();
+    waitSem = true;
+
+    stoppedEvent.reason = "step";
+  }
+
+  if (PauseRequest.load()) {
+    ClearStepRequests();
+    waitSem = true;
+
+    stoppedEvent.reason = "pause";
+  }
+
+  if (waitSem) {
+    Session->send(stoppedEvent);
+    ContinueSem->Wait();
+  }
+}
+
+void cmDebuggerAdapter::OnEndFunctionCall()
+{
+  DefaultThread->PopStackFrame();
+}
+
+static std::shared_ptr<cmListFileFunction> listFileFunction;
+
+void cmDebuggerAdapter::OnBeginFileParse(cmMakefile* mf,
+                                         std::string const& sourcePath)
+{
+  std::unique_lock<std::mutex> lock(Mutex);
+
+  listFileFunction = std::make_shared<cmListFileFunction>(
+    sourcePath, 0, 0, std::vector<cmListFileArgument>());
+  DefaultThread->PushStackFrame(mf, sourcePath, *listFileFunction);
+}
+
+void cmDebuggerAdapter::OnEndFileParse()
+{
+  DefaultThread->PopStackFrame();
+  listFileFunction = nullptr;
+}
+
+void cmDebuggerAdapter::OnMessageOutput(MessageType t, std::string const& text)
+{
+  cm::optional<dap::StoppedEvent> stoppedEvent =
+    ExceptionManager->RaiseExceptionIfAny(t, text);
+  if (stoppedEvent.has_value()) {
+    stoppedEvent->threadId = DefaultThread->GetId();
+    Session->send(*stoppedEvent);
+    ContinueSem->Wait();
+  }
+}
+
+void cmDebuggerAdapter::ClearStepRequests()
+{
+  NextStepFrom.store(INT_MIN);
+  StepInRequest.store(false);
+  StepOutDepth.store(INT_MIN);
+  PauseRequest.store(false);
+}
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerAdapter.h b/Source/cmDebuggerAdapter.h
new file mode 100644
index 0000000..f261d88
--- /dev/null
+++ b/Source/cmDebuggerAdapter.h
@@ -0,0 +1,93 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <atomic>
+#include <cstdint>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include <cm/optional>
+
+#include <cm3p/cppdap/io.h> // IWYU pragma: keep
+
+#include "cmMessageType.h"
+
+class cmListFileFunction;
+class cmMakefile;
+
+namespace cmDebugger {
+class Semaphore;
+class SyncEvent;
+class cmDebuggerBreakpointManager;
+class cmDebuggerExceptionManager;
+class cmDebuggerThread;
+class cmDebuggerThreadManager;
+}
+
+namespace dap {
+class Session;
+}
+
+namespace cmDebugger {
+
+class cmDebuggerConnection
+{
+public:
+  virtual ~cmDebuggerConnection() = default;
+  virtual bool StartListening(std::string& errorMessage) = 0;
+  virtual void WaitForConnection() = 0;
+  virtual std::shared_ptr<dap::Reader> GetReader() = 0;
+  virtual std::shared_ptr<dap::Writer> GetWriter() = 0;
+};
+
+class cmDebuggerAdapter
+{
+public:
+  cmDebuggerAdapter(std::shared_ptr<cmDebuggerConnection> connection,
+                    std::string const& dapLogPath);
+  cmDebuggerAdapter(std::shared_ptr<cmDebuggerConnection> connection,
+                    cm::optional<std::shared_ptr<dap::Writer>> logger);
+  ~cmDebuggerAdapter();
+
+  void ReportExitCode(int exitCode);
+
+  void OnFileParsedSuccessfully(
+    std::string const& sourcePath,
+    std::vector<cmListFileFunction> const& functions);
+  void OnBeginFunctionCall(cmMakefile* mf, std::string const& sourcePath,
+                           cmListFileFunction const& lff);
+  void OnEndFunctionCall();
+  void OnBeginFileParse(cmMakefile* mf, std::string const& sourcePath);
+  void OnEndFileParse();
+
+  void OnMessageOutput(MessageType t, std::string const& text);
+
+private:
+  void ClearStepRequests();
+  std::shared_ptr<cmDebuggerConnection> Connection;
+  std::unique_ptr<dap::Session> Session;
+  std::shared_ptr<dap::Writer> SessionLog;
+  std::thread SessionThread;
+  std::atomic<bool> SessionActive;
+  std::mutex Mutex;
+  std::unique_ptr<SyncEvent> DisconnectEvent;
+  std::unique_ptr<SyncEvent> ConfigurationDoneEvent;
+  std::unique_ptr<Semaphore> ContinueSem;
+  std::atomic<int64_t> NextStepFrom;
+  std::atomic<bool> StepInRequest;
+  std::atomic<int64_t> StepOutDepth;
+  std::atomic<bool> PauseRequest;
+  std::unique_ptr<cmDebuggerThreadManager> ThreadManager;
+  std::shared_ptr<cmDebuggerThread> DefaultThread;
+  std::unique_ptr<cmDebuggerBreakpointManager> BreakpointManager;
+  std::unique_ptr<cmDebuggerExceptionManager> ExceptionManager;
+  bool SupportsVariableType;
+};
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerBreakpointManager.cxx b/Source/cmDebuggerBreakpointManager.cxx
new file mode 100644
index 0000000..152f0f5
--- /dev/null
+++ b/Source/cmDebuggerBreakpointManager.cxx
@@ -0,0 +1,200 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmDebuggerBreakpointManager.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+
+#include <cm3p/cppdap/optional.h>
+#include <cm3p/cppdap/session.h>
+#include <cm3p/cppdap/types.h>
+
+#include "cmDebuggerSourceBreakpoint.h"
+#include "cmListFileCache.h"
+#include "cmSystemTools.h"
+
+namespace cmDebugger {
+
+cmDebuggerBreakpointManager::cmDebuggerBreakpointManager(
+  dap::Session* dapSession)
+  : DapSession(dapSession)
+{
+  // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_SetBreakpoints
+  DapSession->registerHandler([&](const dap::SetBreakpointsRequest& request) {
+    return HandleSetBreakpointsRequest(request);
+  });
+}
+
+int64_t cmDebuggerBreakpointManager::FindFunctionStartLine(
+  std::string const& sourcePath, int64_t line)
+{
+  auto location =
+    find_if(ListFileFunctionLines[sourcePath].begin(),
+            ListFileFunctionLines[sourcePath].end(),
+            [=](cmDebuggerFunctionLocation const& loc) {
+              return loc.StartLine <= line && loc.EndLine >= line;
+            });
+
+  if (location != ListFileFunctionLines[sourcePath].end()) {
+    return location->StartLine;
+  }
+
+  return 0;
+}
+
+int64_t cmDebuggerBreakpointManager::CalibrateBreakpointLine(
+  std::string const& sourcePath, int64_t line)
+{
+  auto location = find_if(ListFileFunctionLines[sourcePath].begin(),
+                          ListFileFunctionLines[sourcePath].end(),
+                          [=](cmDebuggerFunctionLocation const& loc) {
+                            return loc.StartLine >= line;
+                          });
+
+  if (location != ListFileFunctionLines[sourcePath].end()) {
+    return location->StartLine;
+  }
+
+  if (!ListFileFunctionLines[sourcePath].empty() &&
+      ListFileFunctionLines[sourcePath].back().EndLine <= line) {
+    // return last function start line for any breakpoints after.
+    return ListFileFunctionLines[sourcePath].back().StartLine;
+  }
+
+  return 0;
+}
+
+dap::SetBreakpointsResponse
+cmDebuggerBreakpointManager::HandleSetBreakpointsRequest(
+  dap::SetBreakpointsRequest const& request)
+{
+  std::unique_lock<std::mutex> lock(Mutex);
+
+  dap::SetBreakpointsResponse response;
+
+  auto sourcePath =
+    cmSystemTools::GetActualCaseForPath(request.source.path.value());
+  const dap::array<dap::SourceBreakpoint> defaultValue{};
+  const auto& breakpoints = request.breakpoints.value(defaultValue);
+  if (ListFileFunctionLines.find(sourcePath) != ListFileFunctionLines.end()) {
+    // The file has loaded, we can validate breakpoints.
+    if (Breakpoints.find(sourcePath) != Breakpoints.end()) {
+      Breakpoints[sourcePath].clear();
+    }
+    response.breakpoints.resize(breakpoints.size());
+    for (size_t i = 0; i < breakpoints.size(); i++) {
+      int64_t correctedLine =
+        CalibrateBreakpointLine(sourcePath, breakpoints[i].line);
+      if (correctedLine > 0) {
+        Breakpoints[sourcePath].emplace_back(NextBreakpointId++,
+                                             correctedLine);
+        response.breakpoints[i].id = Breakpoints[sourcePath].back().GetId();
+        response.breakpoints[i].line =
+          Breakpoints[sourcePath].back().GetLine();
+        response.breakpoints[i].verified = true;
+      } else {
+        response.breakpoints[i].verified = false;
+        response.breakpoints[i].line = breakpoints[i].line;
+      }
+      dap::Source dapSrc;
+      dapSrc.path = sourcePath;
+      response.breakpoints[i].source = dapSrc;
+    }
+  } else {
+    // The file has not loaded, validate breakpoints later.
+    ListFilePendingValidations.emplace(sourcePath);
+
+    response.breakpoints.resize(breakpoints.size());
+    for (size_t i = 0; i < breakpoints.size(); i++) {
+      Breakpoints[sourcePath].emplace_back(NextBreakpointId++,
+                                           breakpoints[i].line);
+      response.breakpoints[i].id = Breakpoints[sourcePath].back().GetId();
+      response.breakpoints[i].line = Breakpoints[sourcePath].back().GetLine();
+      response.breakpoints[i].verified = false;
+      dap::Source dapSrc;
+      dapSrc.path = sourcePath;
+      response.breakpoints[i].source = dapSrc;
+    }
+  }
+
+  return response;
+}
+
+void cmDebuggerBreakpointManager::SourceFileLoaded(
+  std::string const& sourcePath,
+  std::vector<cmListFileFunction> const& functions)
+{
+  std::unique_lock<std::mutex> lock(Mutex);
+  if (ListFileFunctionLines.find(sourcePath) != ListFileFunctionLines.end()) {
+    // this is not expected.
+    return;
+  }
+
+  for (cmListFileFunction const& func : functions) {
+    ListFileFunctionLines[sourcePath].emplace_back(
+      cmDebuggerFunctionLocation{ func.Line(), func.LineEnd() });
+  }
+
+  if (ListFilePendingValidations.find(sourcePath) ==
+      ListFilePendingValidations.end()) {
+    return;
+  }
+
+  ListFilePendingValidations.erase(sourcePath);
+
+  for (size_t i = 0; i < Breakpoints[sourcePath].size(); i++) {
+    dap::BreakpointEvent breakpointEvent;
+    breakpointEvent.breakpoint.id = Breakpoints[sourcePath][i].GetId();
+    breakpointEvent.breakpoint.line = Breakpoints[sourcePath][i].GetLine();
+    auto source = dap::Source();
+    source.path = sourcePath;
+    breakpointEvent.breakpoint.source = source;
+    int64_t correctedLine = CalibrateBreakpointLine(
+      sourcePath, Breakpoints[sourcePath][i].GetLine());
+    if (correctedLine != Breakpoints[sourcePath][i].GetLine()) {
+      Breakpoints[sourcePath][i].ChangeLine(correctedLine);
+    }
+    breakpointEvent.reason = "changed";
+    breakpointEvent.breakpoint.verified = (correctedLine > 0);
+    if (breakpointEvent.breakpoint.verified) {
+      breakpointEvent.breakpoint.line = correctedLine;
+    } else {
+      Breakpoints[sourcePath][i].Invalid();
+    }
+
+    DapSession->send(breakpointEvent);
+  }
+}
+
+std::vector<int64_t> cmDebuggerBreakpointManager::GetBreakpoints(
+  std::string const& sourcePath, int64_t line)
+{
+  std::unique_lock<std::mutex> lock(Mutex);
+  const auto& all = Breakpoints[sourcePath];
+  std::vector<int64_t> breakpoints;
+  if (all.empty()) {
+    return breakpoints;
+  }
+
+  auto it = all.begin();
+
+  while ((it = std::find_if(
+            it, all.end(), [&](const cmDebuggerSourceBreakpoint& breakpoint) {
+              return (breakpoint.GetIsValid() && breakpoint.GetLine() == line);
+            })) != all.end()) {
+    breakpoints.emplace_back(it->GetId());
+    ++it;
+  }
+
+  return breakpoints;
+}
+
+void cmDebuggerBreakpointManager::ClearAll()
+{
+  std::unique_lock<std::mutex> lock(Mutex);
+  Breakpoints.clear();
+}
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerBreakpointManager.h b/Source/cmDebuggerBreakpointManager.h
new file mode 100644
index 0000000..a4e5df5
--- /dev/null
+++ b/Source/cmDebuggerBreakpointManager.h
@@ -0,0 +1,61 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <cstdint>
+#include <mutex>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <cm3p/cppdap/protocol.h>
+
+class cmListFileFunction;
+
+namespace cmDebugger {
+class cmDebuggerSourceBreakpoint;
+}
+
+namespace dap {
+class Session;
+}
+
+namespace cmDebugger {
+
+struct cmDebuggerFunctionLocation
+{
+  int64_t StartLine;
+  int64_t EndLine;
+};
+
+/** The breakpoint manager. */
+class cmDebuggerBreakpointManager
+{
+  dap::Session* DapSession;
+  std::mutex Mutex;
+  std::unordered_map<std::string, std::vector<cmDebuggerSourceBreakpoint>>
+    Breakpoints;
+  std::unordered_map<std::string,
+                     std::vector<struct cmDebuggerFunctionLocation>>
+    ListFileFunctionLines;
+  std::unordered_set<std::string> ListFilePendingValidations;
+  int64_t NextBreakpointId = 0;
+
+  dap::SetBreakpointsResponse HandleSetBreakpointsRequest(
+    dap::SetBreakpointsRequest const& request);
+  int64_t FindFunctionStartLine(std::string const& sourcePath, int64_t line);
+  int64_t CalibrateBreakpointLine(std::string const& sourcePath, int64_t line);
+
+public:
+  cmDebuggerBreakpointManager(dap::Session* dapSession);
+  void SourceFileLoaded(std::string const& sourcePath,
+                        std::vector<cmListFileFunction> const& functions);
+  std::vector<int64_t> GetBreakpoints(std::string const& sourcePath,
+                                      int64_t line);
+  void ClearAll();
+};
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerExceptionManager.cxx b/Source/cmDebuggerExceptionManager.cxx
new file mode 100644
index 0000000..a27426c
--- /dev/null
+++ b/Source/cmDebuggerExceptionManager.cxx
@@ -0,0 +1,129 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmDebuggerExceptionManager.h"
+
+#include <utility>
+#include <vector>
+
+#include <cm3p/cppdap/optional.h>
+#include <cm3p/cppdap/session.h>
+#include <cm3p/cppdap/types.h>
+
+#include "cmDebuggerProtocol.h"
+#include "cmMessageType.h"
+
+namespace cmDebugger {
+
+cmDebuggerExceptionManager::cmDebuggerExceptionManager(
+  dap::Session* dapSession)
+  : DapSession(dapSession)
+{
+  // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_SetExceptionBreakpoints
+  DapSession->registerHandler(
+    [&](const dap::SetExceptionBreakpointsRequest& request) {
+      return HandleSetExceptionBreakpointsRequest(request);
+    });
+
+  // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_ExceptionInfo
+  DapSession->registerHandler([&](const dap::ExceptionInfoRequest& request) {
+    (void)request;
+    return HandleExceptionInfoRequest();
+  });
+
+  ExceptionMap[MessageType::AUTHOR_WARNING] =
+    cmDebuggerExceptionFilter{ "AUTHOR_WARNING", "Warning (dev)" };
+  ExceptionMap[MessageType::AUTHOR_ERROR] =
+    cmDebuggerExceptionFilter{ "AUTHOR_ERROR", "Error (dev)" };
+  ExceptionMap[MessageType::FATAL_ERROR] =
+    cmDebuggerExceptionFilter{ "FATAL_ERROR", "Fatal error" };
+  ExceptionMap[MessageType::INTERNAL_ERROR] =
+    cmDebuggerExceptionFilter{ "INTERNAL_ERROR", "Internal error" };
+  ExceptionMap[MessageType::MESSAGE] =
+    cmDebuggerExceptionFilter{ "MESSAGE", "Other messages" };
+  ExceptionMap[MessageType::WARNING] =
+    cmDebuggerExceptionFilter{ "WARNING", "Warning" };
+  ExceptionMap[MessageType::LOG] =
+    cmDebuggerExceptionFilter{ "LOG", "Debug log" };
+  ExceptionMap[MessageType::DEPRECATION_ERROR] =
+    cmDebuggerExceptionFilter{ "DEPRECATION_ERROR", "Deprecation error" };
+  ExceptionMap[MessageType::DEPRECATION_WARNING] =
+    cmDebuggerExceptionFilter{ "DEPRECATION_WARNING", "Deprecation warning" };
+  RaiseExceptions["AUTHOR_ERROR"] = true;
+  RaiseExceptions["FATAL_ERROR"] = true;
+  RaiseExceptions["INTERNAL_ERROR"] = true;
+  RaiseExceptions["DEPRECATION_ERROR"] = true;
+}
+
+dap::SetExceptionBreakpointsResponse
+cmDebuggerExceptionManager::HandleSetExceptionBreakpointsRequest(
+  dap::SetExceptionBreakpointsRequest const& request)
+{
+  std::unique_lock<std::mutex> lock(Mutex);
+  dap::SetExceptionBreakpointsResponse response;
+  RaiseExceptions.clear();
+  for (const auto& filter : request.filters) {
+    RaiseExceptions[filter] = true;
+  }
+
+  return response;
+}
+
+dap::ExceptionInfoResponse
+cmDebuggerExceptionManager::HandleExceptionInfoRequest()
+{
+  std::unique_lock<std::mutex> lock(Mutex);
+
+  dap::ExceptionInfoResponse response;
+  if (TheException.has_value()) {
+    response.exceptionId = TheException->Id;
+    response.breakMode = "always";
+    response.description = TheException->Description;
+    TheException = {};
+  }
+  return response;
+}
+
+void cmDebuggerExceptionManager::HandleInitializeRequest(
+  dap::CMakeInitializeResponse& response)
+{
+  std::unique_lock<std::mutex> lock(Mutex);
+  response.supportsExceptionInfoRequest = true;
+
+  dap::array<dap::ExceptionBreakpointsFilter> exceptionBreakpointFilters;
+  for (auto& pair : ExceptionMap) {
+    dap::ExceptionBreakpointsFilter filter;
+    filter.filter = pair.second.Filter;
+    filter.label = pair.second.Label;
+    filter.def = RaiseExceptions[filter.filter];
+    exceptionBreakpointFilters.emplace_back(filter);
+  }
+
+  response.exceptionBreakpointFilters = exceptionBreakpointFilters;
+}
+
+cm::optional<dap::StoppedEvent>
+cmDebuggerExceptionManager::RaiseExceptionIfAny(MessageType t,
+                                                std::string const& text)
+{
+  cm::optional<dap::StoppedEvent> maybeStoppedEvent;
+  std::unique_lock<std::mutex> lock(Mutex);
+  if (RaiseExceptions[ExceptionMap[t].Filter]) {
+    dap::StoppedEvent stoppedEvent;
+    stoppedEvent.allThreadsStopped = true;
+    stoppedEvent.reason = "exception";
+    stoppedEvent.description = "Pause on exception";
+    stoppedEvent.text = text;
+    TheException = cmDebuggerException{ ExceptionMap[t].Filter, text };
+    maybeStoppedEvent = std::move(stoppedEvent);
+  }
+
+  return maybeStoppedEvent;
+}
+
+void cmDebuggerExceptionManager::ClearAll()
+{
+  std::unique_lock<std::mutex> lock(Mutex);
+  RaiseExceptions.clear();
+}
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerExceptionManager.h b/Source/cmDebuggerExceptionManager.h
new file mode 100644
index 0000000..b819128
--- /dev/null
+++ b/Source/cmDebuggerExceptionManager.h
@@ -0,0 +1,70 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <cstddef>
+#include <functional>
+#include <mutex>
+#include <string>
+#include <unordered_map>
+
+#include <cm/optional>
+
+#include <cm3p/cppdap/protocol.h>
+
+#include "cmMessageType.h"
+
+namespace dap {
+class Session;
+struct CMakeInitializeResponse;
+}
+
+namespace cmDebugger {
+
+struct cmDebuggerException
+{
+  std::string Id;
+  std::string Description;
+};
+
+struct cmDebuggerExceptionFilter
+{
+  std::string Filter;
+  std::string Label;
+};
+
+/** The exception manager. */
+class cmDebuggerExceptionManager
+{
+  // Some older C++ standard libraries cannot hash an enum class by default.
+  struct MessageTypeHash
+  {
+    std::size_t operator()(MessageType t) const
+    {
+      return std::hash<int>{}(static_cast<int>(t));
+    }
+  };
+
+  dap::Session* DapSession;
+  std::mutex Mutex;
+  std::unordered_map<std::string, bool> RaiseExceptions;
+  std::unordered_map<MessageType, cmDebuggerExceptionFilter, MessageTypeHash>
+    ExceptionMap;
+  cm::optional<cmDebuggerException> TheException;
+
+  dap::SetExceptionBreakpointsResponse HandleSetExceptionBreakpointsRequest(
+    dap::SetExceptionBreakpointsRequest const& request);
+
+  dap::ExceptionInfoResponse HandleExceptionInfoRequest();
+
+public:
+  cmDebuggerExceptionManager(dap::Session* dapSession);
+  void HandleInitializeRequest(dap::CMakeInitializeResponse& response);
+  cm::optional<dap::StoppedEvent> RaiseExceptionIfAny(MessageType t,
+                                                      std::string const& text);
+  void ClearAll();
+};
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerPipeConnection.cxx b/Source/cmDebuggerPipeConnection.cxx
new file mode 100644
index 0000000..1b54346
--- /dev/null
+++ b/Source/cmDebuggerPipeConnection.cxx
@@ -0,0 +1,293 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmDebuggerPipeConnection.h"
+
+#include <algorithm>
+#include <cassert>
+#include <cstring>
+#include <stdexcept>
+#include <utility>
+
+namespace cmDebugger {
+
+struct write_req_t
+{
+  uv_write_t req;
+  uv_buf_t buf;
+};
+
+cmDebuggerPipeBase::cmDebuggerPipeBase(std::string name)
+  : PipeName(std::move(name))
+{
+  Loop.init();
+  LoopExit.init(
+    *Loop, [](uv_async_t* handle) { uv_stop((uv_loop_t*)handle->data); },
+    Loop);
+  WriteEvent.init(
+    *Loop,
+    [](uv_async_t* handle) {
+      auto* conn = static_cast<cmDebuggerPipeBase*>(handle->data);
+      conn->WriteInternal();
+    },
+    this);
+  PipeClose.init(
+    *Loop,
+    [](uv_async_t* handle) {
+      auto* conn = static_cast<cmDebuggerPipeBase*>(handle->data);
+      if (conn->Pipe.get()) {
+        conn->Pipe->data = nullptr;
+        conn->Pipe.reset();
+      }
+    },
+    this);
+}
+
+void cmDebuggerPipeBase::WaitForConnection()
+{
+  std::unique_lock<std::mutex> lock(Mutex);
+  Connected.wait(lock, [this] { return isOpen() || FailedToOpen; });
+  if (FailedToOpen) {
+    throw std::runtime_error("Failed to open debugger connection.");
+  }
+}
+
+void cmDebuggerPipeBase::close()
+{
+  std::unique_lock<std::mutex> lock(Mutex);
+
+  CloseConnection();
+  PipeClose.send();
+  lock.unlock();
+  ReadReady.notify_all();
+}
+
+size_t cmDebuggerPipeBase::read(void* buffer, size_t n)
+{
+  std::unique_lock<std::mutex> lock(Mutex);
+  ReadReady.wait(lock, [this] { return !isOpen() || !ReadBuffer.empty(); });
+
+  if (!isOpen() && ReadBuffer.empty()) {
+    return 0;
+  }
+
+  auto size = std::min(n, ReadBuffer.size());
+  memcpy(buffer, ReadBuffer.data(), size);
+  ReadBuffer.erase(0, size);
+  return size;
+}
+
+bool cmDebuggerPipeBase::write(const void* buffer, size_t n)
+{
+  std::unique_lock<std::mutex> lock(Mutex);
+  WriteBuffer.append(static_cast<const char*>(buffer), n);
+  lock.unlock();
+  WriteEvent.send();
+
+  lock.lock();
+  WriteComplete.wait(lock, [this] { return WriteBuffer.empty(); });
+  return true;
+}
+
+void cmDebuggerPipeBase::StopLoop()
+{
+  LoopExit.send();
+
+  if (LoopThread.joinable()) {
+    LoopThread.join();
+  }
+}
+
+void cmDebuggerPipeBase::BufferData(const std::string& data)
+{
+  std::unique_lock<std::mutex> lock(Mutex);
+  ReadBuffer += data;
+  lock.unlock();
+  ReadReady.notify_all();
+}
+
+void cmDebuggerPipeBase::WriteInternal()
+{
+  std::unique_lock<std::mutex> lock(Mutex);
+  auto n = WriteBuffer.length();
+  assert(this->Pipe.get());
+  write_req_t* req = new write_req_t;
+  req->req.data = &WriteComplete;
+  char* rawBuffer = new char[n];
+  req->buf = uv_buf_init(rawBuffer, static_cast<unsigned int>(n));
+  memcpy(req->buf.base, WriteBuffer.data(), n);
+  WriteBuffer.clear();
+  lock.unlock();
+
+  uv_write(
+    reinterpret_cast<uv_write_t*>(req), this->Pipe, &req->buf, 1,
+    [](uv_write_t* cb_req, int status) {
+      (void)status; // We need to free memory even if the write failed.
+      write_req_t* wr = reinterpret_cast<write_req_t*>(cb_req);
+      reinterpret_cast<std::condition_variable*>(wr->req.data)->notify_all();
+      delete[] (wr->buf.base);
+      delete wr;
+    });
+
+#ifdef __clang_analyzer__
+  // Tell clang-analyzer that 'rawBuffer' does not leak.
+  // We pass ownership to the closure.
+  delete[] rawBuffer;
+#endif
+}
+
+cmDebuggerPipeConnection::cmDebuggerPipeConnection(std::string name)
+  : cmDebuggerPipeBase(std::move(name))
+{
+  ServerPipeClose.init(
+    *Loop,
+    [](uv_async_t* handle) {
+      auto* conn = static_cast<cmDebuggerPipeConnection*>(handle->data);
+      if (conn->ServerPipe.get()) {
+        conn->ServerPipe->data = nullptr;
+        conn->ServerPipe.reset();
+      }
+    },
+    this);
+}
+
+cmDebuggerPipeConnection::~cmDebuggerPipeConnection()
+{
+  StopLoop();
+}
+
+bool cmDebuggerPipeConnection::StartListening(std::string& errorMessage)
+{
+  this->ServerPipe.init(*Loop, 0,
+                        static_cast<cmDebuggerPipeConnection*>(this));
+
+  int r;
+  if ((r = uv_pipe_bind(this->ServerPipe, this->PipeName.c_str())) != 0) {
+    errorMessage =
+      "Internal Error with " + this->PipeName + ": " + uv_err_name(r);
+    return false;
+  }
+
+  r = uv_listen(this->ServerPipe, 1, [](uv_stream_t* stream, int status) {
+    if (status >= 0) {
+      auto* conn = static_cast<cmDebuggerPipeConnection*>(stream->data);
+      if (conn) {
+        conn->Connect(stream);
+      }
+    }
+  });
+
+  if (r != 0) {
+    errorMessage =
+      "Internal Error listening on " + this->PipeName + ": " + uv_err_name(r);
+    return false;
+  }
+
+  // Start the libuv event loop thread so that a client can connect.
+  LoopThread = std::thread([this] { uv_run(Loop, UV_RUN_DEFAULT); });
+
+  StartedListening.set_value();
+
+  return true;
+}
+
+std::shared_ptr<dap::Reader> cmDebuggerPipeConnection::GetReader()
+{
+  return std::static_pointer_cast<dap::Reader>(shared_from_this());
+}
+
+std::shared_ptr<dap::Writer> cmDebuggerPipeConnection::GetWriter()
+{
+  return std::static_pointer_cast<dap::Writer>(shared_from_this());
+}
+
+bool cmDebuggerPipeConnection::isOpen()
+{
+  return this->Pipe.get() != nullptr;
+}
+
+void cmDebuggerPipeConnection::CloseConnection()
+{
+  ServerPipeClose.send();
+}
+
+void cmDebuggerPipeConnection::Connect(uv_stream_t* server)
+{
+  if (this->Pipe.get()) {
+    // Accept and close all pipes but the first:
+    cm::uv_pipe_ptr rejectPipe;
+
+    rejectPipe.init(*Loop, 0);
+    uv_accept(server, rejectPipe);
+
+    return;
+  }
+
+  cm::uv_pipe_ptr ClientPipe;
+  ClientPipe.init(*Loop, 0, static_cast<cmDebuggerPipeConnection*>(this));
+
+  if (uv_accept(server, ClientPipe) != 0) {
+    return;
+  }
+
+  StartReading<cmDebuggerPipeConnection>(ClientPipe);
+
+  std::unique_lock<std::mutex> lock(Mutex);
+  Pipe = std::move(ClientPipe);
+  lock.unlock();
+  Connected.notify_all();
+}
+
+cmDebuggerPipeClient::~cmDebuggerPipeClient()
+{
+  StopLoop();
+}
+
+void cmDebuggerPipeClient::Start()
+{
+  this->Pipe.init(*Loop, 0, static_cast<cmDebuggerPipeClient*>(this));
+
+  uv_connect_t* connect = new uv_connect_t;
+  connect->data = this;
+  uv_pipe_connect(
+    connect, Pipe, PipeName.c_str(), [](uv_connect_t* cb_connect, int status) {
+      auto* conn = static_cast<cmDebuggerPipeClient*>(cb_connect->data);
+      if (status >= 0) {
+        conn->Connect();
+      } else {
+        conn->FailConnection();
+      }
+      delete cb_connect;
+    });
+
+  // Start the libuv event loop so that the pipe can connect.
+  LoopThread = std::thread([this] { uv_run(Loop, UV_RUN_DEFAULT); });
+}
+
+bool cmDebuggerPipeClient::isOpen()
+{
+  return IsConnected;
+}
+
+void cmDebuggerPipeClient::CloseConnection()
+{
+  IsConnected = false;
+}
+
+void cmDebuggerPipeClient::Connect()
+{
+  StartReading<cmDebuggerPipeClient>(Pipe);
+  std::unique_lock<std::mutex> lock(Mutex);
+  IsConnected = true;
+  lock.unlock();
+  Connected.notify_all();
+}
+
+void cmDebuggerPipeClient::FailConnection()
+{
+  std::unique_lock<std::mutex> lock(Mutex);
+  FailedToOpen = true;
+  lock.unlock();
+  Connected.notify_all();
+}
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerPipeConnection.h b/Source/cmDebuggerPipeConnection.h
new file mode 100644
index 0000000..0991ff7
--- /dev/null
+++ b/Source/cmDebuggerPipeConnection.h
@@ -0,0 +1,139 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <condition_variable>
+#include <cstddef>
+#include <future>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <thread>
+
+#include <cm3p/cppdap/io.h>
+#include <cm3p/uv.h>
+
+#include "cmDebuggerAdapter.h"
+#include "cmUVHandlePtr.h"
+
+namespace cmDebugger {
+
+class cmDebuggerPipeBase : public dap::ReaderWriter
+{
+public:
+  cmDebuggerPipeBase(std::string name);
+
+  void WaitForConnection();
+
+  // dap::ReaderWriter implementation
+
+  void close() final;
+  size_t read(void* buffer, size_t n) final;
+  bool write(const void* buffer, size_t n) final;
+
+protected:
+  virtual void CloseConnection(){};
+  template <typename T>
+  void StartReading(uv_stream_t* stream)
+  {
+    uv_read_start(
+      stream,
+      // alloc_cb
+      [](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
+        (void)handle;
+        char* rawBuffer = new char[suggested_size];
+        *buf =
+          uv_buf_init(rawBuffer, static_cast<unsigned int>(suggested_size));
+      },
+      // read_cb
+      [](uv_stream_t* readStream, ssize_t nread, const uv_buf_t* buf) {
+        auto conn = static_cast<T*>(readStream->data);
+        if (conn) {
+          if (nread >= 0) {
+            conn->BufferData(std::string(buf->base, buf->base + nread));
+          } else {
+            conn->close();
+          }
+        }
+        delete[] (buf->base);
+      });
+  }
+  void StopLoop();
+
+  const std::string PipeName;
+  std::thread LoopThread;
+  cm::uv_loop_ptr Loop;
+  cm::uv_pipe_ptr Pipe;
+  std::mutex Mutex;
+  std::condition_variable Connected;
+  bool FailedToOpen = false;
+
+private:
+  void BufferData(const std::string& data);
+  void WriteInternal();
+
+  cm::uv_async_ptr LoopExit;
+  cm::uv_async_ptr WriteEvent;
+  cm::uv_async_ptr PipeClose;
+  std::string WriteBuffer;
+  std::string ReadBuffer;
+  std::condition_variable ReadReady;
+  std::condition_variable WriteComplete;
+};
+
+class cmDebuggerPipeConnection
+  : public cmDebuggerPipeBase
+  , public cmDebuggerConnection
+  , public std::enable_shared_from_this<cmDebuggerPipeConnection>
+{
+public:
+  cmDebuggerPipeConnection(std::string name);
+  ~cmDebuggerPipeConnection() override;
+
+  void WaitForConnection() override
+  {
+    cmDebuggerPipeBase::WaitForConnection();
+  }
+
+  bool StartListening(std::string& errorMessage) override;
+  std::shared_ptr<dap::Reader> GetReader() override;
+  std::shared_ptr<dap::Writer> GetWriter() override;
+
+  // dap::ReaderWriter implementation
+
+  bool isOpen() override;
+
+  // Used for unit test synchronization
+  std::promise<void> StartedListening;
+
+private:
+  void CloseConnection() override;
+  void Connect(uv_stream_t* server);
+
+  cm::uv_pipe_ptr ServerPipe;
+  cm::uv_async_ptr ServerPipeClose;
+};
+
+class cmDebuggerPipeClient : public cmDebuggerPipeBase
+{
+public:
+  using cmDebuggerPipeBase::cmDebuggerPipeBase;
+  ~cmDebuggerPipeClient() override;
+
+  void Start();
+
+  // dap::ReaderWriter implementation
+
+  bool isOpen() override;
+
+private:
+  void CloseConnection() override;
+  void Connect();
+  void FailConnection();
+
+  bool IsConnected = false;
+};
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerProtocol.cxx b/Source/cmDebuggerProtocol.cxx
new file mode 100644
index 0000000..505de35
--- /dev/null
+++ b/Source/cmDebuggerProtocol.cxx
@@ -0,0 +1,80 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include "cmDebuggerProtocol.h"
+
+#include <string>
+
+namespace dap {
+DAP_IMPLEMENT_STRUCT_TYPEINFO(CMakeVersion, "", DAP_FIELD(major, "major"),
+                              DAP_FIELD(minor, "minor"),
+                              DAP_FIELD(patch, "patch"),
+                              DAP_FIELD(full, "full"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(
+  CMakeInitializeResponse, "",
+  DAP_FIELD(additionalModuleColumns, "additionalModuleColumns"),
+  DAP_FIELD(completionTriggerCharacters, "completionTriggerCharacters"),
+  DAP_FIELD(exceptionBreakpointFilters, "exceptionBreakpointFilters"),
+  DAP_FIELD(supportSuspendDebuggee, "supportSuspendDebuggee"),
+  DAP_FIELD(supportTerminateDebuggee, "supportTerminateDebuggee"),
+  DAP_FIELD(supportedChecksumAlgorithms, "supportedChecksumAlgorithms"),
+  DAP_FIELD(supportsBreakpointLocationsRequest,
+            "supportsBreakpointLocationsRequest"),
+  DAP_FIELD(supportsCancelRequest, "supportsCancelRequest"),
+  DAP_FIELD(supportsClipboardContext, "supportsClipboardContext"),
+  DAP_FIELD(supportsCompletionsRequest, "supportsCompletionsRequest"),
+  DAP_FIELD(supportsConditionalBreakpoints, "supportsConditionalBreakpoints"),
+  DAP_FIELD(supportsConfigurationDoneRequest,
+            "supportsConfigurationDoneRequest"),
+  DAP_FIELD(supportsDataBreakpoints, "supportsDataBreakpoints"),
+  DAP_FIELD(supportsDelayedStackTraceLoading,
+            "supportsDelayedStackTraceLoading"),
+  DAP_FIELD(supportsDisassembleRequest, "supportsDisassembleRequest"),
+  DAP_FIELD(supportsEvaluateForHovers, "supportsEvaluateForHovers"),
+  DAP_FIELD(supportsExceptionFilterOptions, "supportsExceptionFilterOptions"),
+  DAP_FIELD(supportsExceptionInfoRequest, "supportsExceptionInfoRequest"),
+  DAP_FIELD(supportsExceptionOptions, "supportsExceptionOptions"),
+  DAP_FIELD(supportsFunctionBreakpoints, "supportsFunctionBreakpoints"),
+  DAP_FIELD(supportsGotoTargetsRequest, "supportsGotoTargetsRequest"),
+  DAP_FIELD(supportsHitConditionalBreakpoints,
+            "supportsHitConditionalBreakpoints"),
+  DAP_FIELD(supportsInstructionBreakpoints, "supportsInstructionBreakpoints"),
+  DAP_FIELD(supportsLoadedSourcesRequest, "supportsLoadedSourcesRequest"),
+  DAP_FIELD(supportsLogPoints, "supportsLogPoints"),
+  DAP_FIELD(supportsModulesRequest, "supportsModulesRequest"),
+  DAP_FIELD(supportsReadMemoryRequest, "supportsReadMemoryRequest"),
+  DAP_FIELD(supportsRestartFrame, "supportsRestartFrame"),
+  DAP_FIELD(supportsRestartRequest, "supportsRestartRequest"),
+  DAP_FIELD(supportsSetExpression, "supportsSetExpression"),
+  DAP_FIELD(supportsSetVariable, "supportsSetVariable"),
+  DAP_FIELD(supportsSingleThreadExecutionRequests,
+            "supportsSingleThreadExecutionRequests"),
+  DAP_FIELD(supportsStepBack, "supportsStepBack"),
+  DAP_FIELD(supportsStepInTargetsRequest, "supportsStepInTargetsRequest"),
+  DAP_FIELD(supportsSteppingGranularity, "supportsSteppingGranularity"),
+  DAP_FIELD(supportsTerminateRequest, "supportsTerminateRequest"),
+  DAP_FIELD(supportsTerminateThreadsRequest,
+            "supportsTerminateThreadsRequest"),
+  DAP_FIELD(supportsValueFormattingOptions, "supportsValueFormattingOptions"),
+  DAP_FIELD(supportsWriteMemoryRequest, "supportsWriteMemoryRequest"),
+  DAP_FIELD(cmakeVersion, "cmakeVersion"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(
+  CMakeInitializeRequest, "initialize", DAP_FIELD(adapterID, "adapterID"),
+  DAP_FIELD(clientID, "clientID"), DAP_FIELD(clientName, "clientName"),
+  DAP_FIELD(columnsStartAt1, "columnsStartAt1"),
+  DAP_FIELD(linesStartAt1, "linesStartAt1"), DAP_FIELD(locale, "locale"),
+  DAP_FIELD(pathFormat, "pathFormat"),
+  DAP_FIELD(supportsArgsCanBeInterpretedByShell,
+            "supportsArgsCanBeInterpretedByShell"),
+  DAP_FIELD(supportsInvalidatedEvent, "supportsInvalidatedEvent"),
+  DAP_FIELD(supportsMemoryEvent, "supportsMemoryEvent"),
+  DAP_FIELD(supportsMemoryReferences, "supportsMemoryReferences"),
+  DAP_FIELD(supportsProgressReporting, "supportsProgressReporting"),
+  DAP_FIELD(supportsRunInTerminalRequest, "supportsRunInTerminalRequest"),
+  DAP_FIELD(supportsStartDebuggingRequest, "supportsStartDebuggingRequest"),
+  DAP_FIELD(supportsVariablePaging, "supportsVariablePaging"),
+  DAP_FIELD(supportsVariableType, "supportsVariableType"));
+
+} // namespace dap
diff --git a/Source/cmDebuggerProtocol.h b/Source/cmDebuggerProtocol.h
new file mode 100644
index 0000000..4334aed
--- /dev/null
+++ b/Source/cmDebuggerProtocol.h
@@ -0,0 +1,191 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <vector>
+
+#include <cm3p/cppdap/protocol.h>
+
+#include <cmcppdap/include/dap/optional.h>
+#include <cmcppdap/include/dap/typeof.h>
+#include <cmcppdap/include/dap/types.h>
+
+namespace dap {
+
+// Represents the cmake version.
+struct CMakeVersion : public InitializeResponse
+{
+  // The major version number.
+  integer major;
+  // The minor version number.
+  integer minor;
+  // The patch number.
+  integer patch;
+  // The full version string.
+  string full;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(CMakeVersion);
+
+// Response to `initialize` request.
+struct CMakeInitializeResponse : public Response
+{
+  // The set of additional module information exposed by the debug adapter.
+  optional<array<ColumnDescriptor>> additionalModuleColumns;
+  // The set of characters that should trigger completion in a REPL. If not
+  // specified, the UI should assume the `.` character.
+  optional<array<string>> completionTriggerCharacters;
+  // Available exception filter options for the `setExceptionBreakpoints`
+  // request.
+  optional<array<ExceptionBreakpointsFilter>> exceptionBreakpointFilters;
+  // The debug adapter supports the `suspendDebuggee` attribute on the
+  // `disconnect` request.
+  optional<boolean> supportSuspendDebuggee;
+  // The debug adapter supports the `terminateDebuggee` attribute on the
+  // `disconnect` request.
+  optional<boolean> supportTerminateDebuggee;
+  // Checksum algorithms supported by the debug adapter.
+  optional<array<ChecksumAlgorithm>> supportedChecksumAlgorithms;
+  // The debug adapter supports the `breakpointLocations` request.
+  optional<boolean> supportsBreakpointLocationsRequest;
+  // The debug adapter supports the `cancel` request.
+  optional<boolean> supportsCancelRequest;
+  // The debug adapter supports the `clipboard` context value in the `evaluate`
+  // request.
+  optional<boolean> supportsClipboardContext;
+  // The debug adapter supports the `completions` request.
+  optional<boolean> supportsCompletionsRequest;
+  // The debug adapter supports conditional breakpoints.
+  optional<boolean> supportsConditionalBreakpoints;
+  // The debug adapter supports the `configurationDone` request.
+  optional<boolean> supportsConfigurationDoneRequest;
+  // The debug adapter supports data breakpoints.
+  optional<boolean> supportsDataBreakpoints;
+  // The debug adapter supports the delayed loading of parts of the stack,
+  // which requires that both the `startFrame` and `levels` arguments and the
+  // `totalFrames` result of the `stackTrace` request are supported.
+  optional<boolean> supportsDelayedStackTraceLoading;
+  // The debug adapter supports the `disassemble` request.
+  optional<boolean> supportsDisassembleRequest;
+  // The debug adapter supports a (side effect free) `evaluate` request for
+  // data hovers.
+  optional<boolean> supportsEvaluateForHovers;
+  // The debug adapter supports `filterOptions` as an argument on the
+  // `setExceptionBreakpoints` request.
+  optional<boolean> supportsExceptionFilterOptions;
+  // The debug adapter supports the `exceptionInfo` request.
+  optional<boolean> supportsExceptionInfoRequest;
+  // The debug adapter supports `exceptionOptions` on the
+  // `setExceptionBreakpoints` request.
+  optional<boolean> supportsExceptionOptions;
+  // The debug adapter supports function breakpoints.
+  optional<boolean> supportsFunctionBreakpoints;
+  // The debug adapter supports the `gotoTargets` request.
+  optional<boolean> supportsGotoTargetsRequest;
+  // The debug adapter supports breakpoints that break execution after a
+  // specified number of hits.
+  optional<boolean> supportsHitConditionalBreakpoints;
+  // The debug adapter supports adding breakpoints based on instruction
+  // references.
+  optional<boolean> supportsInstructionBreakpoints;
+  // The debug adapter supports the `loadedSources` request.
+  optional<boolean> supportsLoadedSourcesRequest;
+  // The debug adapter supports log points by interpreting the `logMessage`
+  // attribute of the `SourceBreakpoint`.
+  optional<boolean> supportsLogPoints;
+  // The debug adapter supports the `modules` request.
+  optional<boolean> supportsModulesRequest;
+  // The debug adapter supports the `readMemory` request.
+  optional<boolean> supportsReadMemoryRequest;
+  // The debug adapter supports restarting a frame.
+  optional<boolean> supportsRestartFrame;
+  // The debug adapter supports the `restart` request. In this case a client
+  // should not implement `restart` by terminating and relaunching the adapter
+  // but by calling the `restart` request.
+  optional<boolean> supportsRestartRequest;
+  // The debug adapter supports the `setExpression` request.
+  optional<boolean> supportsSetExpression;
+  // The debug adapter supports setting a variable to a value.
+  optional<boolean> supportsSetVariable;
+  // The debug adapter supports the `singleThread` property on the execution
+  // requests (`continue`, `next`, `stepIn`, `stepOut`, `reverseContinue`,
+  // `stepBack`).
+  optional<boolean> supportsSingleThreadExecutionRequests;
+  // The debug adapter supports stepping back via the `stepBack` and
+  // `reverseContinue` requests.
+  optional<boolean> supportsStepBack;
+  // The debug adapter supports the `stepInTargets` request.
+  optional<boolean> supportsStepInTargetsRequest;
+  // The debug adapter supports stepping granularities (argument `granularity`)
+  // for the stepping requests.
+  optional<boolean> supportsSteppingGranularity;
+  // The debug adapter supports the `terminate` request.
+  optional<boolean> supportsTerminateRequest;
+  // The debug adapter supports the `terminateThreads` request.
+  optional<boolean> supportsTerminateThreadsRequest;
+  // The debug adapter supports a `format` attribute on the `stackTrace`,
+  // `variables`, and `evaluate` requests.
+  optional<boolean> supportsValueFormattingOptions;
+  // The debug adapter supports the `writeMemory` request.
+  optional<boolean> supportsWriteMemoryRequest;
+  // The CMake version.
+  CMakeVersion cmakeVersion;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(CMakeInitializeResponse);
+
+// The `initialize` request is sent as the first request from the client to the
+// debug adapter in order to configure it with client capabilities and to
+// retrieve capabilities from the debug adapter. Until the debug adapter has
+// responded with an `initialize` response, the client must not send any
+// additional requests or events to the debug adapter. In addition the debug
+// adapter is not allowed to send any requests or events to the client until it
+// has responded with an `initialize` response. The `initialize` request may
+// only be sent once.
+struct CMakeInitializeRequest : public Request
+{
+  using Response = CMakeInitializeResponse;
+  // The ID of the debug adapter.
+  string adapterID;
+  // The ID of the client using this adapter.
+  optional<string> clientID;
+  // The human-readable name of the client using this adapter.
+  optional<string> clientName;
+  // If true all column numbers are 1-based (default).
+  optional<boolean> columnsStartAt1;
+  // If true all line numbers are 1-based (default).
+  optional<boolean> linesStartAt1;
+  // The ISO-639 locale of the client using this adapter, e.g. en-US or de-CH.
+  optional<string> locale;
+  // Determines in what format paths are specified. The default is `path`,
+  // which is the native format.
+  //
+  // May be one of the following enumeration values:
+  // 'path', 'uri'
+  optional<string> pathFormat;
+  // Client supports the `argsCanBeInterpretedByShell` attribute on the
+  // `runInTerminal` request.
+  optional<boolean> supportsArgsCanBeInterpretedByShell;
+  // Client supports the `invalidated` event.
+  optional<boolean> supportsInvalidatedEvent;
+  // Client supports the `memory` event.
+  optional<boolean> supportsMemoryEvent;
+  // Client supports memory references.
+  optional<boolean> supportsMemoryReferences;
+  // Client supports progress reporting.
+  optional<boolean> supportsProgressReporting;
+  // Client supports the `runInTerminal` request.
+  optional<boolean> supportsRunInTerminalRequest;
+  // Client supports the `startDebugging` request.
+  optional<boolean> supportsStartDebuggingRequest;
+  // Client supports the paging of variables.
+  optional<boolean> supportsVariablePaging;
+  // Client supports the `type` attribute for variables.
+  optional<boolean> supportsVariableType;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(CMakeInitializeRequest);
+
+} // namespace dap
diff --git a/Source/cmDebuggerSourceBreakpoint.cxx b/Source/cmDebuggerSourceBreakpoint.cxx
new file mode 100644
index 0000000..d4665e6
--- /dev/null
+++ b/Source/cmDebuggerSourceBreakpoint.cxx
@@ -0,0 +1,14 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmDebuggerSourceBreakpoint.h"
+
+namespace cmDebugger {
+
+cmDebuggerSourceBreakpoint::cmDebuggerSourceBreakpoint(int64_t id,
+                                                       int64_t line)
+  : Id(id)
+  , Line(line)
+{
+}
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerSourceBreakpoint.h b/Source/cmDebuggerSourceBreakpoint.h
new file mode 100644
index 0000000..f6d6cac
--- /dev/null
+++ b/Source/cmDebuggerSourceBreakpoint.h
@@ -0,0 +1,26 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <cstdint>
+
+namespace cmDebugger {
+
+class cmDebuggerSourceBreakpoint
+{
+  int64_t Id;
+  int64_t Line;
+  bool IsValid = true;
+
+public:
+  cmDebuggerSourceBreakpoint(int64_t id, int64_t line);
+  int64_t GetId() const noexcept { return this->Id; }
+  int64_t GetLine() const noexcept { return this->Line; }
+  void ChangeLine(int64_t line) noexcept { this->Line = line; }
+  bool GetIsValid() const noexcept { return this->IsValid; }
+  void Invalid() noexcept { this->IsValid = false; }
+};
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerStackFrame.cxx b/Source/cmDebuggerStackFrame.cxx
new file mode 100644
index 0000000..789b0a5
--- /dev/null
+++ b/Source/cmDebuggerStackFrame.cxx
@@ -0,0 +1,28 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmDebuggerStackFrame.h"
+
+#include <utility>
+
+#include "cmListFileCache.h"
+
+namespace cmDebugger {
+
+std::atomic<int64_t> cmDebuggerStackFrame::NextId(1);
+
+cmDebuggerStackFrame::cmDebuggerStackFrame(cmMakefile* mf,
+                                           std::string sourcePath,
+                                           cmListFileFunction const& lff)
+  : Id(NextId.fetch_add(1))
+  , FileName(std::move(sourcePath))
+  , Function(lff)
+  , Makefile(mf)
+{
+}
+
+int64_t cmDebuggerStackFrame::GetLine() const noexcept
+{
+  return this->Function.Line();
+}
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerStackFrame.h b/Source/cmDebuggerStackFrame.h
new file mode 100644
index 0000000..dc3b2ab
--- /dev/null
+++ b/Source/cmDebuggerStackFrame.h
@@ -0,0 +1,33 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <atomic>
+#include <cstdint>
+#include <string>
+
+class cmListFileFunction;
+class cmMakefile;
+
+namespace cmDebugger {
+
+class cmDebuggerStackFrame
+{
+  static std::atomic<std::int64_t> NextId;
+  std::int64_t Id;
+  std::string FileName;
+  cmListFileFunction const& Function;
+  cmMakefile* Makefile;
+
+public:
+  cmDebuggerStackFrame(cmMakefile* mf, std::string sourcePath,
+                       cmListFileFunction const& lff);
+  int64_t GetId() const noexcept { return this->Id; }
+  std::string const& GetFileName() const noexcept { return this->FileName; }
+  int64_t GetLine() const noexcept;
+  cmMakefile* GetMakefile() const noexcept { return this->Makefile; }
+};
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerThread.cxx b/Source/cmDebuggerThread.cxx
new file mode 100644
index 0000000..fd52f5a
--- /dev/null
+++ b/Source/cmDebuggerThread.cxx
@@ -0,0 +1,150 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include "cmDebuggerThread.h"
+
+#include <cstdint>
+#include <utility>
+
+#include <cm3p/cppdap/optional.h>
+#include <cm3p/cppdap/types.h>
+
+#include "cmDebuggerStackFrame.h"
+#include "cmDebuggerVariables.h"
+#include "cmDebuggerVariablesHelper.h"
+#include "cmDebuggerVariablesManager.h"
+
+namespace cmDebugger {
+
+cmDebuggerThread::cmDebuggerThread(int64_t id, std::string name)
+  : Id(id)
+  , Name(std::move(name))
+  , VariablesManager(std::make_shared<cmDebuggerVariablesManager>())
+{
+}
+
+void cmDebuggerThread::PushStackFrame(cmMakefile* mf,
+                                      std::string const& sourcePath,
+                                      cmListFileFunction const& lff)
+{
+  std::unique_lock<std::mutex> lock(Mutex);
+  Frames.emplace_back(
+    std::make_shared<cmDebuggerStackFrame>(mf, sourcePath, lff));
+  FrameMap.insert({ Frames.back()->GetId(), Frames.back() });
+}
+
+void cmDebuggerThread::PopStackFrame()
+{
+  std::unique_lock<std::mutex> lock(Mutex);
+  FrameMap.erase(Frames.back()->GetId());
+  FrameScopes.erase(Frames.back()->GetId());
+  FrameVariables.erase(Frames.back()->GetId());
+  Frames.pop_back();
+}
+
+std::shared_ptr<cmDebuggerStackFrame> cmDebuggerThread::GetTopStackFrame()
+{
+  std::unique_lock<std::mutex> lock(Mutex);
+  if (!Frames.empty()) {
+    return Frames.back();
+  }
+
+  return {};
+}
+
+std::shared_ptr<cmDebuggerStackFrame> cmDebuggerThread::GetStackFrame(
+  int64_t frameId)
+{
+  std::unique_lock<std::mutex> lock(Mutex);
+  auto it = FrameMap.find(frameId);
+
+  if (it == FrameMap.end()) {
+    return {};
+  }
+
+  return it->second;
+}
+
+dap::ScopesResponse cmDebuggerThread::GetScopesResponse(
+  int64_t frameId, bool supportsVariableType)
+{
+  std::unique_lock<std::mutex> lock(Mutex);
+  auto it = FrameScopes.find(frameId);
+
+  if (it != FrameScopes.end()) {
+    dap::ScopesResponse response;
+    response.scopes = it->second;
+    return response;
+  }
+
+  auto it2 = FrameMap.find(frameId);
+  if (it2 == FrameMap.end()) {
+    return dap::ScopesResponse();
+  }
+
+  std::shared_ptr<cmDebuggerStackFrame> frame = it2->second;
+  std::shared_ptr<cmDebuggerVariables> localVariables =
+    cmDebuggerVariablesHelper::Create(VariablesManager, "Locals",
+                                      supportsVariableType, frame);
+
+  FrameVariables[frameId].emplace_back(localVariables);
+
+  dap::Scope scope;
+  scope.name = localVariables->GetName();
+  scope.presentationHint = "locals";
+  scope.variablesReference = localVariables->GetId();
+
+  dap::Source source;
+  source.name = frame->GetFileName();
+  source.path = source.name;
+  scope.source = source;
+
+  FrameScopes[frameId].push_back(scope);
+
+  dap::ScopesResponse response;
+  response.scopes.push_back(scope);
+  return response;
+}
+
+dap::VariablesResponse cmDebuggerThread::GetVariablesResponse(
+  dap::VariablesRequest const& request)
+{
+  std::unique_lock<std::mutex> lock(Mutex);
+  dap::VariablesResponse response;
+  response.variables = VariablesManager->HandleVariablesRequest(request);
+  return response;
+}
+
+dap::StackTraceResponse GetStackTraceResponse(
+  std::shared_ptr<cmDebuggerThread> const& thread)
+{
+  dap::StackTraceResponse response;
+  std::unique_lock<std::mutex> lock(thread->Mutex);
+  for (int i = static_cast<int>(thread->Frames.size()) - 1; i >= 0; --i) {
+    dap::Source source;
+    source.name = thread->Frames[i]->GetFileName();
+    source.path = source.name;
+
+#ifdef __GNUC__
+#  pragma GCC diagnostic push
+#  pragma GCC diagnostic ignored "-Warray-bounds"
+#endif
+    dap::StackFrame stackFrame;
+#ifdef __GNUC__
+#  pragma GCC diagnostic pop
+#endif
+    stackFrame.line = thread->Frames[i]->GetLine();
+    stackFrame.column = 1;
+    stackFrame.name = thread->Frames[i]->GetFileName() + " Line " +
+      std::to_string(stackFrame.line);
+    stackFrame.id = thread->Frames[i]->GetId();
+    stackFrame.source = source;
+
+    response.stackFrames.push_back(stackFrame);
+  }
+
+  response.totalFrames = response.stackFrames.size();
+  return response;
+}
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerThread.h b/Source/cmDebuggerThread.h
new file mode 100644
index 0000000..65ee2cf
--- /dev/null
+++ b/Source/cmDebuggerThread.h
@@ -0,0 +1,59 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <cm3p/cppdap/protocol.h>
+
+class cmListFileFunction;
+class cmMakefile;
+
+namespace cmDebugger {
+class cmDebuggerStackFrame;
+class cmDebuggerVariables;
+class cmDebuggerVariablesManager;
+}
+
+namespace cmDebugger {
+
+class cmDebuggerThread
+{
+  int64_t Id;
+  std::string Name;
+  std::vector<std::shared_ptr<cmDebuggerStackFrame>> Frames;
+  std::unordered_map<int64_t, std::shared_ptr<cmDebuggerStackFrame>> FrameMap;
+  std::mutex Mutex;
+  std::unordered_map<int64_t, std::vector<dap::Scope>> FrameScopes;
+  std::unordered_map<int64_t,
+                     std::vector<std::shared_ptr<cmDebuggerVariables>>>
+    FrameVariables;
+  std::shared_ptr<cmDebuggerVariablesManager> VariablesManager;
+
+public:
+  cmDebuggerThread(int64_t id, std::string name);
+  int64_t GetId() const { return this->Id; }
+  const std::string& GetName() const { return this->Name; }
+  void PushStackFrame(cmMakefile* mf, std::string const& sourcePath,
+                      cmListFileFunction const& lff);
+  void PopStackFrame();
+  std::shared_ptr<cmDebuggerStackFrame> GetTopStackFrame();
+  std::shared_ptr<cmDebuggerStackFrame> GetStackFrame(int64_t frameId);
+  size_t GetStackFrameSize() const { return this->Frames.size(); }
+  dap::ScopesResponse GetScopesResponse(int64_t frameId,
+                                        bool supportsVariableType);
+  dap::VariablesResponse GetVariablesResponse(
+    dap::VariablesRequest const& request);
+  friend dap::StackTraceResponse GetStackTraceResponse(
+    std::shared_ptr<cmDebuggerThread> const& thread);
+};
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerThreadManager.cxx b/Source/cmDebuggerThreadManager.cxx
new file mode 100644
index 0000000..0eb443b
--- /dev/null
+++ b/Source/cmDebuggerThreadManager.cxx
@@ -0,0 +1,47 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include "cmDebuggerThreadManager.h"
+
+#include <algorithm>
+
+#include <cm3p/cppdap/protocol.h>
+
+#include "cmDebuggerThread.h"
+
+namespace cmDebugger {
+
+std::atomic<int64_t> cmDebuggerThreadManager::NextThreadId(1);
+
+std::shared_ptr<cmDebuggerThread> cmDebuggerThreadManager::StartThread(
+  std::string const& name)
+{
+  std::shared_ptr<cmDebuggerThread> thread =
+    std::make_shared<cmDebuggerThread>(
+      cmDebuggerThreadManager::NextThreadId.fetch_add(1), name);
+  Threads.emplace_back(thread);
+  return thread;
+}
+
+void cmDebuggerThreadManager::EndThread(
+  std::shared_ptr<cmDebuggerThread> const& thread)
+{
+  Threads.remove(thread);
+}
+
+cm::optional<dap::StackTraceResponse>
+cmDebuggerThreadManager::GetThreadStackTraceResponse(int64_t id)
+{
+  auto it = find_if(Threads.begin(), Threads.end(),
+                    [&](const std::shared_ptr<cmDebuggerThread>& t) {
+                      return t->GetId() == id;
+                    });
+
+  if (it == Threads.end()) {
+    return {};
+  }
+
+  return GetStackTraceResponse(*it);
+}
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerThreadManager.h b/Source/cmDebuggerThreadManager.h
new file mode 100644
index 0000000..934cf85
--- /dev/null
+++ b/Source/cmDebuggerThreadManager.h
@@ -0,0 +1,38 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <atomic>
+#include <cstdint>
+#include <list>
+#include <memory>
+#include <string>
+
+#include <cm/optional>
+
+namespace cmDebugger {
+class cmDebuggerThread;
+}
+
+namespace dap {
+struct StackTraceResponse;
+}
+
+namespace cmDebugger {
+
+class cmDebuggerThreadManager
+{
+  static std::atomic<std::int64_t> NextThreadId;
+  std::list<std::shared_ptr<cmDebuggerThread>> Threads;
+
+public:
+  cmDebuggerThreadManager() = default;
+  std::shared_ptr<cmDebuggerThread> StartThread(std::string const& name);
+  void EndThread(std::shared_ptr<cmDebuggerThread> const& thread);
+  cm::optional<dap::StackTraceResponse> GetThreadStackTraceResponse(
+    std::int64_t id);
+};
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerVariables.cxx b/Source/cmDebuggerVariables.cxx
new file mode 100644
index 0000000..40fe41f
--- /dev/null
+++ b/Source/cmDebuggerVariables.cxx
@@ -0,0 +1,133 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include "cmDebuggerVariables.h"
+
+#include <algorithm>
+#include <vector>
+
+#include <cm3p/cppdap/optional.h>
+#include <cm3p/cppdap/protocol.h>
+#include <cm3p/cppdap/types.h>
+
+#include "cmDebuggerVariablesManager.h"
+
+namespace cmDebugger {
+
+namespace {
+const dap::VariablePresentationHint PrivatePropertyHint = { {},
+                                                            "property",
+                                                            {},
+                                                            "private" };
+const dap::VariablePresentationHint PrivateDataHint = { {},
+                                                        "data",
+                                                        {},
+                                                        "private" };
+}
+
+std::atomic<int64_t> cmDebuggerVariables::NextId(1);
+
+cmDebuggerVariables::cmDebuggerVariables(
+  std::shared_ptr<cmDebuggerVariablesManager> variablesManager,
+  std::string name, bool supportsVariableType)
+  : Id(NextId.fetch_add(1))
+  , Name(std::move(name))
+  , SupportsVariableType(supportsVariableType)
+  , VariablesManager(std::move(variablesManager))
+{
+  VariablesManager->RegisterHandler(
+    Id, [this](dap::VariablesRequest const& request) {
+      (void)request;
+      return this->HandleVariablesRequest();
+    });
+}
+
+cmDebuggerVariables::cmDebuggerVariables(
+  std::shared_ptr<cmDebuggerVariablesManager> variablesManager,
+  std::string name, bool supportsVariableType,
+  std::function<std::vector<cmDebuggerVariableEntry>()> getKeyValuesFunction)
+  : Id(NextId.fetch_add(1))
+  , Name(std::move(name))
+  , GetKeyValuesFunction(std::move(getKeyValuesFunction))
+  , SupportsVariableType(supportsVariableType)
+  , VariablesManager(std::move(variablesManager))
+{
+  VariablesManager->RegisterHandler(
+    Id, [this](dap::VariablesRequest const& request) {
+      (void)request;
+      return this->HandleVariablesRequest();
+    });
+}
+
+void cmDebuggerVariables::AddSubVariables(
+  std::shared_ptr<cmDebuggerVariables> const& variables)
+{
+  if (variables != nullptr) {
+    SubVariables.emplace_back(variables);
+  }
+}
+
+dap::array<dap::Variable> cmDebuggerVariables::HandleVariablesRequest()
+{
+  dap::array<dap::Variable> variables;
+
+  if (GetKeyValuesFunction != nullptr) {
+    auto values = GetKeyValuesFunction();
+    for (auto const& entry : values) {
+      if (IgnoreEmptyStringEntries && entry.Type == "string" &&
+          entry.Value.empty()) {
+        continue;
+      }
+      variables.push_back(dap::Variable{ {},
+                                         {},
+                                         {},
+                                         entry.Name,
+                                         {},
+                                         PrivateDataHint,
+                                         entry.Type,
+                                         entry.Value,
+                                         0 });
+    }
+  }
+
+  EnumerateSubVariablesIfAny(variables);
+
+  if (EnableSorting) {
+    std::sort(variables.begin(), variables.end(),
+              [](dap::Variable const& a, dap::Variable const& b) {
+                return a.name < b.name;
+              });
+  }
+  return variables;
+}
+
+void cmDebuggerVariables::EnumerateSubVariablesIfAny(
+  dap::array<dap::Variable>& toBeReturned) const
+{
+  dap::array<dap::Variable> ret;
+  for (auto const& variables : SubVariables) {
+    toBeReturned.emplace_back(
+      dap::Variable{ {},
+                     {},
+                     {},
+                     variables->GetName(),
+                     {},
+                     PrivatePropertyHint,
+                     SupportsVariableType ? "collection" : nullptr,
+                     variables->GetValue(),
+                     variables->GetId() });
+  }
+}
+
+void cmDebuggerVariables::ClearSubVariables()
+{
+  SubVariables.clear();
+}
+
+cmDebuggerVariables::~cmDebuggerVariables()
+{
+  ClearSubVariables();
+  VariablesManager->UnregisterHandler(Id);
+}
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerVariables.h b/Source/cmDebuggerVariables.h
new file mode 100644
index 0000000..eaaf2a8
--- /dev/null
+++ b/Source/cmDebuggerVariables.h
@@ -0,0 +1,124 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <atomic>
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <cm3p/cppdap/types.h> // IWYU pragma: keep
+
+namespace cmDebugger {
+class cmDebuggerVariablesManager;
+}
+
+namespace dap {
+struct Variable;
+}
+
+namespace cmDebugger {
+
+struct cmDebuggerVariableEntry
+{
+  cmDebuggerVariableEntry()
+    : cmDebuggerVariableEntry("", "", "")
+  {
+  }
+  cmDebuggerVariableEntry(std::string name, std::string value,
+                          std::string type)
+    : Name(std::move(name))
+    , Value(std::move(value))
+    , Type(std::move(type))
+  {
+  }
+  cmDebuggerVariableEntry(std::string name, std::string value)
+    : Name(std::move(name))
+    , Value(std::move(value))
+    , Type("string")
+  {
+  }
+  cmDebuggerVariableEntry(std::string name, const char* value)
+    : Name(std::move(name))
+    , Value(value == nullptr ? "" : value)
+    , Type("string")
+  {
+  }
+  cmDebuggerVariableEntry(std::string name, bool value)
+    : Name(std::move(name))
+    , Value(value ? "TRUE" : "FALSE")
+    , Type("bool")
+  {
+  }
+  cmDebuggerVariableEntry(std::string name, int64_t value)
+    : Name(std::move(name))
+    , Value(std::to_string(value))
+    , Type("int")
+  {
+  }
+  cmDebuggerVariableEntry(std::string name, int value)
+    : Name(std::move(name))
+    , Value(std::to_string(value))
+    , Type("int")
+  {
+  }
+  std::string const Name;
+  std::string const Value;
+  std::string const Type;
+};
+
+class cmDebuggerVariables
+{
+  static std::atomic<int64_t> NextId;
+  int64_t Id;
+  std::string Name;
+  std::string Value;
+
+  std::function<std::vector<cmDebuggerVariableEntry>()> GetKeyValuesFunction;
+  std::vector<std::shared_ptr<cmDebuggerVariables>> SubVariables;
+  bool IgnoreEmptyStringEntries = false;
+  bool EnableSorting = true;
+
+  virtual dap::array<dap::Variable> HandleVariablesRequest();
+  friend class cmDebuggerVariablesManager;
+
+protected:
+  const bool SupportsVariableType;
+  std::shared_ptr<cmDebuggerVariablesManager> VariablesManager;
+  void EnumerateSubVariablesIfAny(
+    dap::array<dap::Variable>& toBeReturned) const;
+  void ClearSubVariables();
+
+public:
+  cmDebuggerVariables(
+    std::shared_ptr<cmDebuggerVariablesManager> variablesManager,
+    std::string name, bool supportsVariableType);
+  cmDebuggerVariables(
+    std::shared_ptr<cmDebuggerVariablesManager> variablesManager,
+    std::string name, bool supportsVariableType,
+    std::function<std::vector<cmDebuggerVariableEntry>()> getKeyValuesFunc);
+  inline int64_t GetId() const noexcept { return this->Id; }
+  inline std::string GetName() const noexcept { return this->Name; }
+  inline std::string GetValue() const noexcept { return this->Value; }
+  inline void SetValue(std::string const& value) noexcept
+  {
+    this->Value = value;
+  }
+  void AddSubVariables(std::shared_ptr<cmDebuggerVariables> const& variables);
+  inline void SetIgnoreEmptyStringEntries(bool value) noexcept
+  {
+    this->IgnoreEmptyStringEntries = value;
+  }
+  inline void SetEnableSorting(bool value) noexcept
+  {
+    this->EnableSorting = value;
+  }
+  virtual ~cmDebuggerVariables();
+};
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerVariablesHelper.cxx b/Source/cmDebuggerVariablesHelper.cxx
new file mode 100644
index 0000000..42ce5e7
--- /dev/null
+++ b/Source/cmDebuggerVariablesHelper.cxx
@@ -0,0 +1,644 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include "cmDebuggerVariablesHelper.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <functional>
+#include <iomanip>
+#include <map>
+#include <sstream>
+
+#include "cm_codecvt.hxx"
+
+#include "cmDebuggerStackFrame.h"
+#include "cmDebuggerVariables.h"
+#include "cmFileSet.h"
+#include "cmGlobalGenerator.h"
+#include "cmList.h"
+#include "cmListFileCache.h"
+#include "cmMakefile.h"
+#include "cmPropertyMap.h"
+#include "cmState.h"
+#include "cmStateSnapshot.h"
+#include "cmTarget.h"
+#include "cmTest.h"
+#include "cmValue.h"
+#include "cmake.h"
+
+namespace cmDebugger {
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::Create(
+  std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+  std::string const& name, bool supportsVariableType,
+  cmPolicies::PolicyMap const& policyMap)
+{
+  static std::map<cmPolicies::PolicyStatus, std::string> policyStatusString = {
+    { cmPolicies::PolicyStatus::OLD, "OLD" },
+    { cmPolicies::PolicyStatus::WARN, "WARN" },
+    { cmPolicies::PolicyStatus::NEW, "NEW" },
+    { cmPolicies::PolicyStatus::REQUIRED_IF_USED, "REQUIRED_IF_USED" },
+    { cmPolicies::PolicyStatus::REQUIRED_ALWAYS, "REQUIRED_ALWAYS" }
+  };
+
+  return std::make_shared<cmDebuggerVariables>(
+    variablesManager, name, supportsVariableType, [=]() {
+      std::vector<cmDebuggerVariableEntry> ret;
+      ret.reserve(cmPolicies::CMPCOUNT);
+      for (int i = 0; i < cmPolicies::CMPCOUNT; ++i) {
+        if (policyMap.IsDefined(static_cast<cmPolicies::PolicyID>(i))) {
+          auto status = policyMap.Get(static_cast<cmPolicies::PolicyID>(i));
+          std::ostringstream ss;
+          ss << "CMP" << std::setfill('0') << std::setw(4) << i;
+          ret.emplace_back(ss.str(), policyStatusString[status]);
+        }
+      }
+      return ret;
+    });
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny(
+  std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+  std::string const& name, bool supportsVariableType,
+  std::vector<std::pair<std::string, std::string>> const& list)
+{
+  if (list.empty()) {
+    return {};
+  }
+
+  auto listVariables = std::make_shared<cmDebuggerVariables>(
+    variablesManager, name, supportsVariableType, [=]() {
+      std::vector<cmDebuggerVariableEntry> ret;
+      ret.reserve(list.size());
+      for (auto const& kv : list) {
+        ret.emplace_back(kv.first, kv.second);
+      }
+      return ret;
+    });
+
+  listVariables->SetValue(std::to_string(list.size()));
+  return listVariables;
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny(
+  std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+  std::string const& name, bool supportsVariableType,
+  cmBTStringRange const& entries)
+{
+  if (entries.empty()) {
+    return {};
+  }
+
+  auto sourceEntries = std::make_shared<cmDebuggerVariables>(
+    variablesManager, name, supportsVariableType);
+
+  for (auto const& entry : entries) {
+    auto arrayVariables = std::make_shared<cmDebuggerVariables>(
+      variablesManager, entry.Value, supportsVariableType, [=]() {
+        cmList items{ entry.Value };
+        std::vector<cmDebuggerVariableEntry> ret;
+        ret.reserve(items.size());
+        int i = 0;
+        for (std::string const& item : items) {
+          ret.emplace_back("[" + std::to_string(i++) + "]", item);
+        }
+        return ret;
+      });
+    arrayVariables->SetEnableSorting(false);
+    sourceEntries->AddSubVariables(arrayVariables);
+  }
+
+  sourceEntries->SetValue(std::to_string(entries.size()));
+  return sourceEntries;
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny(
+  std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+  std::string const& name, bool supportsVariableType,
+  std::set<std::string> const& values)
+{
+  if (values.empty()) {
+    return {};
+  }
+
+  auto arrayVariables = std::make_shared<cmDebuggerVariables>(
+    variablesManager, name, supportsVariableType, [=]() {
+      std::vector<cmDebuggerVariableEntry> ret;
+      ret.reserve(values.size());
+      int i = 0;
+      for (std::string const& value : values) {
+        ret.emplace_back("[" + std::to_string(i++) + "]", value);
+      }
+      return ret;
+    });
+  arrayVariables->SetValue(std::to_string(values.size()));
+  arrayVariables->SetEnableSorting(false);
+  return arrayVariables;
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny(
+  std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+  std::string const& name, bool supportsVariableType,
+  std::vector<std::string> const& values)
+{
+  if (values.empty()) {
+    return {};
+  }
+
+  auto arrayVariables = std::make_shared<cmDebuggerVariables>(
+    variablesManager, name, supportsVariableType, [=]() {
+      std::vector<cmDebuggerVariableEntry> ret;
+      ret.reserve(values.size());
+      int i = 0;
+      for (std::string const& value : values) {
+        ret.emplace_back("[" + std::to_string(i++) + "]", value);
+      }
+      return ret;
+    });
+
+  arrayVariables->SetValue(std::to_string(values.size()));
+  arrayVariables->SetEnableSorting(false);
+  return arrayVariables;
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny(
+  std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+  std::string const& name, bool supportsVariableType,
+  std::vector<BT<std::string>> const& list)
+{
+  if (list.empty()) {
+    return {};
+  }
+
+  auto variables = std::make_shared<cmDebuggerVariables>(
+    variablesManager, name, supportsVariableType, [=]() {
+      std::vector<cmDebuggerVariableEntry> ret;
+      ret.reserve(list.size());
+      int i = 0;
+      for (auto const& item : list) {
+        ret.emplace_back("[" + std::to_string(i++) + "]", item.Value);
+      }
+
+      return ret;
+    });
+
+  variables->SetValue(std::to_string(list.size()));
+  variables->SetEnableSorting(false);
+  return variables;
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny(
+  std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+  std::string const& name, bool supportsVariableType, cmFileSet* fileSet)
+{
+  if (fileSet == nullptr) {
+    return {};
+  }
+
+  static auto visibilityString = [](cmFileSetVisibility visibility) {
+    switch (visibility) {
+      case cmFileSetVisibility::Private:
+        return "Private";
+      case cmFileSetVisibility::Public:
+        return "Public";
+      case cmFileSetVisibility::Interface:
+        return "Interface";
+      default:
+        return "Unknown";
+    }
+  };
+
+  auto variables = std::make_shared<cmDebuggerVariables>(
+    variablesManager, name, supportsVariableType, [=]() {
+      std::vector<cmDebuggerVariableEntry> ret{
+        { "Name", fileSet->GetName() },
+        { "Type", fileSet->GetType() },
+        { "Visibility", visibilityString(fileSet->GetVisibility()) },
+      };
+
+      return ret;
+    });
+
+  variables->AddSubVariables(CreateIfAny(variablesManager, "Directories",
+                                         supportsVariableType,
+                                         fileSet->GetDirectoryEntries()));
+  variables->AddSubVariables(CreateIfAny(variablesManager, "Files",
+                                         supportsVariableType,
+                                         fileSet->GetFileEntries()));
+  return variables;
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny(
+  std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+  std::string const& name, bool supportsVariableType,
+  std::vector<cmFileSet*> const& fileSets)
+{
+  if (fileSets.empty()) {
+    return {};
+  }
+
+  auto fileSetsVariables = std::make_shared<cmDebuggerVariables>(
+    variablesManager, name, supportsVariableType);
+
+  for (auto const& fileSet : fileSets) {
+    fileSetsVariables->AddSubVariables(CreateIfAny(
+      variablesManager, fileSet->GetName(), supportsVariableType, fileSet));
+  }
+
+  return fileSetsVariables;
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny(
+  std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+  std::string const& name, bool supportsVariableType,
+  std::vector<cmTarget*> const& targets)
+{
+  if (targets.empty()) {
+    return {};
+  }
+
+  auto targetsVariables = std::make_shared<cmDebuggerVariables>(
+    variablesManager, name, supportsVariableType);
+
+  for (auto const& target : targets) {
+    auto targetVariables = std::make_shared<cmDebuggerVariables>(
+      variablesManager, target->GetName(), supportsVariableType, [=]() {
+        std::vector<cmDebuggerVariableEntry> ret = {
+          { "InstallPath", target->GetInstallPath() },
+          { "IsAIX", target->IsAIX() },
+          { "IsAndroidGuiExecutable", target->IsAndroidGuiExecutable() },
+          { "IsAppBundleOnApple", target->IsAppBundleOnApple() },
+          { "IsDLLPlatform", target->IsDLLPlatform() },
+          { "IsExecutableWithExports", target->IsExecutableWithExports() },
+          { "IsFrameworkOnApple", target->IsFrameworkOnApple() },
+          { "IsImported", target->IsImported() },
+          { "IsImportedGloballyVisible", target->IsImportedGloballyVisible() },
+          { "IsPerConfig", target->IsPerConfig() },
+          { "Name", target->GetName() },
+          { "RuntimeInstallPath", target->GetRuntimeInstallPath() },
+          { "Type", cmState::GetTargetTypeName(target->GetType()) },
+        };
+
+        return ret;
+      });
+    targetVariables->SetValue(cmState::GetTargetTypeName(target->GetType()));
+
+    targetVariables->AddSubVariables(Create(variablesManager, "PolicyMap",
+                                            supportsVariableType,
+                                            target->GetPolicyMap()));
+    targetVariables->AddSubVariables(
+      CreateIfAny(variablesManager, "Properties", supportsVariableType,
+                  target->GetProperties().GetList()));
+
+    targetVariables->AddSubVariables(
+      CreateIfAny(variablesManager, "IncludeDirectories", supportsVariableType,
+                  target->GetIncludeDirectoriesEntries()));
+    targetVariables->AddSubVariables(CreateIfAny(variablesManager, "Sources",
+                                                 supportsVariableType,
+                                                 target->GetSourceEntries()));
+    targetVariables->AddSubVariables(
+      CreateIfAny(variablesManager, "CompileDefinitions", supportsVariableType,
+                  target->GetCompileDefinitionsEntries()));
+    targetVariables->AddSubVariables(
+      CreateIfAny(variablesManager, "CompileFeatures", supportsVariableType,
+                  target->GetCompileFeaturesEntries()));
+    targetVariables->AddSubVariables(
+      CreateIfAny(variablesManager, "CompileOptions", supportsVariableType,
+                  target->GetCompileOptionsEntries()));
+    targetVariables->AddSubVariables(CreateIfAny(
+      variablesManager, "CxxModuleHeaderSets", supportsVariableType,
+      target->GetCxxModuleHeaderSetsEntries()));
+    targetVariables->AddSubVariables(
+      CreateIfAny(variablesManager, "CxxModuleSets", supportsVariableType,
+                  target->GetCxxModuleSetsEntries()));
+    targetVariables->AddSubVariables(
+      CreateIfAny(variablesManager, "HeaderSets", supportsVariableType,
+                  target->GetHeaderSetsEntries()));
+    targetVariables->AddSubVariables(CreateIfAny(
+      variablesManager, "InterfaceCxxModuleHeaderSets", supportsVariableType,
+      target->GetInterfaceCxxModuleHeaderSetsEntries()));
+    targetVariables->AddSubVariables(CreateIfAny(
+      variablesManager, "InterfaceHeaderSets", supportsVariableType,
+      target->GetInterfaceHeaderSetsEntries()));
+    targetVariables->AddSubVariables(
+      CreateIfAny(variablesManager, "LinkDirectories", supportsVariableType,
+                  target->GetLinkDirectoriesEntries()));
+    targetVariables->AddSubVariables(CreateIfAny(
+      variablesManager, "LinkImplementations", supportsVariableType,
+      target->GetLinkImplementationEntries()));
+    targetVariables->AddSubVariables(CreateIfAny(
+      variablesManager, "LinkInterfaceDirects", supportsVariableType,
+      target->GetLinkInterfaceDirectEntries()));
+    targetVariables->AddSubVariables(CreateIfAny(
+      variablesManager, "LinkInterfaceDirectExcludes", supportsVariableType,
+      target->GetLinkInterfaceDirectExcludeEntries()));
+    targetVariables->AddSubVariables(
+      CreateIfAny(variablesManager, "LinkInterfaces", supportsVariableType,
+                  target->GetLinkInterfaceEntries()));
+    targetVariables->AddSubVariables(
+      CreateIfAny(variablesManager, "LinkOptions", supportsVariableType,
+                  target->GetLinkOptionsEntries()));
+    targetVariables->AddSubVariables(CreateIfAny(
+      variablesManager, "SystemIncludeDirectories", supportsVariableType,
+      target->GetSystemIncludeDirectories()));
+    targetVariables->AddSubVariables(CreateIfAny(variablesManager, "Makefile",
+                                                 supportsVariableType,
+                                                 target->GetMakefile()));
+    targetVariables->AddSubVariables(
+      CreateIfAny(variablesManager, "GlobalGenerator", supportsVariableType,
+                  target->GetGlobalGenerator()));
+
+    std::vector<cmFileSet*> allFileSets;
+    auto allFileSetNames = target->GetAllFileSetNames();
+    allFileSets.reserve(allFileSetNames.size());
+    for (auto const& fileSetName : allFileSetNames) {
+      allFileSets.emplace_back(target->GetFileSet(fileSetName));
+    }
+    targetVariables->AddSubVariables(CreateIfAny(
+      variablesManager, "AllFileSets", supportsVariableType, allFileSets));
+
+    std::vector<cmFileSet*> allInterfaceFileSets;
+    auto allInterfaceFileSetNames = target->GetAllInterfaceFileSets();
+    allInterfaceFileSets.reserve(allInterfaceFileSetNames.size());
+    for (auto const& interfaceFileSetName : allInterfaceFileSetNames) {
+      allInterfaceFileSets.emplace_back(
+        target->GetFileSet(interfaceFileSetName));
+    }
+    targetVariables->AddSubVariables(
+      CreateIfAny(variablesManager, "AllInterfaceFileSets",
+                  supportsVariableType, allInterfaceFileSets));
+
+    targetVariables->SetIgnoreEmptyStringEntries(true);
+    targetsVariables->AddSubVariables(targetVariables);
+  }
+
+  targetsVariables->SetValue(std::to_string(targets.size()));
+  return targetsVariables;
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::Create(
+  std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+  std::string const& name, bool supportsVariableType,
+  std::shared_ptr<cmDebuggerStackFrame> const& frame)
+{
+  auto variables = std::make_shared<cmDebuggerVariables>(
+    variablesManager, name, supportsVariableType, [=]() {
+      return std::vector<cmDebuggerVariableEntry>{ { "CurrentLine",
+                                                     frame->GetLine() } };
+    });
+
+  auto closureKeys = frame->GetMakefile()->GetStateSnapshot().ClosureKeys();
+  auto locals = std::make_shared<cmDebuggerVariables>(
+    variablesManager, "Locals", supportsVariableType, [=]() {
+      std::vector<cmDebuggerVariableEntry> ret;
+      ret.reserve(closureKeys.size());
+      for (auto const& key : closureKeys) {
+        ret.emplace_back(
+          key, frame->GetMakefile()->GetStateSnapshot().GetDefinition(key));
+      }
+      return ret;
+    });
+  locals->SetValue(std::to_string(closureKeys.size()));
+  variables->AddSubVariables(locals);
+
+  std::function<bool(std::string const&)> isDirectory =
+    [](std::string const& key) {
+      size_t pos1 = key.rfind("_DIR");
+      size_t pos2 = key.rfind("_DIRECTORY");
+      return !((pos1 == std::string::npos || pos1 != key.size() - 4) &&
+               (pos2 == std::string::npos || pos2 != key.size() - 10));
+    };
+  auto directorySize =
+    std::count_if(closureKeys.begin(), closureKeys.end(), isDirectory);
+  auto directories = std::make_shared<cmDebuggerVariables>(
+    variablesManager, "Directories", supportsVariableType, [=]() {
+      std::vector<cmDebuggerVariableEntry> ret;
+      ret.reserve(directorySize);
+      for (auto const& key : closureKeys) {
+        if (isDirectory(key)) {
+          ret.emplace_back(
+            key, frame->GetMakefile()->GetStateSnapshot().GetDefinition(key));
+        }
+      }
+      return ret;
+    });
+  directories->SetValue(std::to_string(directorySize));
+  variables->AddSubVariables(directories);
+
+  auto cacheVariables = std::make_shared<cmDebuggerVariables>(
+    variablesManager, "CacheVariables", supportsVariableType);
+  auto* state = frame->GetMakefile()->GetCMakeInstance()->GetState();
+  auto keys = state->GetCacheEntryKeys();
+  for (auto const& key : keys) {
+    auto entry = std::make_shared<cmDebuggerVariables>(
+      variablesManager,
+      key + ":" +
+        cmState::CacheEntryTypeToString(state->GetCacheEntryType(key)),
+      supportsVariableType, [=]() {
+        std::vector<cmDebuggerVariableEntry> ret;
+        auto properties = state->GetCacheEntryPropertyList(key);
+        ret.reserve(properties.size() + 2);
+        for (auto const& propertyName : properties) {
+          ret.emplace_back(propertyName,
+                           state->GetCacheEntryProperty(key, propertyName));
+        }
+
+        ret.emplace_back(
+          "TYPE",
+          cmState::CacheEntryTypeToString(state->GetCacheEntryType(key)));
+        ret.emplace_back("VALUE", state->GetCacheEntryValue(key));
+        return ret;
+      });
+
+    entry->SetValue(state->GetCacheEntryValue(key));
+    cacheVariables->AddSubVariables(entry);
+  }
+
+  cacheVariables->SetValue(std::to_string(keys.size()));
+  variables->AddSubVariables(cacheVariables);
+
+  auto targetVariables =
+    CreateIfAny(variablesManager, "Targets", supportsVariableType,
+                frame->GetMakefile()->GetOrderedTargets());
+
+  variables->AddSubVariables(targetVariables);
+  std::vector<cmTest*> tests;
+  frame->GetMakefile()->GetTests(
+    frame->GetMakefile()->GetDefaultConfiguration(), tests);
+  variables->AddSubVariables(
+    CreateIfAny(variablesManager, "Tests", supportsVariableType, tests));
+
+  return variables;
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny(
+  std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+  std::string const& name, bool supportsVariableType, cmTest* test)
+{
+  if (test == nullptr) {
+    return {};
+  }
+
+  auto variables = std::make_shared<cmDebuggerVariables>(
+    variablesManager, name, supportsVariableType, [=]() {
+      std::vector<cmDebuggerVariableEntry> ret{
+        { "CommandExpandLists", test->GetCommandExpandLists() },
+        { "Name", test->GetName() },
+        { "OldStyle", test->GetOldStyle() },
+      };
+
+      return ret;
+    });
+
+  variables->AddSubVariables(CreateIfAny(
+    variablesManager, "Command", supportsVariableType, test->GetCommand()));
+
+  variables->AddSubVariables(CreateIfAny(variablesManager, "Properties",
+                                         supportsVariableType,
+                                         test->GetProperties().GetList()));
+  return variables;
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny(
+  std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+  std::string const& name, bool supportsVariableType,
+  std::vector<cmTest*> const& tests)
+{
+  if (tests.empty()) {
+    return {};
+  }
+
+  auto variables = std::make_shared<cmDebuggerVariables>(
+    variablesManager, name, supportsVariableType);
+
+  for (auto const& test : tests) {
+    variables->AddSubVariables(CreateIfAny(variablesManager, test->GetName(),
+                                           supportsVariableType, test));
+  }
+  variables->SetValue(std::to_string(tests.size()));
+  return variables;
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny(
+  std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+  std::string const& name, bool supportsVariableType, cmMakefile* mf)
+{
+  if (mf == nullptr) {
+    return {};
+  }
+
+  auto AppleSDKTypeString = [&](cmMakefile::AppleSDK sdk) {
+    switch (sdk) {
+      case cmMakefile::AppleSDK::MacOS:
+        return "MacOS";
+      case cmMakefile::AppleSDK::IPhoneOS:
+        return "IPhoneOS";
+      case cmMakefile::AppleSDK::IPhoneSimulator:
+        return "IPhoneSimulator";
+      case cmMakefile::AppleSDK::AppleTVOS:
+        return "AppleTVOS";
+      case cmMakefile::AppleSDK::AppleTVSimulator:
+        return "AppleTVSimulator";
+      default:
+        return "Unknown";
+    }
+  };
+
+  auto variables = std::make_shared<cmDebuggerVariables>(
+    variablesManager, name, supportsVariableType, [=]() {
+      std::vector<cmDebuggerVariableEntry> ret = {
+        { "DefineFlags", mf->GetDefineFlags() },
+        { "DirectoryId", mf->GetDirectoryId().String },
+        { "IsRootMakefile", mf->IsRootMakefile() },
+        { "HomeDirectory", mf->GetHomeDirectory() },
+        { "HomeOutputDirectory", mf->GetHomeOutputDirectory() },
+        { "CurrentSourceDirectory", mf->GetCurrentSourceDirectory() },
+        { "CurrentBinaryDirectory", mf->GetCurrentBinaryDirectory() },
+        { "PlatformIs32Bit", mf->PlatformIs32Bit() },
+        { "PlatformIs64Bit", mf->PlatformIs64Bit() },
+        { "PlatformIsx32", mf->PlatformIsx32() },
+        { "AppleSDKType", AppleSDKTypeString(mf->GetAppleSDKType()) },
+        { "PlatformIsAppleEmbedded", mf->PlatformIsAppleEmbedded() }
+      };
+
+      return ret;
+    });
+
+  variables->AddSubVariables(CreateIfAny(
+    variablesManager, "ListFiles", supportsVariableType, mf->GetListFiles()));
+  variables->AddSubVariables(CreateIfAny(variablesManager, "OutputFiles",
+                                         supportsVariableType,
+                                         mf->GetOutputFiles()));
+
+  variables->SetIgnoreEmptyStringEntries(true);
+  variables->SetValue(mf->GetDirectoryId().String);
+  return variables;
+}
+
+std::shared_ptr<cmDebuggerVariables> cmDebuggerVariablesHelper::CreateIfAny(
+  std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+  std::string const& name, bool supportsVariableType, cmGlobalGenerator* gen)
+{
+  if (gen == nullptr) {
+    return {};
+  }
+
+  auto makeFileEncodingString = [](codecvt::Encoding encoding) {
+    switch (encoding) {
+      case codecvt::Encoding::None:
+        return "None";
+      case codecvt::Encoding::UTF8:
+        return "UTF8";
+      case codecvt::Encoding::UTF8_WITH_BOM:
+        return "UTF8_WITH_BOM";
+      case codecvt::Encoding::ANSI:
+        return "ANSI";
+      case codecvt::Encoding::ConsoleOutput:
+        return "ConsoleOutput";
+      default:
+        return "Unknown";
+    }
+  };
+
+  auto variables = std::make_shared<cmDebuggerVariables>(
+    variablesManager, name, supportsVariableType, [=]() {
+      std::vector<cmDebuggerVariableEntry> ret = {
+        { "AllTargetName", gen->GetAllTargetName() },
+        { "CleanTargetName", gen->GetCleanTargetName() },
+        { "EditCacheCommand", gen->GetEditCacheCommand() },
+        { "EditCacheTargetName", gen->GetEditCacheTargetName() },
+        { "ExtraGeneratorName", gen->GetExtraGeneratorName() },
+        { "ForceUnixPaths", gen->GetForceUnixPaths() },
+        { "InstallLocalTargetName", gen->GetInstallLocalTargetName() },
+        { "InstallStripTargetName", gen->GetInstallStripTargetName() },
+        { "InstallTargetName", gen->GetInstallTargetName() },
+        { "IsMultiConfig", gen->IsMultiConfig() },
+        { "Name", gen->GetName() },
+        { "MakefileEncoding",
+          makeFileEncodingString(gen->GetMakefileEncoding()) },
+        { "PackageSourceTargetName", gen->GetPackageSourceTargetName() },
+        { "PackageTargetName", gen->GetPackageTargetName() },
+        { "PreinstallTargetName", gen->GetPreinstallTargetName() },
+        { "NeedSymbolicMark", gen->GetNeedSymbolicMark() },
+        { "RebuildCacheTargetName", gen->GetRebuildCacheTargetName() },
+        { "TestTargetName", gen->GetTestTargetName() },
+        { "UseLinkScript", gen->GetUseLinkScript() },
+      };
+
+      return ret;
+    });
+
+  if (gen->GetInstallComponents() != nullptr) {
+    variables->AddSubVariables(
+      CreateIfAny(variablesManager, "InstallComponents", supportsVariableType,
+                  *gen->GetInstallComponents()));
+  }
+
+  variables->SetIgnoreEmptyStringEntries(true);
+  variables->SetValue(gen->GetName());
+
+  return variables;
+}
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerVariablesHelper.h b/Source/cmDebuggerVariablesHelper.h
new file mode 100644
index 0000000..9b11eaf
--- /dev/null
+++ b/Source/cmDebuggerVariablesHelper.h
@@ -0,0 +1,106 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "cmAlgorithms.h"
+#include "cmPolicies.h"
+
+class cmFileSet;
+class cmGlobalGenerator;
+class cmMakefile;
+class cmTarget;
+class cmTest;
+
+namespace cmDebugger {
+class cmDebuggerStackFrame;
+class cmDebuggerVariables;
+class cmDebuggerVariablesManager;
+}
+
+template <typename T>
+class BT;
+
+namespace cmDebugger {
+
+class cmDebuggerVariablesHelper
+{
+  cmDebuggerVariablesHelper() = default;
+
+public:
+  static std::shared_ptr<cmDebuggerVariables> Create(
+    std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+    std::string const& name, bool supportsVariableType,
+    cmPolicies::PolicyMap const& policyMap);
+
+  static std::shared_ptr<cmDebuggerVariables> CreateIfAny(
+    std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+    std::string const& name, bool supportsVariableType,
+    std::vector<std::pair<std::string, std::string>> const& list);
+
+  static std::shared_ptr<cmDebuggerVariables> CreateIfAny(
+    std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+    std::string const& name, bool supportsVariableType,
+    cmBTStringRange const& entries);
+
+  static std::shared_ptr<cmDebuggerVariables> CreateIfAny(
+    std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+    std::string const& name, bool supportsVariableType,
+    std::set<std::string> const& values);
+
+  static std::shared_ptr<cmDebuggerVariables> CreateIfAny(
+    std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+    std::string const& name, bool supportsVariableType,
+    std::vector<std::string> const& values);
+
+  static std::shared_ptr<cmDebuggerVariables> CreateIfAny(
+    std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+    std::string const& name, bool supportsVariableType,
+    std::vector<BT<std::string>> const& list);
+
+  static std::shared_ptr<cmDebuggerVariables> CreateIfAny(
+    std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+    std::string const& name, bool supportsVariableType, cmFileSet* fileSet);
+
+  static std::shared_ptr<cmDebuggerVariables> CreateIfAny(
+    std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+    std::string const& name, bool supportsVariableType,
+    std::vector<cmFileSet*> const& fileSets);
+
+  static std::shared_ptr<cmDebuggerVariables> CreateIfAny(
+    std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+    std::string const& name, bool supportsVariableType, cmTest* test);
+
+  static std::shared_ptr<cmDebuggerVariables> CreateIfAny(
+    std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+    std::string const& name, bool supportsVariableType,
+    std::vector<cmTest*> const& tests);
+
+  static std::shared_ptr<cmDebuggerVariables> CreateIfAny(
+    std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+    std::string const& name, bool supportsVariableType,
+    std::vector<cmTarget*> const& targets);
+
+  static std::shared_ptr<cmDebuggerVariables> Create(
+    std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+    std::string const& name, bool supportsVariableType,
+    std::shared_ptr<cmDebuggerStackFrame> const& frame);
+
+  static std::shared_ptr<cmDebuggerVariables> CreateIfAny(
+    std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+    std::string const& name, bool supportsVariableType, cmMakefile* mf);
+
+  static std::shared_ptr<cmDebuggerVariables> CreateIfAny(
+    std::shared_ptr<cmDebuggerVariablesManager> const& variablesManager,
+    std::string const& name, bool supportsVariableType,
+    cmGlobalGenerator* gen);
+};
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerVariablesManager.cxx b/Source/cmDebuggerVariablesManager.cxx
new file mode 100644
index 0000000..9b9b476
--- /dev/null
+++ b/Source/cmDebuggerVariablesManager.cxx
@@ -0,0 +1,38 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include "cmDebuggerVariablesManager.h"
+
+#include <utility>
+
+#include <cm3p/cppdap/protocol.h>
+#include <cm3p/cppdap/types.h>
+
+namespace cmDebugger {
+
+void cmDebuggerVariablesManager::RegisterHandler(
+  int64_t id,
+  std::function<dap::array<dap::Variable>(dap::VariablesRequest const&)>
+    handler)
+{
+  VariablesHandlers[id] = std::move(handler);
+}
+
+void cmDebuggerVariablesManager::UnregisterHandler(int64_t id)
+{
+  VariablesHandlers.erase(id);
+}
+
+dap::array<dap::Variable> cmDebuggerVariablesManager::HandleVariablesRequest(
+  dap::VariablesRequest const& request)
+{
+  auto it = VariablesHandlers.find(request.variablesReference);
+
+  if (it != VariablesHandlers.end()) {
+    return it->second(request);
+  }
+
+  return dap::array<dap::Variable>();
+}
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerVariablesManager.h b/Source/cmDebuggerVariablesManager.h
new file mode 100644
index 0000000..c219164
--- /dev/null
+++ b/Source/cmDebuggerVariablesManager.h
@@ -0,0 +1,40 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <cstdint>
+#include <functional>
+#include <unordered_map>
+#include <vector>
+
+#include <cm3p/cppdap/types.h> // IWYU pragma: keep
+
+namespace dap {
+struct Variable;
+struct VariablesRequest;
+}
+
+namespace cmDebugger {
+
+class cmDebuggerVariablesManager
+{
+  std::unordered_map<
+    int64_t,
+    std::function<dap::array<dap::Variable>(dap::VariablesRequest const&)>>
+    VariablesHandlers;
+  void RegisterHandler(
+    int64_t id,
+    std::function<dap::array<dap::Variable>(dap::VariablesRequest const&)>
+      handler);
+  void UnregisterHandler(int64_t id);
+  friend class cmDebuggerVariables;
+
+public:
+  cmDebuggerVariablesManager() = default;
+  dap::array<dap::Variable> HandleVariablesRequest(
+    dap::VariablesRequest const& request);
+};
+
+} // namespace cmDebugger
diff --git a/Source/cmDepends.cxx b/Source/cmDepends.cxx
index eca1abd..04bccce 100644
--- a/Source/cmDepends.cxx
+++ b/Source/cmDepends.cxx
@@ -9,6 +9,7 @@
 #include "cmFileTime.h"
 #include "cmFileTimeCache.h"
 #include "cmGeneratedFileStream.h"
+#include "cmList.h"
 #include "cmLocalUnixMakefileGenerator3.h"
 #include "cmMakefile.h"
 #include "cmStringAlgorithms.h"
@@ -28,11 +29,11 @@
   std::map<std::string, std::set<std::string>> dependencies;
   {
     // Lookup the set of sources to scan.
-    std::vector<std::string> pairs;
+    cmList pairs;
     {
       std::string const srcLang = "CMAKE_DEPENDS_CHECK_" + this->Language;
       cmMakefile* mf = this->LocalGenerator->GetMakefile();
-      cmExpandList(mf->GetSafeDefinition(srcLang), pairs);
+      pairs.assign(mf->GetSafeDefinition(srcLang));
     }
     for (auto si = pairs.begin(); si != pairs.end();) {
       // Get the source and object file.
diff --git a/Source/cmDependsC.cxx b/Source/cmDependsC.cxx
index 2527809..408a85b 100644
--- a/Source/cmDependsC.cxx
+++ b/Source/cmDependsC.cxx
@@ -8,6 +8,7 @@
 
 #include "cmFileTime.h"
 #include "cmGlobalUnixMakefileGenerator3.h"
+#include "cmList.h"
 #include "cmLocalUnixMakefileGenerator3.h"
 #include "cmMakefile.h"
 #include "cmStringAlgorithms.h"
@@ -393,10 +394,10 @@
 void cmDependsC::SetupTransforms()
 {
   // Get the transformation rules.
-  std::vector<std::string> transformRules;
   cmMakefile* mf = this->LocalGenerator->GetMakefile();
-  mf->GetDefExpandList("CMAKE_INCLUDE_TRANSFORMS", transformRules, true);
-  for (std::string const& tr : transformRules) {
+  cmList transformRules{ mf->GetDefinition("CMAKE_INCLUDE_TRANSFORMS"),
+                         cmList::EmptyElements::Yes };
+  for (auto const& tr : transformRules) {
     this->ParseTransform(tr);
   }
 
diff --git a/Source/cmDependsCompiler.cxx b/Source/cmDependsCompiler.cxx
index 0cc4946..c8061c3 100644
--- a/Source/cmDependsCompiler.cxx
+++ b/Source/cmDependsCompiler.cxx
@@ -128,7 +128,7 @@
           }
 
           std::string line;
-          if (!isValidPath) {
+          if (!isValidPath && !source.empty()) {
             // insert source as first dependency
             depends.push_back(source);
           }
@@ -158,14 +158,16 @@
           }
 
           // ensure source file is the first dependency
-          if (depends.front() != source) {
-            cm::erase(depends, source);
-            if (!isValidPath) {
-              depends.insert(depends.begin(), source);
+          if (!source.empty()) {
+            if (depends.front() != source) {
+              cm::erase(depends, source);
+              if (!isValidPath) {
+                depends.insert(depends.begin(), source);
+              }
+            } else if (isValidPath) {
+              // remove first dependency because it must not be filtered out
+              depends.erase(depends.begin());
             }
-          } else if (isValidPath) {
-            // remove first dependency because it must not be filtered out
-            depends.erase(depends.begin());
           }
         } else {
           // unknown format, ignore it
@@ -174,8 +176,10 @@
 
         if (isValidPath) {
           cm::erase_if(depends, isValidPath);
-          // insert source as first dependency
-          depends.insert(depends.begin(), source);
+          if (!source.empty()) {
+            // insert source as first dependency
+            depends.insert(depends.begin(), source);
+          }
         }
 
         dependencies[target] = std::move(depends);
diff --git a/Source/cmDependsFortran.cxx b/Source/cmDependsFortran.cxx
index 718097f..d038db7 100644
--- a/Source/cmDependsFortran.cxx
+++ b/Source/cmDependsFortran.cxx
@@ -13,6 +13,7 @@
 #include "cmFortranParser.h" /* Interface to parser object.  */
 #include "cmGeneratedFileStream.h"
 #include "cmGlobalUnixMakefileGenerator3.h"
+#include "cmList.h"
 #include "cmLocalUnixMakefileGenerator3.h"
 #include "cmMakefile.h"
 #include "cmOutputConverter.h"
@@ -78,9 +79,8 @@
   this->SetIncludePathFromLanguage("Fortran");
 
   // Get the list of definitions.
-  std::vector<std::string> definitions;
   cmMakefile* mf = this->LocalGenerator->GetMakefile();
-  mf->GetDefExpandList("CMAKE_TARGET_DEFINITIONS_Fortran", definitions);
+  cmList definitions{ mf->GetDefinition("CMAKE_TARGET_DEFINITIONS_Fortran") };
 
   // translate i.e. FOO=BAR to FOO and add it to the list of defined
   // preprocessor symbols
@@ -244,9 +244,9 @@
 
   // Load information about other targets.
   cmMakefile* mf = this->LocalGenerator->GetMakefile();
-  std::vector<std::string> infoFiles;
-  mf->GetDefExpandList("CMAKE_Fortran_TARGET_LINKED_INFO_FILES", infoFiles);
-  for (std::string const& i : infoFiles) {
+  cmList infoFiles{ mf->GetDefinition(
+    "CMAKE_Fortran_TARGET_LINKED_INFO_FILES") };
+  for (auto const& i : infoFiles) {
     std::string targetDir = cmSystemTools::GetFilenamePath(i);
     std::string fname = targetDir + "/fortran.internal";
     cmsys::ifstream fin(fname.c_str());
@@ -416,7 +416,7 @@
       // file is not updated. In such case the stamp file will be always
       // older than its prerequisite and trigger cmake_copy_f90_mod
       // on each new build. This is expected behavior for incremental
-      // builds and can not be changed without preforming recursive make
+      // builds and can not be changed without performing recursive make
       // calls that would considerably slow down the building process.
       makeDepends << stampFileForMake << ": " << obj_m << '\n';
       makeDepends << "\t$(CMAKE_COMMAND) -E cmake_copy_f90_mod " << modFile
diff --git a/Source/cmDependsJavaParserHelper.cxx b/Source/cmDependsJavaParserHelper.cxx
index 0c5d310..6e617f6 100644
--- a/Source/cmDependsJavaParserHelper.cxx
+++ b/Source/cmDependsJavaParserHelper.cxx
@@ -155,7 +155,7 @@
 void cmDependsJavaParserHelper::PrepareElement(
   cmDependsJavaParserHelper::ParserType* me)
 {
-  // Inititalize self
+  // Initialize self
   me->str = nullptr;
 }
 
diff --git a/Source/cmDyndepCollation.cxx b/Source/cmDyndepCollation.cxx
index 53a262b..80e1357 100644
--- a/Source/cmDyndepCollation.cxx
+++ b/Source/cmDyndepCollation.cxx
@@ -358,6 +358,10 @@
       fsi.Name = tdi_cxx_module_info["name"].asString();
       fsi.RelativeDirectory =
         tdi_cxx_module_info["relative-directory"].asString();
+      if (!fsi.RelativeDirectory.empty() &&
+          fsi.RelativeDirectory.back() != '/') {
+        fsi.RelativeDirectory = cmStrCat(fsi.RelativeDirectory, '/');
+      }
       fsi.SourcePath = tdi_cxx_module_info["source"].asString();
       fsi.Type = tdi_cxx_module_info["type"].asString();
       fsi.Visibility = cmFileSetVisibilityFromName(
@@ -623,3 +627,20 @@
 
   return result;
 }
+
+bool cmDyndepCollation::IsObjectPrivate(
+  std::string const& object, cmCxxModuleExportInfo const& export_info)
+{
+#ifdef _WIN32
+  std::string output_path = object;
+  cmSystemTools::ConvertToUnixSlashes(output_path);
+#else
+  std::string const& output_path = object;
+#endif
+  auto fileset_info_itr = export_info.ObjectToFileSet.find(output_path);
+  if (fileset_info_itr == export_info.ObjectToFileSet.end()) {
+    return false;
+  }
+  auto const& file_set = fileset_info_itr->second;
+  return !cmFileSetVisibilityIsForInterface(file_set.Visibility);
+}
diff --git a/Source/cmDyndepCollation.h b/Source/cmDyndepCollation.h
index e70ac09..48afe2b 100644
--- a/Source/cmDyndepCollation.h
+++ b/Source/cmDyndepCollation.h
@@ -49,4 +49,6 @@
                                   std::vector<cmScanDepInfo> const& objects,
                                   cmCxxModuleExportInfo const& export_info,
                                   cmDyndepMetadataCallbacks const& cb);
+  static bool IsObjectPrivate(std::string const& object,
+                              cmCxxModuleExportInfo const& export_info);
 };
diff --git a/Source/cmEvaluatedTargetProperty.cxx b/Source/cmEvaluatedTargetProperty.cxx
new file mode 100644
index 0000000..b82c29b
--- /dev/null
+++ b/Source/cmEvaluatedTargetProperty.cxx
@@ -0,0 +1,111 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmEvaluatedTargetProperty.h"
+
+#include <unordered_map>
+#include <utility>
+
+#include "cmGeneratorExpressionContext.h"
+#include "cmGeneratorTarget.h"
+#include "cmLinkItem.h"
+#include "cmList.h"
+
+struct cmGeneratorExpressionDAGChecker;
+
+EvaluatedTargetPropertyEntry::EvaluatedTargetPropertyEntry(
+  cmLinkImplItem const& item, cmListFileBacktrace bt)
+  : LinkImplItem(item)
+  , Backtrace(std::move(bt))
+{
+}
+
+EvaluatedTargetPropertyEntry EvaluateTargetPropertyEntry(
+  cmGeneratorTarget const* thisTarget, std::string const& config,
+  std::string const& lang, cmGeneratorExpressionDAGChecker* dagChecker,
+  cmGeneratorTarget::TargetPropertyEntry& entry)
+{
+  EvaluatedTargetPropertyEntry ee(entry.LinkImplItem, entry.GetBacktrace());
+  cmExpandList(entry.Evaluate(thisTarget->GetLocalGenerator(), config,
+                              thisTarget, dagChecker, lang),
+               ee.Values);
+  if (entry.GetHadContextSensitiveCondition()) {
+    ee.ContextDependent = true;
+  }
+  return ee;
+}
+
+EvaluatedTargetPropertyEntries EvaluateTargetPropertyEntries(
+  cmGeneratorTarget const* thisTarget, std::string const& config,
+  std::string const& lang, cmGeneratorExpressionDAGChecker* dagChecker,
+  std::vector<std::unique_ptr<cmGeneratorTarget::TargetPropertyEntry>> const&
+    in)
+{
+  EvaluatedTargetPropertyEntries out;
+  out.Entries.reserve(in.size());
+  for (auto const& entry : in) {
+    out.Entries.emplace_back(EvaluateTargetPropertyEntry(
+      thisTarget, config, lang, dagChecker, *entry));
+  }
+  return out;
+}
+
+namespace {
+void addInterfaceEntry(cmGeneratorTarget const* headTarget,
+                       std::string const& config, std::string const& prop,
+                       std::string const& lang,
+                       cmGeneratorExpressionDAGChecker* dagChecker,
+                       EvaluatedTargetPropertyEntries& entries,
+                       cmGeneratorTarget::LinkInterfaceFor interfaceFor,
+                       std::vector<cmLinkImplItem> const& libraries)
+{
+  for (cmLinkImplItem const& lib : libraries) {
+    if (lib.Target) {
+      EvaluatedTargetPropertyEntry ee(lib, lib.Backtrace);
+      // Pretend $<TARGET_PROPERTY:lib.Target,prop> appeared in our
+      // caller's property and hand-evaluate it as if it were compiled.
+      // Create a context as cmCompiledGeneratorExpression::Evaluate does.
+      cmGeneratorExpressionContext context(
+        headTarget->GetLocalGenerator(), config, false, headTarget, headTarget,
+        true, lib.Backtrace, lang);
+      cmExpandList(lib.Target->EvaluateInterfaceProperty(
+                     prop, &context, dagChecker, interfaceFor),
+                   ee.Values);
+      ee.ContextDependent = context.HadContextSensitiveCondition;
+      entries.Entries.emplace_back(std::move(ee));
+    }
+  }
+}
+}
+
+void AddInterfaceEntries(cmGeneratorTarget const* headTarget,
+                         std::string const& config, std::string const& prop,
+                         std::string const& lang,
+                         cmGeneratorExpressionDAGChecker* dagChecker,
+                         EvaluatedTargetPropertyEntries& entries,
+                         IncludeRuntimeInterface searchRuntime,
+                         cmGeneratorTarget::LinkInterfaceFor interfaceFor)
+{
+  if (searchRuntime == IncludeRuntimeInterface::Yes) {
+    if (cmLinkImplementation const* impl =
+          headTarget->GetLinkImplementation(config, interfaceFor)) {
+      entries.HadContextSensitiveCondition =
+        impl->HadContextSensitiveCondition;
+
+      auto runtimeLibIt = impl->LanguageRuntimeLibraries.find(lang);
+      if (runtimeLibIt != impl->LanguageRuntimeLibraries.end()) {
+        addInterfaceEntry(headTarget, config, prop, lang, dagChecker, entries,
+                          interfaceFor, runtimeLibIt->second);
+      }
+      addInterfaceEntry(headTarget, config, prop, lang, dagChecker, entries,
+                        interfaceFor, impl->Libraries);
+    }
+  } else {
+    if (cmLinkImplementationLibraries const* impl =
+          headTarget->GetLinkImplementationLibraries(config, interfaceFor)) {
+      entries.HadContextSensitiveCondition =
+        impl->HadContextSensitiveCondition;
+      addInterfaceEntry(headTarget, config, prop, lang, dagChecker, entries,
+                        interfaceFor, impl->Libraries);
+    }
+  }
+}
diff --git a/Source/cmEvaluatedTargetProperty.h b/Source/cmEvaluatedTargetProperty.h
new file mode 100644
index 0000000..e413ab0
--- /dev/null
+++ b/Source/cmEvaluatedTargetProperty.h
@@ -0,0 +1,80 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "cmGeneratorTarget.h"
+#include "cmListFileCache.h"
+
+class cmLinkImplItem;
+struct cmGeneratorExpressionDAGChecker;
+
+// Represent a target property entry after evaluating generator expressions
+// and splitting up lists.
+struct EvaluatedTargetPropertyEntry
+{
+  EvaluatedTargetPropertyEntry(cmLinkImplItem const& item,
+                               cmListFileBacktrace bt);
+
+  // Move-only.
+  EvaluatedTargetPropertyEntry(EvaluatedTargetPropertyEntry&&) = default;
+  EvaluatedTargetPropertyEntry(EvaluatedTargetPropertyEntry const&) = delete;
+  EvaluatedTargetPropertyEntry& operator=(EvaluatedTargetPropertyEntry&&) =
+    delete;
+  EvaluatedTargetPropertyEntry& operator=(
+    EvaluatedTargetPropertyEntry const&) = delete;
+
+  cmLinkImplItem const& LinkImplItem;
+  cmListFileBacktrace Backtrace;
+  std::vector<std::string> Values;
+  bool ContextDependent = false;
+};
+
+EvaluatedTargetPropertyEntry EvaluateTargetPropertyEntry(
+  cmGeneratorTarget const* thisTarget, std::string const& config,
+  std::string const& lang, cmGeneratorExpressionDAGChecker* dagChecker,
+  cmGeneratorTarget::TargetPropertyEntry& entry);
+
+struct EvaluatedTargetPropertyEntries
+{
+  std::vector<EvaluatedTargetPropertyEntry> Entries;
+  bool HadContextSensitiveCondition = false;
+};
+
+EvaluatedTargetPropertyEntries EvaluateTargetPropertyEntries(
+  cmGeneratorTarget const* thisTarget, std::string const& config,
+  std::string const& lang, cmGeneratorExpressionDAGChecker* dagChecker,
+  std::vector<std::unique_ptr<cmGeneratorTarget::TargetPropertyEntry>> const&
+    in);
+
+// IncludeRuntimeInterface is used to break the cycle in computing
+// the necessary transitive dependencies of targets that can occur
+// now that we have implicit language runtime targets.
+//
+// To determine the set of languages that a target has we need to iterate
+// all the sources which includes transitive INTERFACE sources.
+// Therefore we can't determine what language runtimes are needed
+// for a target until after all sources are computed.
+//
+// Therefore while computing the applicable INTERFACE_SOURCES we
+// must ignore anything in LanguageRuntimeLibraries or we would
+// create a cycle ( INTERFACE_SOURCES requires LanguageRuntimeLibraries,
+// LanguageRuntimeLibraries requires INTERFACE_SOURCES).
+//
+enum class IncludeRuntimeInterface
+{
+  Yes,
+  No
+};
+
+void AddInterfaceEntries(cmGeneratorTarget const* headTarget,
+                         std::string const& config, std::string const& prop,
+                         std::string const& lang,
+                         cmGeneratorExpressionDAGChecker* dagChecker,
+                         EvaluatedTargetPropertyEntries& entries,
+                         IncludeRuntimeInterface searchRuntime,
+                         cmGeneratorTarget::LinkInterfaceFor interfaceFor =
+                           cmGeneratorTarget::LinkInterfaceFor::Usage);
diff --git a/Source/cmExportBuildAndroidMKGenerator.cxx b/Source/cmExportBuildAndroidMKGenerator.cxx
index 5d0b208..5e7008b 100644
--- a/Source/cmExportBuildAndroidMKGenerator.cxx
+++ b/Source/cmExportBuildAndroidMKGenerator.cxx
@@ -10,6 +10,7 @@
 
 #include "cmGeneratorTarget.h"
 #include "cmLinkItem.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
 #include "cmPolicies.h"
@@ -148,7 +149,7 @@
         }
       } else if (property.first == "INTERFACE_INCLUDE_DIRECTORIES") {
         std::string includes = property.second;
-        std::vector<std::string> includeList = cmExpandedList(includes);
+        cmList includeList{ includes };
         os << "LOCAL_EXPORT_C_INCLUDES := ";
         std::string end;
         for (std::string const& i : includeList) {
@@ -158,9 +159,8 @@
         os << "\n";
       } else if (property.first == "INTERFACE_LINK_OPTIONS") {
         os << "LOCAL_EXPORT_LDFLAGS := ";
-        std::vector<std::string> linkFlagsList =
-          cmExpandedList(property.second);
-        os << cmJoin(linkFlagsList, " ") << "\n";
+        cmList linkFlagsList{ property.second };
+        os << linkFlagsList.join(" ") << "\n";
       } else {
         os << "# " << property.first << " " << (property.second) << "\n";
       }
diff --git a/Source/cmExportBuildFileGenerator.cxx b/Source/cmExportBuildFileGenerator.cxx
index ed199ea..a3637d8 100644
--- a/Source/cmExportBuildFileGenerator.cxx
+++ b/Source/cmExportBuildFileGenerator.cxx
@@ -106,6 +106,9 @@
     this->PopulateInterfaceProperty("INTERFACE_AUTOUIC_OPTIONS", gte,
                                     cmGeneratorExpression::BuildInterface,
                                     properties);
+    this->PopulateInterfaceProperty("INTERFACE_AUTOMOC_MACRO_NAMES", gte,
+                                    cmGeneratorExpression::BuildInterface,
+                                    properties);
     this->PopulateInterfaceProperty("INTERFACE_COMPILE_FEATURES", gte,
                                     cmGeneratorExpression::BuildInterface,
                                     properties);
@@ -254,7 +257,7 @@
     if (target->HasImportLibrary(config)) {
       std::string prop = cmStrCat("IMPORTED_IMPLIB", suffix);
       std::string value =
-        target->GetFullPath(config, cmStateEnums::ImportLibraryArtifact);
+        target->GetFullPath(config, cmStateEnums::ImportLibraryArtifact, true);
       if (mf->GetDefinition("CMAKE_IMPORT_LIBRARY_SUFFIX")) {
         target->GetImplibGNUtoMS(config, value, value,
                                  "${CMAKE_IMPORT_LIBRARY_SUFFIX}");
@@ -539,6 +542,12 @@
   os.SetCopyIfDifferent(true);
 
   for (auto const* tgt : this->ExportedTargets) {
+    // Only targets with C++ module sources will have a
+    // collator-generated install script.
+    if (!tgt->HaveCxx20ModuleSources()) {
+      continue;
+    }
+
     os << "include(\"${CMAKE_CURRENT_LIST_DIR}/target-" << tgt->GetExportName()
        << '-' << config << ".cmake\")\n";
   }
diff --git a/Source/cmExportFileGenerator.cxx b/Source/cmExportFileGenerator.cxx
index c8e2cb8..22276ae 100644
--- a/Source/cmExportFileGenerator.cxx
+++ b/Source/cmExportFileGenerator.cxx
@@ -17,6 +17,7 @@
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratorTarget.h"
 #include "cmLinkItem.h"
+#include "cmList.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
@@ -507,7 +508,7 @@
   if (!p) {
     return;
   }
-  std::vector<std::string> content = cmExpandedList(*p);
+  cmList content{ *p };
   ifaceProperties.insert(content.begin(), content.end());
 }
 
@@ -734,6 +735,22 @@
     lastPos = nameStartPos + libName.size() + 1;
   }
 
+  while (errorString.empty() &&
+         (pos = input.find("$<COMPILE_ONLY:", lastPos)) != std::string::npos) {
+    std::string::size_type nameStartPos = pos + cmStrLen("$<COMPILE_ONLY:");
+    std::string::size_type endPos = input.find('>', nameStartPos);
+    if (endPos == std::string::npos) {
+      errorString = "$<COMPILE_ONLY:...> expression incomplete";
+      break;
+    }
+    std::string libName = input.substr(nameStartPos, endPos - nameStartPos);
+    if (cmGeneratorExpression::IsValidTargetName(libName) &&
+        this->AddTargetNamespace(libName, target, lg)) {
+      input.replace(nameStartPos, endPos - nameStartPos, libName);
+    }
+    lastPos = nameStartPos + libName.size() + 1;
+  }
+
   this->ReplaceInstallPrefix(input);
 
   if (!errorString.empty()) {
@@ -938,13 +955,13 @@
 
   // Isolate the file policy level.
   // Support CMake versions as far back as 2.6 but also support using NEW
-  // policy settings for up to CMake 3.24 (this upper limit may be reviewed
+  // policy settings for up to CMake 3.25 (this upper limit may be reviewed
   // and increased from time to time). This reduces the opportunity for CMake
   // warnings when an older export file is later used with newer CMake
   // versions.
   /* clang-format off */
   os << "cmake_policy(PUSH)\n"
-     << "cmake_policy(VERSION 2.8.3...3.24)\n";
+     << "cmake_policy(VERSION 2.8.3...3.25)\n";
   /* clang-format on */
 }
 
@@ -1063,7 +1080,8 @@
   }
 
   // Mark the imported executable if it has exports.
-  if (target->IsExecutableWithExports()) {
+  if (target->IsExecutableWithExports() ||
+      (target->IsSharedLibraryWithExports() && target->HasImportLibrary(""))) {
     os << "set_property(TARGET " << targetName
        << " PROPERTY ENABLE_EXPORTS 1)\n";
   }
@@ -1244,7 +1262,7 @@
   const auto& targetProperties = gte->Target->GetProperties();
   if (cmValue exportProperties =
         targetProperties.GetPropertyValue("EXPORT_PROPERTIES")) {
-    for (auto& prop : cmExpandedList(*exportProperties)) {
+    for (auto& prop : cmList{ *exportProperties }) {
       /* Black list reserved properties */
       if (cmHasLiteralPrefix(prop, "IMPORTED_") ||
           cmHasLiteralPrefix(prop, "INTERFACE_")) {
@@ -1308,7 +1326,22 @@
          << this->GetFileSetFiles(gte, fileSet, te) << "\n";
     }
 
-    os << "  )\nendif()\n\n";
+    os << "  )\nelse()\n  set_property(TARGET " << targetName
+       << "\n    APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES";
+    for (auto const& name : interfaceFileSets) {
+      auto* fileSet = gte->Target->GetFileSet(name);
+      if (!fileSet) {
+        gte->Makefile->IssueMessage(
+          MessageType::FATAL_ERROR,
+          cmStrCat("File set \"", name,
+                   "\" is listed in interface file sets of ", gte->GetName(),
+                   " but has not been created"));
+        return;
+      }
+
+      os << "\n      " << this->GetFileSetDirectories(gte, fileSet, te);
+    }
+    os << "\n  )\nendif()\n\n";
   }
 }
 
diff --git a/Source/cmExportInstallFileGenerator.cxx b/Source/cmExportInstallFileGenerator.cxx
index 5e190f4..df119ae 100644
--- a/Source/cmExportInstallFileGenerator.cxx
+++ b/Source/cmExportInstallFileGenerator.cxx
@@ -110,6 +110,9 @@
     this->PopulateInterfaceProperty("INTERFACE_AUTOUIC_OPTIONS", gt,
                                     cmGeneratorExpression::InstallInterface,
                                     properties);
+    this->PopulateInterfaceProperty("INTERFACE_AUTOMOC_MACRO_NAMES", gt,
+                                    cmGeneratorExpression::InstallInterface,
+                                    properties);
     this->PopulateInterfaceProperty("INTERFACE_COMPILE_FEATURES", gt,
                                     cmGeneratorExpression::InstallInterface,
                                     properties);
@@ -409,7 +412,7 @@
 
     // Append the installed file name.
     value += cmInstallTargetGenerator::GetInstallFilename(
-      target, config, cmInstallTargetGenerator::NameImplib);
+      target, config, cmInstallTargetGenerator::NameImplibReal);
 
     // Store the property.
     properties[prop] = value;
@@ -430,6 +433,19 @@
     properties[prop] = cmJoin(objects, ";");
     importedLocations.insert(prop);
   } else {
+    if (target->IsFrameworkOnApple() && target->HasImportLibrary(config)) {
+      // store as well IMPLIB value
+      auto importProp = cmStrCat("IMPORTED_IMPLIB", suffix);
+      auto importValue =
+        cmStrCat(value,
+                 cmInstallTargetGenerator::GetInstallFilename(
+                   target, config, cmInstallTargetGenerator::NameImplibReal));
+
+      // Store the property.
+      properties[importProp] = importValue;
+      importedLocations.insert(importProp);
+    }
+
     // Construct the property name.
     std::string prop = cmStrCat("IMPORTED_LOCATION", suffix);
 
@@ -736,6 +752,12 @@
 
   auto& prop_files = this->ConfigCxxModuleTargetFiles[config];
   for (auto const* tgt : this->ExportedTargets) {
+    // Only targets with C++ module sources will have a
+    // collator-generated install script.
+    if (!tgt->HaveCxx20ModuleSources()) {
+      continue;
+    }
+
     auto prop_filename = cmStrCat("target-", tgt->GetExportName(), '-',
                                   filename_config, ".cmake");
     prop_files.emplace_back(cmStrCat(dest, prop_filename));
diff --git a/Source/cmExportTryCompileFileGenerator.cxx b/Source/cmExportTryCompileFileGenerator.cxx
index 33c057d..f30c3c3 100644
--- a/Source/cmExportTryCompileFileGenerator.cxx
+++ b/Source/cmExportTryCompileFileGenerator.cxx
@@ -12,6 +12,7 @@
 #include "cmGeneratorExpressionDAGChecker.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
+#include "cmList.h"
 #include "cmListFileCache.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
@@ -85,7 +86,7 @@
   std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(*prop);
 
   cmTarget dummyHead("try_compile_dummy_exe", cmStateEnums::EXECUTABLE,
-                     cmTarget::VisibilityNormal, tgt->Target->GetMakefile(),
+                     cmTarget::Visibility::Normal, tgt->Target->GetMakefile(),
                      cmTarget::PerConfig::Yes);
 
   cmGeneratorTarget gDummyHead(&dummyHead, tgt->GetLocalGenerator());
@@ -126,7 +127,7 @@
       std::string evalResult =
         this->FindTargets(p, target, std::string(), emitted);
 
-      std::vector<std::string> depends = cmExpandedList(evalResult);
+      cmList depends{ evalResult };
       for (std::string const& li : depends) {
         cmGeneratorTarget* tgt =
           target->GetLocalGenerator()->FindGeneratorTargetToUse(li);
diff --git a/Source/cmExternalMakefileProjectGenerator.cxx b/Source/cmExternalMakefileProjectGenerator.cxx
index 5895d66..5fecb35 100644
--- a/Source/cmExternalMakefileProjectGenerator.cxx
+++ b/Source/cmExternalMakefileProjectGenerator.cxx
@@ -17,14 +17,13 @@
 std::string cmExternalMakefileProjectGenerator::CreateFullGeneratorName(
   const std::string& globalGenerator, const std::string& extraGenerator)
 {
-  std::string fullName;
-  if (!globalGenerator.empty()) {
-    if (!extraGenerator.empty()) {
-      fullName = cmStrCat(extraGenerator, " - ");
-    }
-    fullName += globalGenerator;
+  if (globalGenerator.empty()) {
+    return {};
   }
-  return fullName;
+  if (extraGenerator.empty()) {
+    return globalGenerator;
+  }
+  return cmStrCat(extraGenerator, " - ", globalGenerator);
 }
 
 bool cmExternalMakefileProjectGenerator::Open(
diff --git a/Source/cmExtraCodeBlocksGenerator.cxx b/Source/cmExtraCodeBlocksGenerator.cxx
index 988c5c3..8d7f33e 100644
--- a/Source/cmExtraCodeBlocksGenerator.cxx
+++ b/Source/cmExtraCodeBlocksGenerator.cxx
@@ -14,6 +14,7 @@
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
+#include "cmList.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
 #include "cmRange.h"
@@ -45,7 +46,7 @@
 {
   static cmExternalMakefileProjectGeneratorSimpleFactory<
     cmExtraCodeBlocksGenerator>
-    factory("CodeBlocks", "Generates CodeBlocks project files.");
+    factory("CodeBlocks", "Generates CodeBlocks project files (deprecated).");
 
   if (factory.GetSupportedGlobalGenerators().empty()) {
 #if defined(_WIN32)
@@ -567,13 +568,13 @@
     std::string systemIncludeDirs = makefile->GetSafeDefinition(
       "CMAKE_EXTRA_GENERATOR_CXX_SYSTEM_INCLUDE_DIRS");
     if (!systemIncludeDirs.empty()) {
-      cm::append(allIncludeDirs, cmExpandedList(systemIncludeDirs));
+      cm::append(allIncludeDirs, cmList{ systemIncludeDirs });
     }
 
     systemIncludeDirs = makefile->GetSafeDefinition(
       "CMAKE_EXTRA_GENERATOR_C_SYSTEM_INCLUDE_DIRS");
     if (!systemIncludeDirs.empty()) {
-      cm::append(allIncludeDirs, cmExpandedList(systemIncludeDirs));
+      cm::append(allIncludeDirs, cmList{ systemIncludeDirs });
     }
 
     auto end = cmRemoveDuplicates(allIncludeDirs);
diff --git a/Source/cmExtraCodeLiteGenerator.cxx b/Source/cmExtraCodeLiteGenerator.cxx
index 9e8ac5c..7538a7f 100644
--- a/Source/cmExtraCodeLiteGenerator.cxx
+++ b/Source/cmExtraCodeLiteGenerator.cxx
@@ -33,7 +33,7 @@
 {
   static cmExternalMakefileProjectGeneratorSimpleFactory<
     cmExtraCodeLiteGenerator>
-    factory("CodeLite", "Generates CodeLite project files.");
+    factory("CodeLite", "Generates CodeLite project files (deprecated).");
 
   if (factory.GetSupportedGlobalGenerators().empty()) {
 #if defined(_WIN32)
diff --git a/Source/cmExtraEclipseCDT4Generator.cxx b/Source/cmExtraEclipseCDT4Generator.cxx
index a07acdc..6cba37b 100644
--- a/Source/cmExtraEclipseCDT4Generator.cxx
+++ b/Source/cmExtraEclipseCDT4Generator.cxx
@@ -16,6 +16,7 @@
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
+#include "cmList.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
@@ -63,7 +64,8 @@
 {
   static cmExternalMakefileProjectGeneratorSimpleFactory<
     cmExtraEclipseCDT4Generator>
-    factory("Eclipse CDT4", "Generates Eclipse CDT 4.0 project files.");
+    factory("Eclipse CDT4",
+            "Generates Eclipse CDT 4.0 project files (deprecated).");
 
   if (factory.GetSupportedGlobalGenerators().empty()) {
 // TODO: Verify if __CYGWIN__ should be checked.
@@ -255,7 +257,7 @@
     // The variable is in the env, but not in the cache. Use it and put it
     // in the cache
     valueToUse = envVarValue;
-    mf->AddCacheDefinition(cacheEntryName, valueToUse, cacheEntryName.c_str(),
+    mf->AddCacheDefinition(cacheEntryName, valueToUse, cacheEntryName,
                            cmStateEnums::STRING, true);
     mf->GetCMakeInstance()->SaveCache(lg.GetBinaryDirectory());
   } else if (!envVarSet && cacheValue) {
@@ -270,9 +272,8 @@
     valueToUse = *cacheValue;
     if (valueToUse.find(envVarValue) == std::string::npos) {
       valueToUse = envVarValue;
-      mf->AddCacheDefinition(cacheEntryName, valueToUse,
-                             cacheEntryName.c_str(), cmStateEnums::STRING,
-                             true);
+      mf->AddCacheDefinition(cacheEntryName, valueToUse, cacheEntryName,
+                             cmStateEnums::STRING, true);
       mf->GetCMakeInstance()->SaveCache(lg.GetBinaryDirectory());
     }
   }
@@ -417,7 +418,7 @@
 
   if (cmValue extraNaturesProp =
         mf->GetState()->GetGlobalProperty("ECLIPSE_EXTRA_NATURES")) {
-    std::vector<std::string> extraNatures = cmExpandedList(*extraNaturesProp);
+    cmList extraNatures{ *extraNaturesProp };
     for (std::string const& n : extraNatures) {
       xml.Element("nature", n);
     }
@@ -797,7 +798,7 @@
     mf->GetDefinition("CMAKE_EXTRA_GENERATOR_C_SYSTEM_DEFINED_MACROS");
   if (this->CEnabled && cDefs) {
     // Expand the list.
-    std::vector<std::string> defs = cmExpandedList(*cDefs, true);
+    cmList defs{ *cDefs, cmList::EmptyElements::Yes };
 
     // the list must contain only definition-value pairs:
     if ((defs.size() % 2) == 0) {
@@ -829,7 +830,7 @@
     mf->GetDefinition("CMAKE_EXTRA_GENERATOR_CXX_SYSTEM_DEFINED_MACROS");
   if (this->CXXEnabled && cxxDefs) {
     // Expand the list.
-    std::vector<std::string> defs = cmExpandedList(*cxxDefs, true);
+    cmList defs{ *cxxDefs, cmList::EmptyElements::Yes };
 
     // the list must contain only definition-value pairs:
     if ((defs.size() % 2) == 0) {
@@ -878,14 +879,14 @@
   if (this->CEnabled && !compiler.empty()) {
     std::string systemIncludeDirs =
       mf->GetSafeDefinition("CMAKE_EXTRA_GENERATOR_C_SYSTEM_INCLUDE_DIRS");
-    std::vector<std::string> dirs = cmExpandedList(systemIncludeDirs);
+    cmList dirs{ systemIncludeDirs };
     this->AppendIncludeDirectories(xml, dirs, emitted);
   }
   compiler = mf->GetSafeDefinition("CMAKE_CXX_COMPILER");
   if (this->CXXEnabled && !compiler.empty()) {
     std::string systemIncludeDirs =
       mf->GetSafeDefinition("CMAKE_EXTRA_GENERATOR_CXX_SYSTEM_INCLUDE_DIRS");
-    std::vector<std::string> dirs = cmExpandedList(systemIncludeDirs);
+    cmList dirs{ systemIncludeDirs };
     this->AppendIncludeDirectories(xml, dirs, emitted);
   }
 
diff --git a/Source/cmExtraKateGenerator.cxx b/Source/cmExtraKateGenerator.cxx
index eec43c4..6e6d009 100644
--- a/Source/cmExtraKateGenerator.cxx
+++ b/Source/cmExtraKateGenerator.cxx
@@ -8,6 +8,7 @@
 #include <set>
 #include <vector>
 
+#include "cmCMakePath.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
@@ -24,7 +25,7 @@
 cmExternalMakefileProjectGeneratorFactory* cmExtraKateGenerator::GetFactory()
 {
   static cmExternalMakefileProjectGeneratorSimpleFactory<cmExtraKateGenerator>
-    factory("Kate", "Generates Kate project files.");
+    factory("Kate", "Generates Kate project files (deprecated).");
 
   if (factory.GetSupportedGlobalGenerators().empty()) {
 #if defined(_WIN32)
@@ -34,6 +35,7 @@
 // factory.AddSupportedGlobalGenerator("MSYS Makefiles");
 #endif
     factory.AddSupportedGlobalGenerator("Ninja");
+    factory.AddSupportedGlobalGenerator("Ninja Multi-Config");
     factory.AddSupportedGlobalGenerator("Unix Makefiles");
   }
 
@@ -47,7 +49,9 @@
   this->ProjectName = this->GenerateProjectName(
     lg->GetProjectName(), mf->GetSafeDefinition("CMAKE_BUILD_TYPE"),
     this->GetPathBasename(lg->GetBinaryDirectory()));
-  this->UseNinja = (this->GlobalGenerator->GetName() == "Ninja");
+  this->UseNinja =
+    ((this->GlobalGenerator->GetName() == "Ninja") ||
+     (this->GlobalGenerator->GetName() == "Ninja Multi-Config"));
 
   this->CreateKateProjectFile(*lg);
   this->CreateDummyKateProjectFile(*lg);
@@ -81,6 +85,7 @@
   const std::string& makeArgs =
     mf->GetSafeDefinition("CMAKE_KATE_MAKE_ARGUMENTS");
   std::string const& homeOutputDir = lg.GetBinaryDirectory();
+  const auto configs = mf->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
 
   /* clang-format off */
   fout <<
@@ -104,16 +109,16 @@
   // this is for kate >= 4.13:
   fout << "\t\t\"targets\":[\n";
 
-  this->AppendTarget(fout, "all", make, makeArgs, homeOutputDir,
+  this->AppendTarget(fout, "all", configs, make, makeArgs, homeOutputDir,
                      homeOutputDir);
-  this->AppendTarget(fout, "clean", make, makeArgs, homeOutputDir,
+  this->AppendTarget(fout, "clean", configs, make, makeArgs, homeOutputDir,
                      homeOutputDir);
 
   // add all executable and library targets and some of the GLOBAL
   // and UTILITY targets
   for (const auto& localGen : this->GlobalGenerator->GetLocalGenerators()) {
     const auto& targets = localGen->GetGeneratorTargets();
-    std::string currentDir = localGen->GetCurrentBinaryDirectory();
+    const std::string currentDir = localGen->GetCurrentBinaryDirectory();
     bool topLevel = (currentDir == localGen->GetBinaryDirectory());
 
     for (const auto& target : targets) {
@@ -137,8 +142,8 @@
             }
           }
           if (insertTarget) {
-            this->AppendTarget(fout, targetName, make, makeArgs, currentDir,
-                               homeOutputDir);
+            this->AppendTarget(fout, targetName, configs, make, makeArgs,
+                               currentDir, homeOutputDir);
           }
         } break;
         case cmStateEnums::UTILITY:
@@ -153,19 +158,21 @@
             break;
           }
 
-          this->AppendTarget(fout, targetName, make, makeArgs, currentDir,
-                             homeOutputDir);
+          this->AppendTarget(fout, targetName, configs, make, makeArgs,
+                             currentDir, homeOutputDir);
           break;
         case cmStateEnums::EXECUTABLE:
         case cmStateEnums::STATIC_LIBRARY:
         case cmStateEnums::SHARED_LIBRARY:
         case cmStateEnums::MODULE_LIBRARY:
         case cmStateEnums::OBJECT_LIBRARY: {
-          this->AppendTarget(fout, targetName, make, makeArgs, currentDir,
-                             homeOutputDir);
-          std::string fastTarget = cmStrCat(targetName, "/fast");
-          this->AppendTarget(fout, fastTarget, make, makeArgs, currentDir,
-                             homeOutputDir);
+          this->AppendTarget(fout, targetName, configs, make, makeArgs,
+                             currentDir, homeOutputDir);
+          if (!this->UseNinja) {
+            std::string fastTarget = cmStrCat(targetName, "/fast");
+            this->AppendTarget(fout, fastTarget, configs, make, makeArgs,
+                               currentDir, homeOutputDir);
+          }
 
         } break;
         default:
@@ -178,29 +185,36 @@
     std::vector<std::string> objectFileTargets;
     localGen->GetIndividualFileTargets(objectFileTargets);
     for (std::string const& f : objectFileTargets) {
-      this->AppendTarget(fout, f, make, makeArgs, currentDir, homeOutputDir);
+      this->AppendTarget(fout, f, configs, make, makeArgs, currentDir,
+                         homeOutputDir);
     }
   }
 
   fout << "\t] }\n";
 }
 
-void cmExtraKateGenerator::AppendTarget(cmGeneratedFileStream& fout,
-                                        const std::string& target,
-                                        const std::string& make,
-                                        const std::string& makeArgs,
-                                        const std::string& path,
-                                        const std::string& homeOutputDir) const
+void cmExtraKateGenerator::AppendTarget(
+  cmGeneratedFileStream& fout, const std::string& target,
+  const std::vector<std::string>& configs, const std::string& make,
+  const std::string& makeArgs, const std::string& path,
+  const std::string& homeOutputDir) const
 {
   static char JsonSep = ' ';
 
-  fout << "\t\t\t" << JsonSep << R"({"name":")" << target
-       << "\", "
-          "\"build_cmd\":\""
-       << make << " -C \\\"" << (this->UseNinja ? homeOutputDir : path)
-       << "\\\" " << makeArgs << " " << target << "\"}\n";
+  for (const std::string& conf : configs) {
+    fout << "\t\t\t" << JsonSep << R"({"name":")" << target
+         << ((configs.size() > 1) ? (std::string(":") + conf) : std::string())
+         << "\", "
+            "\"build_cmd\":\""
+         << make << " -C \\\"" << (this->UseNinja ? homeOutputDir : path)
+         << "\\\" "
+         << ((this->UseNinja && configs.size() > 1)
+               ? std::string(" -f build-") + conf + ".ninja"
+               : std::string())
+         << makeArgs << " " << target << "\"}\n";
 
-  JsonSep = ',';
+    JsonSep = ',';
+  }
 }
 
 void cmExtraKateGenerator::CreateDummyKateProjectFile(
@@ -220,17 +234,58 @@
 std::string cmExtraKateGenerator::GenerateFilesString(
   const cmLocalGenerator& lg) const
 {
-  std::string s = cmStrCat(lg.GetSourceDirectory(), "/.git");
-  if (cmSystemTools::FileExists(s)) {
-    return "\"git\": 1 ";
+  const cmMakefile* mf = lg.GetMakefile();
+  std::string mode =
+    cmSystemTools::UpperCase(mf->GetSafeDefinition("CMAKE_KATE_FILES_MODE"));
+  static const std::string gitString = "\"git\": 1 ";
+  static const std::string svnString = "\"svn\": 1 ";
+  static const std::string hgString = "\"hg\": 1 ";
+  static const std::string fossilString = "\"fossil\": 1 ";
+
+  if (mode == "SVN") {
+    return svnString;
+  }
+  if (mode == "GIT") {
+    return gitString;
+  }
+  if (mode == "HG") {
+    return hgString;
+  }
+  if (mode == "FOSSIL") {
+    return fossilString;
   }
 
-  s = cmStrCat(lg.GetSourceDirectory(), "/.svn");
-  if (cmSystemTools::FileExists(s)) {
-    return "\"svn\": 1 ";
-  }
+  // check for the VCS files except when "forced" to "FILES" mode:
+  if (mode != "LIST") {
+    cmCMakePath startDir(lg.GetSourceDirectory(), cmCMakePath::auto_format);
+    // move the directories up to the root directory to see whether we are in
+    // a subdir of a svn, git, hg or fossil checkout
+    for (;;) {
+      std::string s = startDir.String() + "/.git";
+      if (cmSystemTools::FileExists(s)) {
+        return gitString;
+      }
 
-  s = cmStrCat(lg.GetSourceDirectory(), '/');
+      s = startDir.String() + "/.svn";
+      if (cmSystemTools::FileExists(s)) {
+        return svnString;
+      }
+
+      s = startDir.String() + "/.hg";
+      if (cmSystemTools::FileExists(s)) {
+        return hgString;
+      }
+      s = startDir.String() + "/.fslckout";
+      if (cmSystemTools::FileExists(s)) {
+        return fossilString;
+      }
+
+      if (!startDir.HasRelativePath()) { // have we reached the root dir ?
+        break;
+      }
+      startDir = startDir.GetParentPath();
+    }
+  }
 
   std::set<std::string> files;
   std::string tmp;
@@ -240,9 +295,8 @@
     cmMakefile* makefile = lgen->GetMakefile();
     const std::vector<std::string>& listFiles = makefile->GetListFiles();
     for (std::string const& listFile : listFiles) {
-      tmp = listFile;
-      {
-        files.insert(tmp);
+      if (listFile.find("/CMakeFiles/") == std::string::npos) {
+        files.insert(listFile);
       }
     }
 
diff --git a/Source/cmExtraKateGenerator.h b/Source/cmExtraKateGenerator.h
index c66ddbf..203fa2f 100644
--- a/Source/cmExtraKateGenerator.h
+++ b/Source/cmExtraKateGenerator.h
@@ -5,6 +5,7 @@
 #include "cmConfigure.h" // IWYU pragma: keep
 
 #include <string>
+#include <vector>
 
 #include "cmExternalMakefileProjectGenerator.h"
 
@@ -29,6 +30,7 @@
   void WriteTargets(const cmLocalGenerator& lg,
                     cmGeneratedFileStream& fout) const;
   void AppendTarget(cmGeneratedFileStream& fout, const std::string& target,
+                    const std::vector<std::string>& configs,
                     const std::string& make, const std::string& makeArgs,
                     const std::string& path,
                     const std::string& homeOutputDir) const;
diff --git a/Source/cmExtraSublimeTextGenerator.cxx b/Source/cmExtraSublimeTextGenerator.cxx
index 19e87d5..205a691 100644
--- a/Source/cmExtraSublimeTextGenerator.cxx
+++ b/Source/cmExtraSublimeTextGenerator.cxx
@@ -14,6 +14,7 @@
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
+#include "cmList.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
@@ -43,7 +44,8 @@
 {
   static cmExternalMakefileProjectGeneratorSimpleFactory<
     cmExtraSublimeTextGenerator>
-    factory("Sublime Text 2", "Generates Sublime Text 2 project files.");
+    factory("Sublime Text 2",
+            "Generates Sublime Text 2 project files (deprecated).");
 
   if (factory.GetSupportedGlobalGenerators().empty()) {
 #if defined(_WIN32)
@@ -137,7 +139,7 @@
   // End of build_systems
   fout << "\n\t]";
   std::string systemName = mf->GetSafeDefinition("CMAKE_SYSTEM_NAME");
-  std::vector<std::string> tokens = cmExpandedList(this->EnvSettings);
+  cmList tokens{ this->EnvSettings };
 
   if (!this->EnvSettings.empty()) {
     fout << ",";
diff --git a/Source/cmFileAPI.cxx b/Source/cmFileAPI.cxx
index d1d3d25..8b98916 100644
--- a/Source/cmFileAPI.cxx
+++ b/Source/cmFileAPI.cxx
@@ -728,7 +728,7 @@
 // The "codemodel" object kind.
 
 // Update Help/manual/cmake-file-api.7.rst when updating this constant.
-static unsigned int const CodeModelV2Minor = 5;
+static unsigned int const CodeModelV2Minor = 6;
 
 void cmFileAPI::BuildClientRequestCodeModel(
   ClientRequest& r, std::vector<RequestVersion> const& versions)
diff --git a/Source/cmFileAPICodemodel.cxx b/Source/cmFileAPICodemodel.cxx
index d3683d4..280ebb0 100644
--- a/Source/cmFileAPICodemodel.cxx
+++ b/Source/cmFileAPICodemodel.cxx
@@ -328,6 +328,7 @@
   std::vector<JBT<std::string>> Defines;
   std::vector<JBT<std::string>> PrecompileHeaders;
   std::vector<IncludeEntry> Includes;
+  std::vector<IncludeEntry> Frameworks;
 
   friend bool operator==(CompileData const& l, CompileData const& r)
   {
@@ -335,7 +336,7 @@
             l.Flags == r.Flags && l.Defines == r.Defines &&
             l.PrecompileHeaders == r.PrecompileHeaders &&
             l.LanguageStandard == r.LanguageStandard &&
-            l.Includes == r.Includes);
+            l.Includes == r.Includes && l.Frameworks == r.Frameworks);
   }
 };
 }
@@ -356,6 +357,12 @@
          hash<Json::ArrayIndex>()(i.Path.Backtrace.Index) ^
          (i.IsSystem ? std::numeric_limits<size_t>::max() : 0));
     }
+    for (auto const& i : in.Frameworks) {
+      result = result ^
+        (hash<std::string>()(i.Path.Value) ^
+         hash<Json::ArrayIndex>()(i.Path.Backtrace.Index) ^
+         (i.IsSystem ? std::numeric_limits<size_t>::max() : 0));
+    }
     for (auto const& i : in.Flags) {
       result = result ^ hash<std::string>()(i.Value) ^
         hash<Json::ArrayIndex>()(i.Backtrace.Index);
@@ -468,6 +475,7 @@
   Json::Value DumpPaths();
   Json::Value DumpCompileData(CompileData const& cd);
   Json::Value DumpInclude(CompileData::IncludeEntry const& inc);
+  Json::Value DumpFramework(CompileData::IncludeEntry const& fw);
   Json::Value DumpPrecompileHeader(JBT<std::string> const& header);
   Json::Value DumpLanguageStandard(JBTs<std::string> const& standard);
   Json::Value DumpDefine(JBT<std::string> const& def);
@@ -1294,9 +1302,15 @@
   std::vector<BT<std::string>> includePathList =
     lg->GetIncludeDirectories(this->GT, lang, this->Config);
   for (BT<std::string> const& i : includePathList) {
-    cd.Includes.emplace_back(
-      this->ToJBT(i),
-      this->GT->IsSystemIncludeDirectory(i.Value, this->Config, lang));
+    if (this->GT->IsApple() && cmSystemTools::IsPathToFramework(i.Value)) {
+      cd.Frameworks.emplace_back(
+        this->ToJBT(i),
+        this->GT->IsSystemIncludeDirectory(i.Value, this->Config, lang));
+    } else {
+      cd.Includes.emplace_back(
+        this->ToJBT(i),
+        this->GT->IsSystemIncludeDirectory(i.Value, this->Config, lang));
+    }
   }
   std::vector<BT<std::string>> precompileHeaders =
     this->GT->GetPrecompileHeaders(this->Config, lang);
@@ -1355,8 +1369,8 @@
   }
 
   // Add precompile headers compile options.
-  std::vector<std::string> architectures;
-  this->GT->GetAppleArchs(this->Config, architectures);
+  std::vector<std::string> architectures =
+    this->GT->GetAppleArchs(this->Config, fd.Language);
   if (architectures.empty()) {
     architectures.emplace_back();
   }
@@ -1408,7 +1422,11 @@
         bool const isSystemInclude =
           this->GT->IsSystemIncludeDirectory(i, this->Config, fd.Language);
         BT<std::string> include(i, tmpInclude.Backtrace);
-        fd.Includes.emplace_back(this->ToJBT(include), isSystemInclude);
+        if (this->GT->IsApple() && cmSystemTools::IsPathToFramework(i)) {
+          fd.Frameworks.emplace_back(this->ToJBT(include), isSystemInclude);
+        } else {
+          fd.Includes.emplace_back(this->ToJBT(include), isSystemInclude);
+        }
       }
     }
   }
@@ -1481,6 +1499,13 @@
   cd.Includes.insert(cd.Includes.end(), td.Includes.begin(),
                      td.Includes.end());
 
+  // Use source-specific frameworks followed by target-wide frameworks.
+  cd.Frameworks.reserve(fd.Frameworks.size() + td.Frameworks.size());
+  cd.Frameworks.insert(cd.Frameworks.end(), fd.Frameworks.begin(),
+                       fd.Frameworks.end());
+  cd.Frameworks.insert(cd.Frameworks.end(), td.Frameworks.begin(),
+                       td.Frameworks.end());
+
   // Use target-wide defines followed by source-specific defines.
   cd.Defines.reserve(td.Defines.size() + fd.Defines.size());
   cd.Defines.insert(cd.Defines.end(), td.Defines.begin(), td.Defines.end());
@@ -1696,6 +1721,13 @@
     }
     result["includes"] = includes;
   }
+  if (!cd.Frameworks.empty()) {
+    Json::Value frameworks = Json::arrayValue;
+    for (auto const& i : cd.Frameworks) {
+      frameworks.append(this->DumpFramework(i));
+    }
+    result["frameworks"] = frameworks;
+  }
   if (!cd.Defines.empty()) {
     Json::Value defines = Json::arrayValue;
     for (JBT<std::string> const& d : cd.Defines) {
@@ -1729,6 +1761,12 @@
   return include;
 }
 
+Json::Value Target::DumpFramework(CompileData::IncludeEntry const& fw)
+{
+  // for now, idem as include
+  return this->DumpInclude(fw);
+}
+
 Json::Value Target::DumpPrecompileHeader(JBT<std::string> const& header)
 {
   Json::Value precompileHeader = Json::objectValue;
diff --git a/Source/cmFileAPIToolchains.cxx b/Source/cmFileAPIToolchains.cxx
index fe2972f..a51ae20 100644
--- a/Source/cmFileAPIToolchains.cxx
+++ b/Source/cmFileAPIToolchains.cxx
@@ -10,6 +10,7 @@
 
 #include "cmFileAPI.h"
 #include "cmGlobalGenerator.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmState.h"
 #include "cmStringAlgorithms.h"
@@ -124,10 +125,11 @@
     cmStrCat("CMAKE_", lang, "_", variable.VariableSuffix);
 
   if (variable.IsList) {
-    std::vector<std::string> values;
-    if (mf->GetDefExpandList(variableName, values)) {
+    cmValue data = mf->GetDefinition(variableName);
+    if (data) {
+      cmList values(data);
       Json::Value jsonArray = Json::arrayValue;
-      for (std::string const& value : values) {
+      for (auto const& value : values) {
         jsonArray.append(value);
       }
       object[variable.ObjectKey] = jsonArray;
diff --git a/Source/cmFileCommand.cxx b/Source/cmFileCommand.cxx
index 00a68a8..45fba8b 100644
--- a/Source/cmFileCommand.cxx
+++ b/Source/cmFileCommand.cxx
@@ -30,7 +30,6 @@
 
 #include "cmArgumentParser.h"
 #include "cmArgumentParserTypes.h"
-#include "cmCMakePath.h"
 #include "cmCryptoHash.h"
 #include "cmELF.h"
 #include "cmExecutionStatus.h"
@@ -1278,9 +1277,9 @@
     }
   }
 
-  cmCMakePath path(input, cmCMakePath::auto_format);
-  path = path.Absolute(*arguments.BaseDirectory).Normal();
-  auto realPath = cmSystemTools::GetRealPath(path.GenericString());
+  auto realPath =
+    cmSystemTools::CollapseFullPath(input, *arguments.BaseDirectory);
+  realPath = cmSystemTools::GetRealPath(realPath);
 
   status.GetMakefile().AddDefinition(args[2], realPath);
 
diff --git a/Source/cmFileCopier.cxx b/Source/cmFileCopier.cxx
index ef55abf..663bddc 100644
--- a/Source/cmFileCopier.cxx
+++ b/Source/cmFileCopier.cxx
@@ -9,6 +9,7 @@
 #include "cmExecutionStatus.h"
 #include "cmFSPermissions.h"
 #include "cmFileTimes.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
@@ -169,8 +170,7 @@
   cmValue default_dir_install_permissions = this->Makefile->GetDefinition(
     "CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS");
   if (cmNonempty(default_dir_install_permissions)) {
-    std::vector<std::string> items =
-      cmExpandedList(*default_dir_install_permissions);
+    cmList items{ *default_dir_install_permissions };
     for (const auto& arg : items) {
       if (!this->CheckPermissions(arg, **mode)) {
         this->Status.SetError(
diff --git a/Source/cmFileLockResult.cxx b/Source/cmFileLockResult.cxx
index 70b8cdb..b7f7f38 100644
--- a/Source/cmFileLockResult.cxx
+++ b/Source/cmFileLockResult.cxx
@@ -56,9 +56,9 @@
 #  define WINMSG_BUF_LEN (1024)
       char winmsg[WINMSG_BUF_LEN];
       DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
-      if (FormatMessageA(flags, NULL, this->ErrorValue,
+      if (FormatMessageA(flags, nullptr, this->ErrorValue,
                          MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
-                         (LPSTR)winmsg, WINMSG_BUF_LEN, NULL)) {
+                         (LPSTR)winmsg, WINMSG_BUF_LEN, nullptr)) {
         const std::string message = winmsg;
         return message;
       } else {
diff --git a/Source/cmFileLockWin32.cxx b/Source/cmFileLockWin32.cxx
index b8e435a..7bee5f2 100644
--- a/Source/cmFileLockWin32.cxx
+++ b/Source/cmFileLockWin32.cxx
@@ -36,9 +36,9 @@
 {
   const DWORD access = GENERIC_READ | GENERIC_WRITE;
   const DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
-  const PSECURITY_ATTRIBUTES security = NULL;
+  const PSECURITY_ATTRIBUTES security = nullptr;
   const DWORD attr = 0;
-  const HANDLE templ = NULL;
+  const HANDLE templ = nullptr;
   this->File = CreateFileW(
     cmSystemTools::ConvertToWindowsExtendedPath(this->Filename).c_str(),
     access, shareMode, security, OPEN_EXISTING, attr, templ);
diff --git a/Source/cmFileSet.cxx b/Source/cmFileSet.cxx
index b96ba6e..48a2570 100644
--- a/Source/cmFileSet.cxx
+++ b/Source/cmFileSet.cxx
@@ -12,6 +12,7 @@
 #include "cmsys/RegularExpression.hxx"
 
 #include "cmGeneratorExpression.h"
+#include "cmList.h"
 #include "cmListFileCache.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
@@ -113,7 +114,7 @@
   std::vector<std::unique_ptr<cmCompiledGeneratorExpression>> result;
 
   for (auto const& entry : this->FileEntries) {
-    for (auto const& ex : cmExpandedList(entry.Value)) {
+    for (auto const& ex : cmList{ entry.Value }) {
       cmGeneratorExpression ge(this->CMakeInstance, entry.Backtrace);
       auto cge = ge.Parse(ex);
       result.push_back(std::move(cge));
@@ -129,7 +130,7 @@
   std::vector<std::unique_ptr<cmCompiledGeneratorExpression>> result;
 
   for (auto const& entry : this->DirectoryEntries) {
-    for (auto const& ex : cmExpandedList(entry.Value)) {
+    for (auto const& ex : cmList{ entry.Value }) {
       cmGeneratorExpression ge(this->CMakeInstance, entry.Backtrace);
       auto cge = ge.Parse(ex);
       result.push_back(std::move(cge));
@@ -148,7 +149,7 @@
   std::vector<std::string> result;
   for (auto const& cge : cges) {
     auto entry = cge->Evaluate(lg, config, target, dagChecker);
-    auto dirs = cmExpandedList(entry);
+    cmList dirs{ entry };
     for (std::string dir : dirs) {
       if (!cmSystemTools::FileIsFullPath(dir)) {
         dir = cmStrCat(lg->GetCurrentSourceDirectory(), '/', dir);
@@ -184,7 +185,7 @@
   cmGeneratorExpressionDAGChecker* dagChecker) const
 {
   auto files = cge->Evaluate(lg, config, target, dagChecker);
-  for (std::string file : cmExpandedList(files)) {
+  for (std::string file : cmList{ files }) {
     if (!cmSystemTools::FileIsFullPath(file)) {
       file = cmStrCat(lg->GetCurrentSourceDirectory(), '/', file);
     }
diff --git a/Source/cmFindBase.cxx b/Source/cmFindBase.cxx
index 4df81d5..5e92dd0 100644
--- a/Source/cmFindBase.cxx
+++ b/Source/cmFindBase.cxx
@@ -15,6 +15,7 @@
 
 #include "cmCMakePath.h"
 #include "cmExecutionStatus.h"
+#include "cmList.h"
 #include "cmListFileCache.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
@@ -154,7 +155,7 @@
       }
       // ensure a macro is not specified as validator
       const auto& validatorName = args[j];
-      auto macros = cmExpandedList(this->Makefile->GetProperty("MACROS"));
+      cmList macros{ this->Makefile->GetProperty("MACROS") };
       if (std::find_if(macros.begin(), macros.end(),
                        [&validatorName](const std::string& item) {
                          return cmSystemTools::Strucmp(validatorName.c_str(),
@@ -403,7 +404,7 @@
       this->Makefile->GetDefinition("CMAKE_SYSTEM_PREFIX_PATH");
 
     // remove entries from CMAKE_SYSTEM_PREFIX_PATH
-    std::vector<std::string> expanded = cmExpandedList(*prefix_paths);
+    cmList expanded{ *prefix_paths };
     install_entry.remove_self(expanded);
     staging_entry.remove_self(expanded);
 
@@ -508,7 +509,7 @@
       // value.
       if (value != *existingValue || this->AlreadyInCacheWithoutMetaInfo) {
         this->Makefile->GetCMakeInstance()->AddCacheEntry(
-          this->VariableName, value, this->VariableDocumentation.c_str(),
+          this->VariableName, value, this->VariableDocumentation,
           this->VariableType);
         if (this->Makefile->GetPolicyStatus(cmPolicies::CMP0126) ==
             cmPolicies::NEW) {
@@ -533,7 +534,7 @@
     if (this->StoreResultInCache) {
       if (this->AlreadyInCacheWithoutMetaInfo) {
         this->Makefile->AddCacheDefinition(this->VariableName, "",
-                                           this->VariableDocumentation.c_str(),
+                                           this->VariableDocumentation,
                                            this->VariableType);
         if (this->Makefile->GetPolicyStatus(cmPolicies::CMP0126) ==
               cmPolicies::NEW &&
@@ -563,7 +564,7 @@
   if (!value.empty()) {
     if (this->StoreResultInCache) {
       this->Makefile->AddCacheDefinition(this->VariableName, value,
-                                         this->VariableDocumentation.c_str(),
+                                         this->VariableDocumentation,
                                          this->VariableType, force);
       if (updateNormalVariable &&
           this->Makefile->IsNormalDefinitionSet(this->VariableName)) {
@@ -579,7 +580,7 @@
   auto notFound = cmStrCat(this->VariableName, "-NOTFOUND");
   if (this->StoreResultInCache) {
     this->Makefile->AddCacheDefinition(this->VariableName, notFound,
-                                       this->VariableDocumentation.c_str(),
+                                       this->VariableDocumentation,
                                        this->VariableType, force);
     if (updateNormalVariable &&
         this->Makefile->IsNormalDefinitionSet(this->VariableName)) {
diff --git a/Source/cmFindCommon.cxx b/Source/cmFindCommon.cxx
index c3fb907..bec6369 100644
--- a/Source/cmFindCommon.cxx
+++ b/Source/cmFindCommon.cxx
@@ -9,6 +9,7 @@
 #include <cmext/algorithm>
 
 #include "cmExecutionStatus.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
 #include "cmPolicies.h"
@@ -238,9 +239,9 @@
   }
 
   // Construct the list of path roots with no trailing slashes.
-  std::vector<std::string> roots;
+  cmList roots;
   if (rootPath) {
-    cmExpandList(*rootPath, roots);
+    roots.assign(*rootPath);
   }
   if (sysrootCompile) {
     roots.emplace_back(*sysrootCompile);
@@ -251,14 +252,14 @@
   if (sysroot) {
     roots.emplace_back(*sysroot);
   }
-  for (std::string& r : roots) {
+  for (auto& r : roots) {
     cmSystemTools::ConvertToUnixSlashes(r);
   }
 
   cmValue stagePrefix = this->Makefile->GetDefinition("CMAKE_STAGING_PREFIX");
 
   // Copy the original set of unrooted paths.
-  std::vector<std::string> unrootedPaths = paths;
+  auto unrootedPaths = paths;
   paths.clear();
 
   auto isSameDirectoryOrSubDirectory = [](std::string const& l,
@@ -267,8 +268,8 @@
       cmSystemTools::IsSubDirectory(l, r);
   };
 
-  for (std::string const& r : roots) {
-    for (std::string const& up : unrootedPaths) {
+  for (auto const& r : roots) {
+    for (auto const& up : unrootedPaths) {
       // Place the unrooted path under the current root if it is not
       // already inside.  Skip the unrooted path if it is relative to
       // a user home directory or is empty.
@@ -308,7 +309,7 @@
   // Construct the list of path roots with no trailing slashes.
   for (const char* pathName : paths) {
     // Get the list of paths to ignore from the variable.
-    this->Makefile->GetDefExpandList(pathName, ignore);
+    cmList::append(ignore, this->Makefile->GetDefinition(pathName));
   }
 
   for (std::string& i : ignore) {
@@ -333,7 +334,7 @@
   // Construct the list of path roots with no trailing slashes.
   for (const char* pathName : paths) {
     // Get the list of paths to ignore from the variable.
-    this->Makefile->GetDefExpandList(pathName, ignore);
+    cmList::append(ignore, this->Makefile->GetDefinition(pathName));
   }
 
   for (std::string& i : ignore) {
diff --git a/Source/cmFindLibraryCommand.cxx b/Source/cmFindLibraryCommand.cxx
index 6296a60..9eb0603 100644
--- a/Source/cmFindLibraryCommand.cxx
+++ b/Source/cmFindLibraryCommand.cxx
@@ -11,6 +11,7 @@
 #include "cmsys/RegularExpression.hxx"
 
 #include "cmGlobalGenerator.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmState.h"
 #include "cmStateTypes.h"
@@ -196,8 +197,8 @@
   cmGlobalGenerator* GG;
 
   // List of valid prefixes and suffixes.
-  std::vector<std::string> Prefixes;
-  std::vector<std::string> Suffixes;
+  cmList Prefixes;
+  cmList Suffixes;
   std::string PrefixRegexStr;
   std::string SuffixRegexStr;
 
@@ -223,7 +224,7 @@
   std::string TestPath;
 
   void RegexFromLiteral(std::string& out, std::string const& in);
-  void RegexFromList(std::string& out, std::vector<std::string> const& in);
+  void RegexFromList(std::string& out, cmList const& in);
   size_type GetPrefixIndex(std::string const& prefix)
   {
     return std::find(this->Prefixes.begin(), this->Prefixes.end(), prefix) -
@@ -307,8 +308,8 @@
   std::string const& prefixes_list = get_prefixes(this->Makefile);
   std::string const& suffixes_list = get_suffixes(this->Makefile);
 
-  cmExpandList(prefixes_list, this->Prefixes, true);
-  cmExpandList(suffixes_list, this->Suffixes, true);
+  this->Prefixes.assign(prefixes_list, cmList::EmptyElements::Yes);
+  this->Suffixes.assign(suffixes_list, cmList::EmptyElements::Yes);
   this->RegexFromList(this->PrefixRegexStr, this->Prefixes);
   this->RegexFromList(this->SuffixRegexStr, this->Suffixes);
 
@@ -334,14 +335,13 @@
   }
 }
 
-void cmFindLibraryHelper::RegexFromList(std::string& out,
-                                        std::vector<std::string> const& in)
+void cmFindLibraryHelper::RegexFromList(std::string& out, cmList const& in)
 {
   // Surround the list in parens so the '|' does not apply to anything
   // else and the result can be checked after matching.
   out += "(";
   const char* sep = "";
-  for (std::string const& s : in) {
+  for (auto const& s : in) {
     // Separate from previous item.
     out += sep;
     sep = "|";
diff --git a/Source/cmFindPackageCommand.cxx b/Source/cmFindPackageCommand.cxx
index c1b82ab..f863a51 100644
--- a/Source/cmFindPackageCommand.cxx
+++ b/Source/cmFindPackageCommand.cxx
@@ -23,6 +23,7 @@
 
 #include "cmAlgorithms.h"
 #include "cmDependencyProvider.h"
+#include "cmList.h"
 #include "cmListFileCache.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
@@ -456,6 +457,51 @@
 
 } // anonymous namespace
 
+class cmFindPackageCommand::FlushDebugBufferOnExit
+{
+  cmFindPackageCommand& Command;
+
+public:
+  FlushDebugBufferOnExit(cmFindPackageCommand& command)
+    : Command(command)
+  {
+  }
+  ~FlushDebugBufferOnExit()
+  {
+    if (!Command.DebugBuffer.empty()) {
+      Command.DebugMessage(Command.DebugBuffer);
+    }
+  }
+};
+
+class cmFindPackageCommand::PushPopRootPathStack
+{
+  cmFindPackageCommand& Command;
+
+public:
+  PushPopRootPathStack(cmFindPackageCommand& command)
+    : Command(command)
+  {
+    Command.PushFindPackageRootPathStack();
+  }
+  ~PushPopRootPathStack() { Command.PopFindPackageRootPathStack(); }
+};
+
+class cmFindPackageCommand::SetRestoreFindDefinitions
+{
+  cmFindPackageCommand& Command;
+
+public:
+  SetRestoreFindDefinitions(
+    cmFindPackageCommand& command, const std::string& components,
+    const std::vector<std::pair<std::string, const char*>>& componentVarDefs)
+    : Command(command)
+  {
+    Command.SetModuleVariables(components, componentVarDefs);
+  }
+  ~SetRestoreFindDefinitions() { Command.RestoreFindDefinitions(); }
+};
+
 cmFindPackageCommand::PathLabel
   cmFindPackageCommand::PathLabel::PackageRedirect("PACKAGE_REDIRECT");
 cmFindPackageCommand::PathLabel cmFindPackageCommand::PathLabel::UserRegistry(
@@ -502,6 +548,10 @@
   this->DebugMode = false;
   this->AppendSearchPathGroups();
 
+  this->DeprecatedFindModules["CUDA"] = cmPolicies::CMP0146;
+  this->DeprecatedFindModules["Dart"] = cmPolicies::CMP0145;
+  this->DeprecatedFindModules["PythonInterp"] = cmPolicies::CMP0148;
+  this->DeprecatedFindModules["PythonLibs"] = cmPolicies::CMP0148;
   this->DeprecatedFindModules["Qt"] = cmPolicies::CMP0084;
 }
 
@@ -975,37 +1025,26 @@
     }
   }
 
+  // Limit package nesting depth well below the recursion depth limit because
+  // find_package nesting uses more stack space than normal recursion.
   {
-    // Allocate a PACKAGE_ROOT_PATH for the current find_package call.
-    this->Makefile->FindPackageRootPathStack.emplace_back();
-    std::vector<std::string>& rootPaths =
-      this->Makefile->FindPackageRootPathStack.back();
-
-    // Add root paths from <PackageName>_ROOT CMake and environment variables,
-    // subject to CMP0074.
-    switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0074)) {
-      case cmPolicies::WARN:
-        this->Makefile->MaybeWarnCMP0074(this->Name);
-        CM_FALLTHROUGH;
-      case cmPolicies::OLD:
-        // OLD behavior is to ignore the <pkg>_ROOT variables.
-        break;
-      case cmPolicies::REQUIRED_IF_USED:
-      case cmPolicies::REQUIRED_ALWAYS:
-        this->Makefile->IssueMessage(
-          MessageType::FATAL_ERROR,
-          cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0074));
-        break;
-      case cmPolicies::NEW: {
-        // NEW behavior is to honor the <pkg>_ROOT variables.
-        std::string const rootVar = this->Name + "_ROOT";
-        this->Makefile->GetDefExpandList(rootVar, rootPaths, false);
-        cmSystemTools::GetPath(rootPaths, rootVar.c_str());
-      } break;
+    static std::size_t const findPackageDepthMinMax = 100;
+    std::size_t const findPackageDepthMax = std::max(
+      this->Makefile->GetRecursionDepthLimit() / 2, findPackageDepthMinMax);
+    std::size_t const findPackageDepth =
+      this->Makefile->FindPackageRootPathStack.size() + 1;
+    if (findPackageDepth > findPackageDepthMax) {
+      this->SetError(cmStrCat("maximum nesting depth of ", findPackageDepthMax,
+                              " exceeded."));
+      return false;
     }
   }
 
-  this->SetModuleVariables(components, componentVarDefs);
+  // RAII objects to ensure we leave this function with consistent state
+  FlushDebugBufferOnExit flushDebugBufferOnExit(*this);
+  PushPopRootPathStack pushPopRootPathStack(*this);
+  SetRestoreFindDefinitions setRestoreFindDefinitions(*this, components,
+                                                      componentVarDefs);
 
   // See if we have been told to delegate to FetchContent or some other
   // redirected config package first. We have to check all names that
@@ -1126,16 +1165,6 @@
 
   this->AppendSuccessInformation();
 
-  // Restore original state of "_FIND_" variables set in SetModuleVariables()
-  this->RestoreFindDefinitions();
-
-  // Pop the package stack
-  this->Makefile->FindPackageRootPathStack.pop_back();
-
-  if (!this->DebugBuffer.empty()) {
-    this->DebugMessage(this->DebugBuffer);
-  }
-
   return loadedPackage;
 }
 
@@ -1699,7 +1728,7 @@
   std::string const help =
     cmStrCat("The directory containing a CMake configuration file for ",
              this->Name, '.');
-  this->Makefile->AddCacheDefinition(this->Variable, value, help.c_str(),
+  this->Makefile->AddCacheDefinition(this->Variable, value, help,
                                      cmStateEnums::PATH, true);
   if (this->Makefile->GetPolicyStatus(cmPolicies::CMP0126) ==
         cmPolicies::NEW &&
@@ -1753,28 +1782,20 @@
 
 void cmFindPackageCommand::AppendToFoundProperty(const bool found)
 {
-  std::vector<std::string> foundContents;
+  cmList foundContents;
   cmValue foundProp =
     this->Makefile->GetState()->GetGlobalProperty("PACKAGES_FOUND");
-  if (cmNonempty(foundProp)) {
-    cmExpandList(*foundProp, foundContents, false);
-    auto nameIt =
-      std::find(foundContents.begin(), foundContents.end(), this->Name);
-    if (nameIt != foundContents.end()) {
-      foundContents.erase(nameIt);
-    }
+  if (!foundProp.IsEmpty()) {
+    foundContents.assign(*foundProp);
+    foundContents.remove_items({ this->Name });
   }
 
-  std::vector<std::string> notFoundContents;
+  cmList notFoundContents;
   cmValue notFoundProp =
     this->Makefile->GetState()->GetGlobalProperty("PACKAGES_NOT_FOUND");
-  if (cmNonempty(notFoundProp)) {
-    cmExpandList(*notFoundProp, notFoundContents, false);
-    auto nameIt =
-      std::find(notFoundContents.begin(), notFoundContents.end(), this->Name);
-    if (nameIt != notFoundContents.end()) {
-      notFoundContents.erase(nameIt);
-    }
+  if (!notFoundProp.IsEmpty()) {
+    notFoundContents.assign(*notFoundProp);
+    notFoundContents.remove_items({ this->Name });
   }
 
   if (found) {
@@ -1783,12 +1804,11 @@
     notFoundContents.push_back(this->Name);
   }
 
-  std::string tmp = cmJoin(foundContents, ";");
-  this->Makefile->GetState()->SetGlobalProperty("PACKAGES_FOUND", tmp.c_str());
+  this->Makefile->GetState()->SetGlobalProperty("PACKAGES_FOUND",
+                                                foundContents.to_string());
 
-  tmp = cmJoin(notFoundContents, ";");
   this->Makefile->GetState()->SetGlobalProperty("PACKAGES_NOT_FOUND",
-                                                tmp.c_str());
+                                                notFoundContents.to_string());
 }
 
 void cmFindPackageCommand::AppendSuccessInformation()
@@ -1825,7 +1845,7 @@
       cmStrCat(this->VersionExact ? "==" : ">=", ' ', this->Version);
   }
   this->Makefile->GetState()->SetGlobalProperty(versionInfoPropName,
-                                                versionInfo.c_str());
+                                                versionInfo);
   if (this->Required) {
     std::string const requiredInfoPropName =
       cmStrCat("_CMAKE_", this->Name, "_TYPE");
@@ -1834,6 +1854,99 @@
   }
 }
 
+void cmFindPackageCommand::PushFindPackageRootPathStack()
+{
+  // Allocate a PACKAGE_ROOT_PATH for the current find_package call.
+  this->Makefile->FindPackageRootPathStack.emplace_back();
+  std::vector<std::string>& rootPaths =
+    this->Makefile->FindPackageRootPathStack.back();
+
+  // Add root paths from <PackageName>_ROOT CMake and environment variables,
+  // subject to CMP0074.
+  std::string const rootVar = this->Name + "_ROOT";
+  cmValue rootDef = this->Makefile->GetDefinition(rootVar);
+  if (rootDef && rootDef.IsEmpty()) {
+    rootDef = nullptr;
+  }
+  cm::optional<std::string> rootEnv = cmSystemTools::GetEnvVar(rootVar);
+  if (rootEnv && rootEnv->empty()) {
+    rootEnv = cm::nullopt;
+  }
+  switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0074)) {
+    case cmPolicies::WARN:
+      this->Makefile->MaybeWarnCMP0074(rootVar, rootDef, rootEnv);
+      CM_FALLTHROUGH;
+    case cmPolicies::OLD:
+      // OLD behavior is to ignore the <PackageName>_ROOT variables.
+      return;
+    case cmPolicies::REQUIRED_IF_USED:
+    case cmPolicies::REQUIRED_ALWAYS:
+      this->Makefile->IssueMessage(
+        MessageType::FATAL_ERROR,
+        cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0074));
+      return;
+    case cmPolicies::NEW: {
+      // NEW behavior is to honor the <PackageName>_ROOT variables.
+    } break;
+  }
+
+  // Add root paths from <PACKAGENAME>_ROOT CMake and environment variables,
+  // if they are different than <PackageName>_ROOT, and subject to CMP0144.
+  std::string const rootVAR = cmSystemTools::UpperCase(rootVar);
+  cmValue rootDEF;
+  cm::optional<std::string> rootENV;
+  if (rootVAR != rootVar) {
+    rootDEF = this->Makefile->GetDefinition(rootVAR);
+    if (rootDEF && (rootDEF.IsEmpty() || rootDEF == rootDef)) {
+      rootDEF = nullptr;
+    }
+    rootENV = cmSystemTools::GetEnvVar(rootVAR);
+    if (rootENV && (rootENV->empty() || rootENV == rootEnv)) {
+      rootENV = cm::nullopt;
+    }
+  }
+
+  switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0144)) {
+    case cmPolicies::WARN:
+      this->Makefile->MaybeWarnCMP0144(rootVAR, rootDEF, rootENV);
+      CM_FALLTHROUGH;
+    case cmPolicies::OLD:
+      // OLD behavior is to ignore the <PACKAGENAME>_ROOT variables.
+      rootDEF = nullptr;
+      rootENV = cm::nullopt;
+      break;
+    case cmPolicies::REQUIRED_IF_USED:
+    case cmPolicies::REQUIRED_ALWAYS:
+      this->Makefile->IssueMessage(
+        MessageType::FATAL_ERROR,
+        cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0144));
+      return;
+    case cmPolicies::NEW: {
+      // NEW behavior is to honor the <PACKAGENAME>_ROOT variables.
+    } break;
+  }
+
+  if (rootDef) {
+    cmExpandList(*rootDef, rootPaths);
+  }
+  if (rootDEF) {
+    cmExpandList(*rootDEF, rootPaths);
+  }
+  if (rootEnv) {
+    std::vector<std::string> p = cmSystemTools::SplitEnvPath(*rootEnv);
+    std::move(p.begin(), p.end(), std::back_inserter(rootPaths));
+  }
+  if (rootENV) {
+    std::vector<std::string> p = cmSystemTools::SplitEnvPath(*rootENV);
+    std::move(p.begin(), p.end(), std::back_inserter(rootPaths));
+  }
+}
+
+void cmFindPackageCommand::PopFindPackageRootPathStack()
+{
+  this->Makefile->FindPackageRootPathStack.pop_back();
+}
+
 void cmFindPackageCommand::ComputePrefixes()
 {
   this->FillPrefixesPackageRedirect();
@@ -2217,7 +2330,7 @@
     cmValue prefix_paths =
       this->Makefile->GetDefinition("CMAKE_SYSTEM_PREFIX_PATH");
     // remove entry from CMAKE_SYSTEM_PREFIX_PATH
-    std::vector<std::string> expanded = cmExpandedList(*prefix_paths);
+    cmList expanded{ *prefix_paths };
     long count = 0;
     for (const auto& path : expanded) {
       bool const to_add =
diff --git a/Source/cmFindPackageCommand.h b/Source/cmFindPackageCommand.h
index 28e00a1..653e15f 100644
--- a/Source/cmFindPackageCommand.h
+++ b/Source/cmFindPackageCommand.h
@@ -105,6 +105,8 @@
   void AddFindDefinition(const std::string& var, cm::string_view value);
   void RestoreFindDefinitions();
 
+  class SetRestoreFindDefinitions;
+
   enum /*class*/ HandlePackageModeType
   {
     Module,
@@ -125,6 +127,10 @@
   void StoreVersionFound();
   void SetConfigDirCacheVariable(const std::string& value);
 
+  void PushFindPackageRootPathStack();
+  void PopFindPackageRootPathStack();
+  class PushPopRootPathStack;
+
   void ComputePrefixes();
   void FillPrefixesPackageRedirect();
   void FillPrefixesPackageRoot();
@@ -212,6 +218,8 @@
   std::set<std::string> IgnoredPrefixPaths;
   std::string DebugBuffer;
 
+  class FlushDebugBufferOnExit;
+
   /*! the selected sortOrder (None by default)*/
   SortOrderType SortOrder = None;
   /*! the selected sortDirection (Asc by default)*/
diff --git a/Source/cmForEachCommand.cxx b/Source/cmForEachCommand.cxx
index 3465c23..21a140d 100644
--- a/Source/cmForEachCommand.cxx
+++ b/Source/cmForEachCommand.cxx
@@ -23,6 +23,7 @@
 
 #include "cmExecutionStatus.h"
 #include "cmFunctionBlocker.h"
+#include "cmList.h"
 #include "cmListFileCache.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
@@ -156,16 +157,16 @@
   auto& mf = inStatus.GetMakefile();
 
   // Expand the list of list-variables into a list of lists of strings
-  std::vector<std::vector<std::string>> values;
+  std::vector<cmList> values;
   values.reserve(this->Args.size() - this->IterationVarsCount);
   // Also track the longest list size
   std::size_t maxItems = 0u;
   for (auto const& var :
        cmMakeRange(this->Args).advance(this->IterationVarsCount)) {
-    std::vector<std::string> items;
+    cmList items;
     auto const& value = mf.GetSafeDefinition(var);
     if (!value.empty()) {
-      cmExpandList(value, items, true);
+      items.assign(value, cmList::EmptyElements::Yes);
     }
     maxItems = std::max(maxItems, items.size());
     values.emplace_back(std::move(items));
@@ -344,7 +345,7 @@
     } else if (doing == DoingLists) {
       auto const& value = makefile.GetSafeDefinition(arg);
       if (!value.empty()) {
-        cmExpandList(value, fb->Args, true);
+        cmExpandList(value, fb->Args, cmList::EmptyElements::Yes);
       }
 
     } else if (doing == DoingItems || doing == DoingZipLists) {
diff --git a/Source/cmGccDepfileLexerHelper.cxx b/Source/cmGccDepfileLexerHelper.cxx
index 34c8824..87377de 100644
--- a/Source/cmGccDepfileLexerHelper.cxx
+++ b/Source/cmGccDepfileLexerHelper.cxx
@@ -2,6 +2,7 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmGccDepfileLexerHelper.h"
 
+#include <algorithm>
 #include <cstdio>
 #include <memory>
 #include <string>
@@ -113,6 +114,11 @@
 void cmGccDepfileLexerHelper::sanitizeContent()
 {
   for (auto it = this->Content.begin(); it != this->Content.end();) {
+    // remove duplicate path entries
+    std::sort(it->paths.begin(), it->paths.end());
+    auto last = std::unique(it->paths.begin(), it->paths.end());
+    it->paths.erase(last, it->paths.end());
+
     // Remove empty paths and normalize windows paths
     for (auto pit = it->paths.begin(); pit != it->paths.end();) {
       if (pit->empty()) {
diff --git a/Source/cmGeneratorExpression.cxx b/Source/cmGeneratorExpression.cxx
index c5ae31b..04decd2 100644
--- a/Source/cmGeneratorExpression.cxx
+++ b/Source/cmGeneratorExpression.cxx
@@ -14,6 +14,7 @@
 #include "cmGeneratorExpressionEvaluator.h"
 #include "cmGeneratorExpressionLexer.h"
 #include "cmGeneratorExpressionParser.h"
+#include "cmList.h"
 #include "cmLocalGenerator.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
diff --git a/Source/cmGeneratorExpressionDAGChecker.cxx b/Source/cmGeneratorExpressionDAGChecker.cxx
index 6be5153..d51dbd0 100644
--- a/Source/cmGeneratorExpressionDAGChecker.cxx
+++ b/Source/cmGeneratorExpressionDAGChecker.cxx
@@ -27,6 +27,7 @@
   , Content(content)
   , Backtrace(std::move(backtrace))
   , TransitivePropertiesOnly(false)
+  , CMP0131(false)
 {
   this->Initialize();
 }
@@ -41,6 +42,7 @@
   , Content(content)
   , Backtrace()
   , TransitivePropertiesOnly(false)
+  , CMP0131(false)
 {
   this->Initialize();
 }
@@ -143,6 +145,12 @@
   return this->Top()->TransitivePropertiesOnly;
 }
 
+bool cmGeneratorExpressionDAGChecker::GetTransitivePropertiesOnlyCMP0131()
+  const
+{
+  return this->Top()->CMP0131;
+}
+
 bool cmGeneratorExpressionDAGChecker::EvaluatingGenexExpression() const
 {
   return cmHasLiteralPrefix(this->Property, "TARGET_GENEX_EVAL:") ||
@@ -177,6 +185,15 @@
   return property == "LINK_OPTIONS"_s;
 }
 
+bool cmGeneratorExpressionDAGChecker::EvaluatingLinkerLauncher() const
+{
+  cm::string_view property(this->Top()->Property);
+
+  return property.length() > cmStrLen("_LINKER_LAUNCHER") &&
+    property.substr(property.length() - cmStrLen("_LINKER_LAUNCHER")) ==
+    "_LINKER_LAUNCHER"_s;
+}
+
 bool cmGeneratorExpressionDAGChecker::EvaluatingLinkLibraries(
   cmGeneratorTarget const* tgt, ForGenex genex) const
 {
diff --git a/Source/cmGeneratorExpressionDAGChecker.h b/Source/cmGeneratorExpressionDAGChecker.h
index 55d131f..1919b01 100644
--- a/Source/cmGeneratorExpressionDAGChecker.h
+++ b/Source/cmGeneratorExpressionDAGChecker.h
@@ -70,6 +70,7 @@
   bool EvaluatingCompileExpression() const;
   bool EvaluatingLinkExpression() const;
   bool EvaluatingLinkOptionsExpression() const;
+  bool EvaluatingLinkerLauncher() const;
 
   enum class ForGenex
   {
@@ -89,6 +90,9 @@
   bool GetTransitivePropertiesOnly() const;
   void SetTransitivePropertiesOnly() { this->TransitivePropertiesOnly = true; }
 
+  bool GetTransitivePropertiesOnlyCMP0131() const;
+  void SetTransitivePropertiesOnlyCMP0131() { this->CMP0131 = true; }
+
   cmGeneratorExpressionDAGChecker const* Top() const;
   cmGeneratorTarget const* TopTarget() const;
 
@@ -104,4 +108,5 @@
   const cmListFileBacktrace Backtrace;
   Result CheckResult;
   bool TransitivePropertiesOnly;
+  bool CMP0131;
 };
diff --git a/Source/cmGeneratorExpressionNode.cxx b/Source/cmGeneratorExpressionNode.cxx
index 6595323..bb4fc7e 100644
--- a/Source/cmGeneratorExpressionNode.cxx
+++ b/Source/cmGeneratorExpressionNode.cxx
@@ -12,20 +12,19 @@
 #include <memory>
 #include <set>
 #include <sstream>
+#include <stdexcept>
 #include <unordered_map>
 #include <utility>
 
 #include <cm/iterator>
 #include <cm/optional>
 #include <cm/string_view>
-#include <cm/vector>
 #include <cmext/algorithm>
 #include <cmext/string_view>
 
 #include "cmsys/RegularExpression.hxx"
 #include "cmsys/String.h"
 
-#include "cmAlgorithms.h"
 #include "cmCMakePath.h"
 #include "cmComputeLinkInformation.h"
 #include "cmGeneratorExpression.h"
@@ -35,6 +34,7 @@
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
 #include "cmLinkItem.h"
+#include "cmList.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
@@ -291,18 +291,18 @@
     const GeneratorExpressionContent* /*content*/,
     cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override
   {
-    std::vector<std::string> values;
-    std::vector<std::string> checkValues;
+    cmList values;
+    cmList checkValues;
     bool check = false;
     switch (context->LG->GetPolicyStatus(cmPolicies::CMP0085)) {
       case cmPolicies::WARN:
         if (parameters.front().empty()) {
           check = true;
-          cmExpandList(parameters[1], checkValues, true);
+          checkValues.assign(parameters[1], cmList::EmptyElements::Yes);
         }
         CM_FALLTHROUGH;
       case cmPolicies::OLD:
-        cmExpandList(parameters[1], values);
+        values.assign(parameters[1]);
         if (check && values != checkValues) {
           std::ostringstream e;
           e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0085)
@@ -319,11 +319,11 @@
       case cmPolicies::REQUIRED_IF_USED:
       case cmPolicies::REQUIRED_ALWAYS:
       case cmPolicies::NEW:
-        cmExpandList(parameters[1], values, true);
+        values.assign(parameters[1], cmList::EmptyElements::Yes);
         break;
     }
 
-    return cm::contains(values, parameters.front()) ? "1" : "0";
+    return values.find(parameters.front()) != cmList::npos ? "1" : "0";
   }
 } inListNode;
 
@@ -352,24 +352,17 @@
       return {};
     }
 
-    const bool exclude = parameters[1] == "EXCLUDE";
-
-    cmsys::RegularExpression re;
-    if (!re.compile(parameters[2])) {
+    try {
+      return cmList{ parameters.front(), cmList::EmptyElements::Yes }
+        .filter(parameters[2],
+                parameters[1] == "EXCLUDE" ? cmList::FilterMode::EXCLUDE
+                                           : cmList::FilterMode::INCLUDE)
+        .to_string();
+    } catch (std::invalid_argument&) {
       reportError(context, content->GetOriginalExpression(),
                   "$<FILTER:...> failed to compile regex");
       return {};
     }
-
-    std::vector<std::string> values;
-    std::vector<std::string> result;
-    cmExpandList(parameters.front(), values, true);
-
-    std::copy_if(values.cbegin(), values.cend(), std::back_inserter(result),
-                 [&re, exclude](std::string const& input) {
-                   return exclude ^ re.find(input);
-                 });
-    return cmJoin(cmMakeRange(result.cbegin(), result.cend()), ";");
   }
 } filterNode;
 
@@ -391,11 +384,7 @@
         "$<REMOVE_DUPLICATES:...> expression requires one parameter");
     }
 
-    std::vector<std::string> values = cmExpandedList(parameters.front(), true);
-
-    auto valuesEnd = cmRemoveDuplicates(values);
-    auto valuesBegin = values.cbegin();
-    return cmJoin(cmMakeRange(valuesBegin, valuesEnd), ";");
+    return cmList{ parameters.front() }.remove_duplicates().to_string();
   }
 
 } removeDuplicatesNode;
@@ -645,22 +634,48 @@
 
 using Arguments = Range<std::vector<std::string>>;
 
-bool CheckPathParametersEx(cmGeneratorExpressionContext* ctx,
-                           const GeneratorExpressionContent* cnt,
-                           cm::string_view option, std::size_t count,
-                           int required = 1, bool exactly = true)
+bool CheckGenExParameters(cmGeneratorExpressionContext* ctx,
+                          const GeneratorExpressionContent* cnt,
+                          cm::string_view genex, cm::string_view option,
+                          std::size_t count, int required = 1,
+                          bool exactly = true)
 {
   if (static_cast<int>(count) < required ||
       (exactly && static_cast<int>(count) > required)) {
+    std::string nbParameters;
+    switch (required) {
+      case 1:
+        nbParameters = "one parameter";
+        break;
+      case 2:
+        nbParameters = "two parameters";
+        break;
+      case 3:
+        nbParameters = "three parameters";
+        break;
+      case 4:
+        nbParameters = "four parameters";
+        break;
+      default:
+        nbParameters = cmStrCat(std::to_string(required), " parameters");
+    }
     reportError(ctx, cnt->GetOriginalExpression(),
-                cmStrCat("$<PATH:", option, "> expression requires ",
-                         (exactly ? "exactly" : "at least"), ' ',
-                         (required == 1 ? "one parameter" : "two parameters"),
+                cmStrCat("$<", genex, ':', option, "> expression requires ",
+                         (exactly ? "exactly" : "at least"), ' ', nbParameters,
                          '.'));
     return false;
   }
   return true;
 };
+
+bool CheckPathParametersEx(cmGeneratorExpressionContext* ctx,
+                           const GeneratorExpressionContent* cnt,
+                           cm::string_view option, std::size_t count,
+                           int required = 1, bool exactly = true)
+{
+  return CheckGenExParameters(ctx, cnt, "PATH"_s, option, count, required,
+                              exactly);
+}
 bool CheckPathParameters(cmGeneratorExpressionContext* ctx,
                          const GeneratorExpressionContent* cnt,
                          cm::string_view option, const Arguments& args,
@@ -668,6 +683,7 @@
 {
   return CheckPathParametersEx(ctx, cnt, option, args.size(), required);
 };
+
 std::string ToString(bool isTrue)
 {
   return isTrue ? "1" : "0";
@@ -688,6 +704,14 @@
     const GeneratorExpressionContent* content,
     cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override
   {
+    static auto processList =
+      [](std::string const& arg,
+         std::function<void(std::string&)> transform) -> std::string {
+      cmList list{ arg };
+      std::for_each(list.begin(), list.end(), std::move(transform));
+      return list.to_string();
+    };
+
     static std::unordered_map<
       cm::string_view,
       std::function<std::string(cmGeneratorExpressionContext*,
@@ -698,38 +722,49 @@
           [](cmGeneratorExpressionContext* ctx,
              const GeneratorExpressionContent* cnt,
              Arguments& args) -> std::string {
-            return CheckPathParameters(ctx, cnt, "GET_ROOT_NAME"_s, args) &&
-                !args.front().empty()
-              ? cmCMakePath{ args.front() }.GetRootName().String()
-              : std::string{};
+            if (CheckPathParameters(ctx, cnt, "GET_ROOT_NAME"_s, args) &&
+                !args.front().empty()) {
+              return processList(args.front(), [](std::string& value) {
+                value = cmCMakePath{ value }.GetRootName().String();
+              });
+            }
+            return std::string{};
           } },
         { "GET_ROOT_DIRECTORY"_s,
           [](cmGeneratorExpressionContext* ctx,
              const GeneratorExpressionContent* cnt,
              Arguments& args) -> std::string {
-            return CheckPathParameters(ctx, cnt, "GET_ROOT_DIRECTORY"_s,
-                                       args) &&
-                !args.front().empty()
-              ? cmCMakePath{ args.front() }.GetRootDirectory().String()
-              : std::string{};
+            if (CheckPathParameters(ctx, cnt, "GET_ROOT_DIRECTORY"_s, args) &&
+                !args.front().empty()) {
+              return processList(args.front(), [](std::string& value) {
+                value = cmCMakePath{ value }.GetRootDirectory().String();
+              });
+            }
+            return std::string{};
           } },
         { "GET_ROOT_PATH"_s,
           [](cmGeneratorExpressionContext* ctx,
              const GeneratorExpressionContent* cnt,
              Arguments& args) -> std::string {
-            return CheckPathParameters(ctx, cnt, "GET_ROOT_PATH"_s, args) &&
-                !args.front().empty()
-              ? cmCMakePath{ args.front() }.GetRootPath().String()
-              : std::string{};
+            if (CheckPathParameters(ctx, cnt, "GET_ROOT_PATH"_s, args) &&
+                !args.front().empty()) {
+              return processList(args.front(), [](std::string& value) {
+                value = cmCMakePath{ value }.GetRootPath().String();
+              });
+            }
+            return std::string{};
           } },
         { "GET_FILENAME"_s,
           [](cmGeneratorExpressionContext* ctx,
              const GeneratorExpressionContent* cnt,
              Arguments& args) -> std::string {
-            return CheckPathParameters(ctx, cnt, "GET_FILENAME"_s, args) &&
-                !args.front().empty()
-              ? cmCMakePath{ args.front() }.GetFileName().String()
-              : std::string{};
+            if (CheckPathParameters(ctx, cnt, "GET_FILENAME"_s, args) &&
+                !args.front().empty()) {
+              return processList(args.front(), [](std::string& value) {
+                value = cmCMakePath{ value }.GetFileName().String();
+              });
+            }
+            return std::string{};
           } },
         { "GET_EXTENSION"_s,
           [](cmGeneratorExpressionContext* ctx,
@@ -746,9 +781,14 @@
               if (args.front().empty()) {
                 return std::string{};
               }
-              return lastOnly
-                ? cmCMakePath{ args.front() }.GetExtension().String()
-                : cmCMakePath{ args.front() }.GetWideExtension().String();
+              if (lastOnly) {
+                return processList(args.front(), [](std::string& value) {
+                  value = cmCMakePath{ value }.GetExtension().String();
+                });
+              }
+              return processList(args.front(), [](std::string& value) {
+                value = cmCMakePath{ value }.GetWideExtension().String();
+              });
             }
             return std::string{};
           } },
@@ -766,9 +806,14 @@
               if (args.front().empty()) {
                 return std::string{};
               }
-              return lastOnly
-                ? cmCMakePath{ args.front() }.GetStem().String()
-                : cmCMakePath{ args.front() }.GetNarrowStem().String();
+              if (lastOnly) {
+                return processList(args.front(), [](std::string& value) {
+                  value = cmCMakePath{ value }.GetStem().String();
+                });
+              }
+              return processList(args.front(), [](std::string& value) {
+                value = cmCMakePath{ value }.GetNarrowStem().String();
+              });
             }
             return std::string{};
           } },
@@ -776,19 +821,24 @@
           [](cmGeneratorExpressionContext* ctx,
              const GeneratorExpressionContent* cnt,
              Arguments& args) -> std::string {
-            return CheckPathParameters(ctx, cnt, "GET_RELATIVE_PART"_s,
-                                       args) &&
-                !args.front().empty()
-              ? cmCMakePath{ args.front() }.GetRelativePath().String()
-              : std::string{};
+            if (CheckPathParameters(ctx, cnt, "GET_RELATIVE_PART"_s, args) &&
+                !args.front().empty()) {
+              return processList(args.front(), [](std::string& value) {
+                value = cmCMakePath{ value }.GetRelativePath().String();
+              });
+            }
+            return std::string{};
           } },
         { "GET_PARENT_PATH"_s,
           [](cmGeneratorExpressionContext* ctx,
              const GeneratorExpressionContent* cnt,
              Arguments& args) -> std::string {
-            return CheckPathParameters(ctx, cnt, "GET_PARENT_PATH"_s, args)
-              ? cmCMakePath{ args.front() }.GetParentPath().String()
-              : std::string{};
+            if (CheckPathParameters(ctx, cnt, "GET_PARENT_PATH"_s, args)) {
+              return processList(args.front(), [](std::string& value) {
+                value = cmCMakePath{ value }.GetParentPath().String();
+              });
+            }
+            return std::string{};
           } },
         { "HAS_ROOT_NAME"_s,
           [](cmGeneratorExpressionContext* ctx,
@@ -904,10 +954,12 @@
                                       normalize ? "CMAKE_PATH,NORMALIZE"_s
                                                 : "CMAKE_PATH"_s,
                                       args.size(), 1)) {
-              auto path =
-                cmCMakePath{ args.front(), cmCMakePath::auto_format };
-              return normalize ? path.Normal().GenericString()
-                               : path.GenericString();
+              return processList(
+                args.front(), [normalize](std::string& value) {
+                  auto path = cmCMakePath{ value, cmCMakePath::auto_format };
+                  value = normalize ? path.Normal().GenericString()
+                                    : path.GenericString();
+                });
             }
             return std::string{};
           } },
@@ -917,11 +969,16 @@
              Arguments& args) -> std::string {
             if (CheckPathParametersEx(ctx, cnt, "APPEND"_s, args.size(), 1,
                                       false)) {
-              cmCMakePath path;
-              for (const auto& p : args) {
-                path /= p;
-              }
-              return path.String();
+              auto const& list = args.front();
+              args.advance(1);
+
+              return processList(list, [&args](std::string& value) {
+                cmCMakePath path{ value };
+                for (const auto& p : args) {
+                  path /= p;
+                }
+                value = path.String();
+              });
             }
             return std::string{};
           } },
@@ -929,20 +986,26 @@
           [](cmGeneratorExpressionContext* ctx,
              const GeneratorExpressionContent* cnt,
              Arguments& args) -> std::string {
-            return CheckPathParameters(ctx, cnt, "REMOVE_FILENAME"_s, args) &&
-                !args.front().empty()
-              ? cmCMakePath{ args.front() }.RemoveFileName().String()
-              : std::string{};
+            if (CheckPathParameters(ctx, cnt, "REMOVE_FILENAME"_s, args) &&
+                !args.front().empty()) {
+              return processList(args.front(), [](std::string& value) {
+                value = cmCMakePath{ value }.RemoveFileName().String();
+              });
+            }
+            return std::string{};
           } },
         { "REPLACE_FILENAME"_s,
           [](cmGeneratorExpressionContext* ctx,
              const GeneratorExpressionContent* cnt,
              Arguments& args) -> std::string {
-            return CheckPathParameters(ctx, cnt, "REPLACE_FILENAME"_s, args, 2)
-              ? cmCMakePath{ args[0] }
-                  .ReplaceFileName(cmCMakePath{ args[1] })
-                  .String()
-              : std::string{};
+            if (CheckPathParameters(ctx, cnt, "REPLACE_FILENAME"_s, args, 2)) {
+              return processList(args.front(), [&args](std::string& value) {
+                value = cmCMakePath{ value }
+                          .ReplaceFileName(cmCMakePath{ args[1] })
+                          .String();
+              });
+            }
+            return std::string{};
           } },
         { "REMOVE_EXTENSION"_s,
           [](cmGeneratorExpressionContext* ctx,
@@ -959,9 +1022,14 @@
               if (args.front().empty()) {
                 return std::string{};
               }
-              return lastOnly
-                ? cmCMakePath{ args.front() }.RemoveExtension().String()
-                : cmCMakePath{ args.front() }.RemoveWideExtension().String();
+              if (lastOnly) {
+                return processList(args.front(), [](std::string& value) {
+                  value = cmCMakePath{ value }.RemoveExtension().String();
+                });
+              }
+              return processList(args.front(), [](std::string& value) {
+                value = cmCMakePath{ value }.RemoveWideExtension().String();
+              });
             }
             return std::string{};
           } },
@@ -979,13 +1047,17 @@
                                         : "REPLACE_EXTENSION"_s,
                                       args.size(), 2)) {
               if (lastOnly) {
-                return cmCMakePath{ args[0] }
-                  .ReplaceExtension(cmCMakePath{ args[1] })
-                  .String();
+                return processList(args.front(), [&args](std::string& value) {
+                  value = cmCMakePath{ value }
+                            .ReplaceExtension(cmCMakePath{ args[1] })
+                            .String();
+                });
               }
-              return cmCMakePath{ args[0] }
-                .ReplaceWideExtension(cmCMakePath{ args[1] })
-                .String();
+              return processList(args.front(), [&args](std::string& value) {
+                value = cmCMakePath{ value }
+                          .ReplaceWideExtension(cmCMakePath{ args[1] })
+                          .String();
+              });
             }
             return std::string{};
           } },
@@ -993,18 +1065,24 @@
           [](cmGeneratorExpressionContext* ctx,
              const GeneratorExpressionContent* cnt,
              Arguments& args) -> std::string {
-            return CheckPathParameters(ctx, cnt, "NORMAL_PATH"_s, args) &&
-                !args.front().empty()
-              ? cmCMakePath{ args.front() }.Normal().String()
-              : std::string{};
+            if (CheckPathParameters(ctx, cnt, "NORMAL_PATH"_s, args) &&
+                !args.front().empty()) {
+              return processList(args.front(), [](std::string& value) {
+                value = cmCMakePath{ value }.Normal().String();
+              });
+            }
+            return std::string{};
           } },
         { "RELATIVE_PATH"_s,
           [](cmGeneratorExpressionContext* ctx,
              const GeneratorExpressionContent* cnt,
              Arguments& args) -> std::string {
-            return CheckPathParameters(ctx, cnt, "RELATIVE_PATH"_s, args, 2)
-              ? cmCMakePath{ args[0] }.Relative(args[1]).String()
-              : std::string{};
+            if (CheckPathParameters(ctx, cnt, "RELATIVE_PATH"_s, args, 2)) {
+              return processList(args.front(), [&args](std::string& value) {
+                value = cmCMakePath{ value }.Relative(args[1]).String();
+              });
+            }
+            return std::string{};
           } },
         { "ABSOLUTE_PATH"_s,
           [](cmGeneratorExpressionContext* ctx,
@@ -1018,8 +1096,11 @@
                                       normalize ? "ABSOLUTE_PATH,NORMALIZE"_s
                                                 : "ABSOLUTE_PATH"_s,
                                       args.size(), 2)) {
-              auto path = cmCMakePath{ args[0] }.Absolute(args[1]);
-              return normalize ? path.Normal().String() : path.String();
+              return processList(
+                args.front(), [&args, normalize](std::string& value) {
+                  auto path = cmCMakePath{ value }.Absolute(args[1]);
+                  value = normalize ? path.Normal().String() : path.String();
+                });
             }
             return std::string{};
           } }
@@ -1053,6 +1134,675 @@
   }
 } pathEqualNode;
 
+namespace {
+inline bool CheckListParametersEx(cmGeneratorExpressionContext* ctx,
+                                  const GeneratorExpressionContent* cnt,
+                                  cm::string_view option, std::size_t count,
+                                  int required = 1, bool exactly = true)
+{
+  return CheckGenExParameters(ctx, cnt, "LIST"_s, option, count, required,
+                              exactly);
+}
+inline bool CheckListParameters(cmGeneratorExpressionContext* ctx,
+                                const GeneratorExpressionContent* cnt,
+                                cm::string_view option, const Arguments& args,
+                                int required = 1)
+{
+  return CheckListParametersEx(ctx, cnt, option, args.size(), required);
+};
+
+inline cmList GetList(std::string const& list)
+{
+  return list.empty() ? cmList{} : cmList{ list, cmList::EmptyElements::Yes };
+}
+
+bool GetNumericArgument(const std::string& arg, cmList::index_type& value)
+{
+  try {
+    std::size_t pos;
+
+    if (sizeof(cmList::index_type) == sizeof(long)) {
+      value = std::stol(arg, &pos);
+    } else {
+      value = std::stoll(arg, &pos);
+    }
+
+    if (pos != arg.length()) {
+      // this is not a number
+      return false;
+    }
+  } catch (const std::invalid_argument&) {
+    return false;
+  }
+
+  return true;
+}
+
+bool GetNumericArguments(
+  cmGeneratorExpressionContext* ctx, const GeneratorExpressionContent* cnt,
+  Arguments const& args, std::vector<cmList::index_type>& indexes,
+  cmList::ExpandElements expandElements = cmList::ExpandElements::No)
+{
+  using IndexRange = cmRange<Arguments::const_iterator>;
+  IndexRange arguments(args.begin(), args.end());
+  cmList list;
+  if (expandElements == cmList::ExpandElements::Yes) {
+    list = cmList{ args.begin(), args.end(), expandElements };
+    arguments = IndexRange{ list.begin(), list.end() };
+  }
+
+  for (auto const& value : arguments) {
+    cmList::index_type index;
+    if (!GetNumericArgument(value, index)) {
+      reportError(ctx, cnt->GetOriginalExpression(),
+                  cmStrCat("index: \"", value, "\" is not a valid index"));
+      return false;
+    }
+    indexes.push_back(index);
+  }
+  return true;
+}
+}
+
+static const struct ListNode : public cmGeneratorExpressionNode
+{
+  ListNode() {} // NOLINT(modernize-use-equals-default)
+
+  int NumExpectedParameters() const override { return TwoOrMoreParameters; }
+
+  bool AcceptsArbitraryContentParameter() const override { return true; }
+
+  std::string Evaluate(
+    const std::vector<std::string>& parameters,
+    cmGeneratorExpressionContext* context,
+    const GeneratorExpressionContent* content,
+    cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override
+  {
+    static std::unordered_map<
+      cm::string_view,
+      std::function<std::string(cmGeneratorExpressionContext*,
+                                const GeneratorExpressionContent*,
+                                Arguments&)>>
+      listCommands{
+        { "LENGTH"_s,
+          [](cmGeneratorExpressionContext* ctx,
+             const GeneratorExpressionContent* cnt,
+             Arguments& args) -> std::string {
+            if (CheckListParameters(ctx, cnt, "LENGTH"_s, args)) {
+              return std::to_string(GetList(args.front()).size());
+            }
+            return std::string{};
+          } },
+        { "GET"_s,
+          [](cmGeneratorExpressionContext* ctx,
+             const GeneratorExpressionContent* cnt,
+             Arguments& args) -> std::string {
+            if (CheckListParametersEx(ctx, cnt, "GET"_s, args.size(), 2,
+                                      false)) {
+              auto list = GetList(args.front());
+              if (list.empty()) {
+                reportError(ctx, cnt->GetOriginalExpression(),
+                            "given empty list");
+                return std::string{};
+              }
+
+              std::vector<cmList::index_type> indexes;
+              if (!GetNumericArguments(ctx, cnt, args.advance(1), indexes,
+                                       cmList::ExpandElements::Yes)) {
+                return std::string{};
+              }
+              try {
+                return list.get_items(indexes.begin(), indexes.end())
+                  .to_string();
+              } catch (std::out_of_range& e) {
+                reportError(ctx, cnt->GetOriginalExpression(), e.what());
+                return std::string{};
+              }
+            }
+            return std::string{};
+          } },
+        { "JOIN"_s,
+          [](cmGeneratorExpressionContext* ctx,
+             const GeneratorExpressionContent* cnt,
+             Arguments& args) -> std::string {
+            if (CheckListParameters(ctx, cnt, "JOIN"_s, args, 2)) {
+              return GetList(args.front()).join(args[1]);
+            }
+            return std::string{};
+          } },
+        { "SUBLIST"_s,
+          [](cmGeneratorExpressionContext* ctx,
+             const GeneratorExpressionContent* cnt,
+             Arguments& args) -> std::string {
+            if (CheckListParameters(ctx, cnt, "SUBLIST"_s, args, 3)) {
+              auto list = GetList(args.front());
+              if (!list.empty()) {
+                std::vector<cmList::index_type> indexes;
+                if (!GetNumericArguments(ctx, cnt, args.advance(1), indexes)) {
+                  return std::string{};
+                }
+                if (indexes[0] < 0) {
+                  reportError(ctx, cnt->GetOriginalExpression(),
+                              cmStrCat("begin index: ", indexes[0],
+                                       " is out of range 0 - ",
+                                       list.size() - 1));
+                  return std::string{};
+                }
+                if (indexes[1] < -1) {
+                  reportError(ctx, cnt->GetOriginalExpression(),
+                              cmStrCat("length: ", indexes[1],
+                                       " should be -1 or greater"));
+                  return std::string{};
+                }
+                try {
+                  return list
+                    .sublist(static_cast<cmList::size_type>(indexes[0]),
+                             static_cast<cmList::size_type>(indexes[1]))
+                    .to_string();
+                } catch (std::out_of_range& e) {
+                  reportError(ctx, cnt->GetOriginalExpression(), e.what());
+                  return std::string{};
+                }
+              }
+            }
+            return std::string{};
+          } },
+        { "FIND"_s,
+          [](cmGeneratorExpressionContext* ctx,
+             const GeneratorExpressionContent* cnt,
+             Arguments& args) -> std::string {
+            if (CheckListParameters(ctx, cnt, "FIND"_s, args, 2)) {
+              auto list = GetList(args.front());
+              auto index = list.find(args[1]);
+              return index == cmList::npos ? "-1" : std::to_string(index);
+            }
+            return std::string{};
+          } },
+        { "APPEND"_s,
+          [](cmGeneratorExpressionContext* ctx,
+             const GeneratorExpressionContent* cnt,
+             Arguments& args) -> std::string {
+            if (CheckListParametersEx(ctx, cnt, "APPEND"_s, args.size(), 2,
+                                      false)) {
+              auto list = args.front();
+              args.advance(1);
+              return cmList::append(list, args.begin(), args.end());
+            }
+            return std::string{};
+          } },
+        { "PREPEND"_s,
+          [](cmGeneratorExpressionContext* ctx,
+             const GeneratorExpressionContent* cnt,
+             Arguments& args) -> std::string {
+            if (CheckListParametersEx(ctx, cnt, "PREPEND"_s, args.size(), 2,
+                                      false)) {
+              auto list = args.front();
+              args.advance(1);
+              return cmList::prepend(list, args.begin(), args.end());
+            }
+            return std::string{};
+          } },
+        { "INSERT"_s,
+          [](cmGeneratorExpressionContext* ctx,
+             const GeneratorExpressionContent* cnt,
+             Arguments& args) -> std::string {
+            if (CheckListParametersEx(ctx, cnt, "INSERT"_s, args.size(), 3,
+                                      false)) {
+              cmList::index_type index;
+              if (!GetNumericArgument(args[1], index)) {
+                reportError(
+                  ctx, cnt->GetOriginalExpression(),
+                  cmStrCat("index: \"", args[1], "\" is not a valid index"));
+                return std::string{};
+              }
+              try {
+                auto list = GetList(args.front());
+                args.advance(2);
+                list.insert_items(index, args.begin(), args.end());
+                return list.to_string();
+              } catch (std::out_of_range& e) {
+                reportError(ctx, cnt->GetOriginalExpression(), e.what());
+                return std::string{};
+              }
+            }
+            return std::string{};
+          } },
+        { "POP_BACK"_s,
+          [](cmGeneratorExpressionContext* ctx,
+             const GeneratorExpressionContent* cnt,
+             Arguments& args) -> std::string {
+            if (CheckListParameters(ctx, cnt, "POP_BACK"_s, args)) {
+              auto list = GetList(args.front());
+              if (!list.empty()) {
+                list.pop_back();
+                return list.to_string();
+              }
+            }
+            return std::string{};
+          } },
+        { "POP_FRONT"_s,
+          [](cmGeneratorExpressionContext* ctx,
+             const GeneratorExpressionContent* cnt,
+             Arguments& args) -> std::string {
+            if (CheckListParameters(ctx, cnt, "POP_FRONT"_s, args)) {
+              auto list = GetList(args.front());
+              if (!list.empty()) {
+                list.pop_front();
+                return list.to_string();
+              }
+            }
+            return std::string{};
+          } },
+        { "REMOVE_DUPLICATES"_s,
+          [](cmGeneratorExpressionContext* ctx,
+             const GeneratorExpressionContent* cnt,
+             Arguments& args) -> std::string {
+            if (CheckListParameters(ctx, cnt, "REMOVE_DUPLICATES"_s, args)) {
+              return GetList(args.front()).remove_duplicates().to_string();
+            }
+            return std::string{};
+          } },
+        { "REMOVE_ITEM"_s,
+          [](cmGeneratorExpressionContext* ctx,
+             const GeneratorExpressionContent* cnt,
+             Arguments& args) -> std::string {
+            if (CheckListParametersEx(ctx, cnt, "REMOVE_ITEM"_s, args.size(),
+                                      2, false)) {
+              auto list = GetList(args.front());
+              args.advance(1);
+              cmList items{ args.begin(), args.end(),
+                            cmList::ExpandElements::Yes };
+              return list.remove_items(items.begin(), items.end()).to_string();
+            }
+            return std::string{};
+          } },
+        { "REMOVE_AT"_s,
+          [](cmGeneratorExpressionContext* ctx,
+             const GeneratorExpressionContent* cnt,
+             Arguments& args) -> std::string {
+            if (CheckListParametersEx(ctx, cnt, "REMOVE_AT"_s, args.size(), 2,
+                                      false)) {
+              auto list = GetList(args.front());
+              std::vector<cmList::index_type> indexes;
+              if (!GetNumericArguments(ctx, cnt, args.advance(1), indexes,
+                                       cmList::ExpandElements::Yes)) {
+                return std::string{};
+              }
+              try {
+                return list.remove_items(indexes.begin(), indexes.end())
+                  .to_string();
+              } catch (std::out_of_range& e) {
+                reportError(ctx, cnt->GetOriginalExpression(), e.what());
+                return std::string{};
+              }
+            }
+            return std::string{};
+          } },
+        { "FILTER"_s,
+          [](cmGeneratorExpressionContext* ctx,
+             const GeneratorExpressionContent* cnt,
+             Arguments& args) -> std::string {
+            if (CheckListParameters(ctx, cnt, "FILTER"_s, args, 3)) {
+              auto const& op = args[1];
+              if (op != "INCLUDE"_s && op != "EXCLUDE"_s) {
+                reportError(
+                  ctx, cnt->GetOriginalExpression(),
+                  cmStrCat("sub-command FILTER does not recognize operator \"",
+                           op, "\". It must be either INCLUDE or EXCLUDE."));
+                return std::string{};
+              }
+              try {
+                return GetList(args.front())
+                  .filter(args[2],
+                          op == "INCLUDE"_s ? cmList::FilterMode::INCLUDE
+                                            : cmList::FilterMode::EXCLUDE)
+                  .to_string();
+              } catch (std::invalid_argument&) {
+                reportError(
+                  ctx, cnt->GetOriginalExpression(),
+                  cmStrCat("sub-command FILTER, failed to compile regex \"",
+                           args[2], "\"."));
+                return std::string{};
+              }
+            }
+            return std::string{};
+          } },
+        { "TRANSFORM"_s,
+          [](cmGeneratorExpressionContext* ctx,
+             const GeneratorExpressionContent* cnt,
+             Arguments& args) -> std::string {
+            if (CheckListParametersEx(ctx, cnt, "TRANSFORM"_s, args.size(), 2,
+                                      false)) {
+              auto list = GetList(args.front());
+              if (!list.empty()) {
+                struct ActionDescriptor
+                {
+                  ActionDescriptor(std::string name)
+                    : Name(std::move(name))
+                  {
+                  }
+                  ActionDescriptor(std::string name,
+                                   cmList::TransformAction action, int arity)
+                    : Name(std::move(name))
+                    , Action(action)
+                    , Arity(arity)
+                  {
+                  }
+
+                  operator const std::string&() const { return this->Name; }
+
+                  std::string Name;
+                  cmList::TransformAction Action;
+                  int Arity = 0;
+                };
+
+                static std::set<
+                  ActionDescriptor,
+                  std::function<bool(const std::string&, const std::string&)>>
+                  descriptors{
+                    { { "APPEND", cmList::TransformAction::APPEND, 1 },
+                      { "PREPEND", cmList::TransformAction::PREPEND, 1 },
+                      { "TOUPPER", cmList::TransformAction::TOUPPER, 0 },
+                      { "TOLOWER", cmList::TransformAction::TOLOWER, 0 },
+                      { "STRIP", cmList::TransformAction::STRIP, 0 },
+                      { "REPLACE", cmList::TransformAction::REPLACE, 2 } },
+                    [](const std::string& x, const std::string& y) {
+                      return x < y;
+                    }
+                  };
+
+                auto descriptor = descriptors.find(args.advance(1).front());
+                if (descriptor == descriptors.end()) {
+                  reportError(ctx, cnt->GetOriginalExpression(),
+                              cmStrCat(" sub-command TRANSFORM, ",
+                                       args.front(), " invalid action."));
+                  return std::string{};
+                }
+
+                // Action arguments
+                args.advance(1);
+                if (args.size() < descriptor->Arity) {
+                  reportError(ctx, cnt->GetOriginalExpression(),
+                              cmStrCat("sub-command TRANSFORM, action ",
+                                       descriptor->Name, " expects ",
+                                       descriptor->Arity, " argument(s)."));
+                  return std::string{};
+                }
+                std::vector<std::string> arguments;
+                if (descriptor->Arity > 0) {
+                  arguments = std::vector<std::string>(
+                    args.begin(), args.begin() + descriptor->Arity);
+                  args.advance(descriptor->Arity);
+                }
+
+                const std::string REGEX{ "REGEX" };
+                const std::string AT{ "AT" };
+                const std::string FOR{ "FOR" };
+                std::unique_ptr<cmList::TransformSelector> selector;
+
+                try {
+                  // handle optional arguments
+                  while (!args.empty()) {
+                    if ((args.front() == REGEX || args.front() == AT ||
+                         args.front() == FOR) &&
+                        selector) {
+                      reportError(ctx, cnt->GetOriginalExpression(),
+                                  cmStrCat("sub-command TRANSFORM, selector "
+                                           "already specified (",
+                                           selector->GetTag(), ")."));
+
+                      return std::string{};
+                    }
+
+                    // REGEX selector
+                    if (args.front() == REGEX) {
+                      if (args.advance(1).empty()) {
+                        reportError(
+                          ctx, cnt->GetOriginalExpression(),
+                          "sub-command TRANSFORM, selector REGEX expects "
+                          "'regular expression' argument.");
+                        return std::string{};
+                      }
+
+                      selector = cmList::TransformSelector::New<
+                        cmList::TransformSelector::REGEX>(args.front());
+
+                      args.advance(1);
+                      continue;
+                    }
+
+                    // AT selector
+                    if (args.front() == AT) {
+                      args.advance(1);
+                      // get all specified indexes
+                      std::vector<cmList::index_type> indexes;
+                      while (!args.empty()) {
+                        cmList indexList{ args.front() };
+                        for (auto const& index : indexList) {
+                          cmList::index_type value;
+
+                          if (!GetNumericArgument(index, value)) {
+                            // this is not a number, stop processing
+                            reportError(
+                              ctx, cnt->GetOriginalExpression(),
+                              cmStrCat("sub-command TRANSFORM, selector AT: '",
+                                       index, "': unexpected argument."));
+                            return std::string{};
+                          }
+                          indexes.push_back(value);
+                        }
+                        args.advance(1);
+                      }
+
+                      if (indexes.empty()) {
+                        reportError(ctx, cnt->GetOriginalExpression(),
+                                    "sub-command TRANSFORM, selector AT "
+                                    "expects at least one "
+                                    "numeric value.");
+                        return std::string{};
+                      }
+
+                      selector = cmList::TransformSelector::New<
+                        cmList::TransformSelector::AT>(std::move(indexes));
+
+                      continue;
+                    }
+
+                    // FOR selector
+                    if (args.front() == FOR) {
+                      if (args.advance(1).size() < 2) {
+                        reportError(ctx, cnt->GetOriginalExpression(),
+                                    "sub-command TRANSFORM, selector FOR "
+                                    "expects, at least,"
+                                    " two arguments.");
+                        return std::string{};
+                      }
+
+                      cmList::index_type start = 0;
+                      cmList::index_type stop = 0;
+                      cmList::index_type step = 1;
+                      bool valid = false;
+
+                      if (GetNumericArgument(args.front(), start) &&
+                          GetNumericArgument(args.advance(1).front(), stop)) {
+                        valid = true;
+                      }
+
+                      if (!valid) {
+                        reportError(
+                          ctx, cnt->GetOriginalExpression(),
+                          "sub-command TRANSFORM, selector FOR expects, "
+                          "at least, two numeric values.");
+                        return std::string{};
+                      }
+                      // try to read a third numeric value for step
+                      if (!args.advance(1).empty()) {
+                        if (!GetNumericArgument(args.front(), step)) {
+                          // this is not a number
+                          step = -1;
+                        }
+                        args.advance(1);
+                      }
+
+                      if (step <= 0) {
+                        reportError(
+                          ctx, cnt->GetOriginalExpression(),
+                          "sub-command TRANSFORM, selector FOR expects "
+                          "positive numeric value for <step>.");
+                        return std::string{};
+                      }
+
+                      selector = cmList::TransformSelector::New<
+                        cmList::TransformSelector::FOR>({ start, stop, step });
+                      continue;
+                    }
+
+                    reportError(ctx, cnt->GetOriginalExpression(),
+                                cmStrCat("sub-command TRANSFORM, '",
+                                         cmJoin(args, ", "),
+                                         "': unexpected argument(s)."));
+                    return std::string{};
+                  }
+
+                  return list
+                    .transform(descriptor->Action, arguments,
+                               std::move(selector))
+                    .to_string();
+                } catch (cmList::transform_error& e) {
+                  reportError(ctx, cnt->GetOriginalExpression(), e.what());
+                  return std::string{};
+                }
+              }
+            }
+            return std::string{};
+          } },
+        { "REVERSE"_s,
+          [](cmGeneratorExpressionContext* ctx,
+             const GeneratorExpressionContent* cnt,
+             Arguments& args) -> std::string {
+            if (CheckListParameters(ctx, cnt, "REVERSE"_s, args)) {
+              return GetList(args.front()).reverse().to_string();
+            }
+            return std::string{};
+          } },
+        { "SORT"_s,
+          [](cmGeneratorExpressionContext* ctx,
+             const GeneratorExpressionContent* cnt,
+             Arguments& args) -> std::string {
+            if (CheckListParametersEx(ctx, cnt, "SORT"_s, args.size(), 1,
+                                      false)) {
+              auto list = GetList(args.front());
+              args.advance(1);
+              const auto COMPARE = "COMPARE:"_s;
+              const auto CASE = "CASE:"_s;
+              const auto ORDER = "ORDER:"_s;
+              using SortConfig = cmList::SortConfiguration;
+              SortConfig sortConfig;
+              for (auto const& arg : args) {
+                if (cmHasPrefix(arg, COMPARE)) {
+                  if (sortConfig.Compare !=
+                      SortConfig::CompareMethod::DEFAULT) {
+                    reportError(ctx, cnt->GetOriginalExpression(),
+                                "sub-command SORT, COMPARE option has been "
+                                "specified multiple times.");
+                    return std::string{};
+                  }
+                  auto option =
+                    cm::string_view{ arg.c_str() + COMPARE.length() };
+                  if (option == "STRING"_s) {
+                    sortConfig.Compare = SortConfig::CompareMethod::STRING;
+                    continue;
+                  }
+                  if (option == "FILE_BASENAME"_s) {
+                    sortConfig.Compare =
+                      SortConfig::CompareMethod::FILE_BASENAME;
+                    continue;
+                  }
+                  if (option == "NATURAL"_s) {
+                    sortConfig.Compare = SortConfig::CompareMethod::NATURAL;
+                    continue;
+                  }
+                  reportError(
+                    ctx, cnt->GetOriginalExpression(),
+                    cmStrCat(
+                      "sub-command SORT, an invalid COMPARE option has been "
+                      "specified: \"",
+                      option, "\"."));
+                  return std::string{};
+                }
+                if (cmHasPrefix(arg, CASE)) {
+                  if (sortConfig.Case !=
+                      SortConfig::CaseSensitivity::DEFAULT) {
+                    reportError(ctx, cnt->GetOriginalExpression(),
+                                "sub-command SORT, CASE option has been "
+                                "specified multiple times.");
+                    return std::string{};
+                  }
+                  auto option = cm::string_view{ arg.c_str() + CASE.length() };
+                  if (option == "SENSITIVE"_s) {
+                    sortConfig.Case = SortConfig::CaseSensitivity::SENSITIVE;
+                    continue;
+                  }
+                  if (option == "INSENSITIVE"_s) {
+                    sortConfig.Case = SortConfig::CaseSensitivity::INSENSITIVE;
+                    continue;
+                  }
+                  reportError(
+                    ctx, cnt->GetOriginalExpression(),
+                    cmStrCat(
+                      "sub-command SORT, an invalid CASE option has been "
+                      "specified: \"",
+                      option, "\"."));
+                  return std::string{};
+                }
+                if (cmHasPrefix(arg, ORDER)) {
+                  if (sortConfig.Order != SortConfig::OrderMode::DEFAULT) {
+                    reportError(ctx, cnt->GetOriginalExpression(),
+                                "sub-command SORT, ORDER option has been "
+                                "specified multiple times.");
+                    return std::string{};
+                  }
+                  auto option =
+                    cm::string_view{ arg.c_str() + ORDER.length() };
+                  if (option == "ASCENDING"_s) {
+                    sortConfig.Order = SortConfig::OrderMode::ASCENDING;
+                    continue;
+                  }
+                  if (option == "DESCENDING"_s) {
+                    sortConfig.Order = SortConfig::OrderMode::DESCENDING;
+                    continue;
+                  }
+                  reportError(
+                    ctx, cnt->GetOriginalExpression(),
+                    cmStrCat(
+                      "sub-command SORT, an invalid ORDER option has been "
+                      "specified: \"",
+                      option, "\"."));
+                  return std::string{};
+                }
+                reportError(ctx, cnt->GetOriginalExpression(),
+                            cmStrCat("sub-command SORT, option \"", arg,
+                                     "\" is invalid."));
+                return std::string{};
+              }
+
+              return list.sort(sortConfig).to_string();
+            }
+            return std::string{};
+          } }
+      };
+
+    if (cm::contains(listCommands, parameters.front())) {
+      auto args = Arguments{ parameters }.advance(1);
+      return listCommands[parameters.front()](context, content, args);
+    }
+
+    reportError(context, content->GetOriginalExpression(),
+                cmStrCat(parameters.front(), ": invalid option."));
+    return std::string{};
+  }
+} listNode;
+
 static const struct MakeCIdentifierNode : public cmGeneratorExpressionNode
 {
   MakeCIdentifierNode() {} // NOLINT(modernize-use-equals-default)
@@ -1296,6 +2046,28 @@
 static const VersionNode<cmSystemTools::OP_LESS_EQUAL> versionLessEqNode;
 static const VersionNode<cmSystemTools::OP_EQUAL> versionEqualNode;
 
+static const struct CompileOnlyNode : public cmGeneratorExpressionNode
+{
+  CompileOnlyNode() {} // NOLINT(modernize-use-equals-default)
+
+  std::string Evaluate(
+    const std::vector<std::string>& parameters,
+    cmGeneratorExpressionContext* context,
+    const GeneratorExpressionContent* content,
+    cmGeneratorExpressionDAGChecker* dagChecker) const override
+  {
+    if (!dagChecker) {
+      reportError(context, content->GetOriginalExpression(),
+                  "$<COMPILE_ONLY:...> may only be used for linking");
+      return std::string();
+    }
+    if (dagChecker->GetTransitivePropertiesOnly()) {
+      return parameters.front();
+    }
+    return std::string{};
+  }
+} compileOnlyNode;
+
 static const struct LinkOnlyNode : public cmGeneratorExpressionNode
 {
   LinkOnlyNode() {} // NOLINT(modernize-use-equals-default)
@@ -1311,7 +2083,7 @@
                   "$<LINK_ONLY:...> may only be used for linking");
       return std::string();
     }
-    if (!dagChecker->GetTransitivePropertiesOnly()) {
+    if (!dagChecker->GetTransitivePropertiesOnlyCMP0131()) {
       return parameters.front();
     }
     return std::string();
@@ -1395,11 +2167,11 @@
         // This imported target has an appropriate location
         // for this (possibly mapped) config.
         // Check if there is a proper config mapping for the tested config.
-        std::vector<std::string> mappedConfigs;
+        cmList mappedConfigs;
         std::string mapProp = cmStrCat(
           "MAP_IMPORTED_CONFIG_", cmSystemTools::UpperCase(context->Config));
         if (cmValue mapValue = context->CurrentTarget->GetProperty(mapProp)) {
-          cmExpandList(cmSystemTools::UpperCase(*mapValue), mappedConfigs);
+          mappedConfigs.assign(cmSystemTools::UpperCase(*mapValue));
 
           for (auto const& param : parameters) {
             if (cm::contains(mappedConfigs, cmSystemTools::UpperCase(param))) {
@@ -1435,8 +2207,7 @@
     const GeneratorExpressionContent* /*content*/,
     cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override
   {
-    std::vector<std::string> list = cmExpandedList(parameters.front());
-    return cmJoin(list, parameters[1]);
+    return cmList{ parameters.front() }.join(parameters[1]);
   }
 } joinNode;
 
@@ -1468,7 +2239,8 @@
         genName.find("Ninja") == std::string::npos &&
         genName.find("Visual Studio") == std::string::npos &&
         genName.find("Xcode") == std::string::npos &&
-        genName.find("Watcom WMake") == std::string::npos) {
+        genName.find("Watcom WMake") == std::string::npos &&
+        genName.find("Green Hills MULTI") == std::string::npos) {
       reportError(context, content->GetOriginalExpression(),
                   "$<COMPILE_LANGUAGE:...> not supported for this generator.");
       return std::string();
@@ -1504,7 +2276,8 @@
       // reportError(context, content->GetOriginalExpression(), "");
       reportError(
         context, content->GetOriginalExpression(),
-        "$<COMPILE_LANG_AND_ID:lang,id> may only be used with binary targets "
+        "$<COMPILE_LANG_AND_ID:lang,id> may only be used with binary "
+        "targets "
         "to specify include directories, compile definitions, and compile "
         "options.  It may not be used with the add_custom_command, "
         "add_custom_target, or file(GENERATE) commands.");
@@ -1516,7 +2289,8 @@
         genName.find("Ninja") == std::string::npos &&
         genName.find("Visual Studio") == std::string::npos &&
         genName.find("Xcode") == std::string::npos &&
-        genName.find("Watcom WMake") == std::string::npos) {
+        genName.find("Watcom WMake") == std::string::npos &&
+        genName.find("Green Hills MULTI") == std::string::npos) {
       reportError(
         context, content->GetOriginalExpression(),
         "$<COMPILE_LANG_AND_ID:lang,id> not supported for this generator.");
@@ -1548,7 +2322,8 @@
   {
     if (!context->HeadTarget || !dagChecker ||
         !(dagChecker->EvaluatingLinkExpression() ||
-          dagChecker->EvaluatingLinkLibraries())) {
+          dagChecker->EvaluatingLinkLibraries() ||
+          dagChecker->EvaluatingLinkerLauncher())) {
       reportError(context, content->GetOriginalExpression(),
                   "$<LINK_LANGUAGE:...> may only be used with binary targets "
                   "to specify link libraries, link directories, link options "
@@ -1568,7 +2343,8 @@
         genName.find("Ninja") == std::string::npos &&
         genName.find("Visual Studio") == std::string::npos &&
         genName.find("Xcode") == std::string::npos &&
-        genName.find("Watcom WMake") == std::string::npos) {
+        genName.find("Watcom WMake") == std::string::npos &&
+        genName.find("Green Hills MULTI") == std::string::npos) {
       reportError(context, content->GetOriginalExpression(),
                   "$<LINK_LANGUAGE:...> not supported for this generator.");
       return std::string();
@@ -1641,11 +2417,13 @@
   {
     if (!context->HeadTarget || !dagChecker ||
         !(dagChecker->EvaluatingLinkExpression() ||
-          dagChecker->EvaluatingLinkLibraries())) {
+          dagChecker->EvaluatingLinkLibraries() ||
+          dagChecker->EvaluatingLinkerLauncher())) {
       reportError(
         context, content->GetOriginalExpression(),
         "$<LINK_LANG_AND_ID:lang,id> may only be used with binary targets "
-        "to specify link libraries, link directories, link options, and link "
+        "to specify link libraries, link directories, link options, and "
+        "link "
         "depends.");
       return std::string();
     }
@@ -1656,7 +2434,8 @@
         genName.find("Ninja") == std::string::npos &&
         genName.find("Visual Studio") == std::string::npos &&
         genName.find("Xcode") == std::string::npos &&
-        genName.find("Watcom WMake") == std::string::npos) {
+        genName.find("Watcom WMake") == std::string::npos &&
+        genName.find("Green Hills MULTI") == std::string::npos) {
       reportError(
         context, content->GetOriginalExpression(),
         "$<LINK_LANG_AND_ID:lang,id> not supported for this generator.");
@@ -1703,8 +2482,7 @@
       return std::string();
     }
 
-    std::vector<std::string> list;
-    cmExpandLists(parameters.begin(), parameters.end(), list);
+    cmList list{ parameters.begin(), parameters.end() };
     if (list.empty()) {
       reportError(
         context, content->GetOriginalExpression(),
@@ -1789,8 +2567,7 @@
       return std::string();
     }
 
-    std::vector<std::string> list;
-    cmExpandLists(parameters.begin(), parameters.end(), list);
+    cmList list{ parameters.begin(), parameters.end() };
     if (list.empty()) {
       reportError(
         context, content->GetOriginalExpression(),
@@ -1880,8 +2657,7 @@
     }
 
     if (context->HeadTarget->IsDeviceLink()) {
-      std::vector<std::string> list;
-      cmExpandLists(parameters.begin(), parameters.end(), list);
+      cmList list{ parameters.begin(), parameters.end() };
       const auto DL_BEGIN = "<DEVICE_LINK>"_s;
       const auto DL_END = "</DEVICE_LINK>"_s;
       cm::erase_if(list, [&](const std::string& item) {
@@ -2026,7 +2802,8 @@
         reportError(
           context, content->GetOriginalExpression(),
           "$<TARGET_PROPERTY:prop>  may only be used with binary targets.  "
-          "It may not be used with add_custom_command or add_custom_target.  "
+          "It may not be used with add_custom_command or add_custom_target. "
+          " "
           " "
           "Specify the target to read a property from using the "
           "$<TARGET_PROPERTY:tgt,prop> signature instead.");
@@ -2098,7 +2875,8 @@
 
     if (dagCheckerParent) {
       if (dagCheckerParent->EvaluatingGenexExpression() ||
-          dagCheckerParent->EvaluatingPICExpression()) {
+          dagCheckerParent->EvaluatingPICExpression() ||
+          dagCheckerParent->EvaluatingLinkerLauncher()) {
         // No check required.
       } else if (dagCheckerParent->EvaluatingLinkLibraries()) {
         evaluatingLinkLibraries = true;
@@ -2290,14 +3068,14 @@
       }
     }
 
-    std::vector<std::string> objects;
+    cmList objects;
 
     if (gt->IsImported()) {
       cmValue loc = nullptr;
       cmValue imp = nullptr;
       std::string suffix;
       if (gt->Target->GetMappedConfig(context->Config, loc, imp, suffix)) {
-        cmExpandList(*loc, objects);
+        objects.assign(*loc);
       }
       context->HadContextSensitiveCondition = true;
     } else {
@@ -2315,7 +3093,7 @@
         context->HadContextSensitiveCondition = true;
       }
 
-      for (std::string& o : objects) {
+      for (auto& o : objects) {
         o = cmStrCat(obj_dir, o);
       }
     }
@@ -2330,15 +3108,12 @@
   }
 } targetObjectsNode;
 
-static const struct TargetRuntimeDllsNode : public cmGeneratorExpressionNode
+struct TargetRuntimeDllsBaseNode : public cmGeneratorExpressionNode
 {
-  TargetRuntimeDllsNode() {} // NOLINT(modernize-use-equals-default)
-
-  std::string Evaluate(
+  std::vector<std::string> CollectDlls(
     const std::vector<std::string>& parameters,
     cmGeneratorExpressionContext* context,
-    const GeneratorExpressionContent* content,
-    cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override
+    const GeneratorExpressionContent* content) const
   {
     std::string const& tgtName = parameters.front();
     cmGeneratorTarget* gt = context->LG->FindGeneratorTargetToUse(tgtName);
@@ -2347,7 +3122,7 @@
       e << "Objects of target \"" << tgtName
         << "\" referenced but no such target exists.";
       reportError(context, content->GetOriginalExpression(), e.str());
-      return std::string();
+      return std::vector<std::string>();
     }
     cmStateEnums::TargetType type = gt->GetType();
     if (type != cmStateEnums::EXECUTABLE &&
@@ -2358,7 +3133,7 @@
         << "\" referenced but is not one of the allowed target types "
         << "(EXECUTABLE, SHARED, MODULE).";
       reportError(context, content->GetOriginalExpression(), e.str());
-      return std::string();
+      return std::vector<std::string>();
     }
 
     if (auto* cli = gt->GetLinkInformation(context->Config)) {
@@ -2371,13 +3146,51 @@
         }
       }
 
-      return cmJoin(dllPaths, ";");
+      return dllPaths;
     }
 
-    return "";
+    return std::vector<std::string>();
+  }
+};
+
+static const struct TargetRuntimeDllsNode : public TargetRuntimeDllsBaseNode
+{
+  TargetRuntimeDllsNode() {} // NOLINT(modernize-use-equals-default)
+
+  std::string Evaluate(
+    const std::vector<std::string>& parameters,
+    cmGeneratorExpressionContext* context,
+    const GeneratorExpressionContent* content,
+    cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override
+  {
+    std::vector<std::string> dlls = CollectDlls(parameters, context, content);
+    return cmJoin(dlls, ";");
   }
 } targetRuntimeDllsNode;
 
+static const struct TargetRuntimeDllDirsNode : public TargetRuntimeDllsBaseNode
+{
+  TargetRuntimeDllDirsNode() {} // NOLINT(modernize-use-equals-default)
+
+  std::string Evaluate(
+    const std::vector<std::string>& parameters,
+    cmGeneratorExpressionContext* context,
+    const GeneratorExpressionContent* content,
+    cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override
+  {
+    std::vector<std::string> dlls = CollectDlls(parameters, context, content);
+    std::vector<std::string> dllDirs;
+    for (const std::string& dll : dlls) {
+      std::string directory = cmSystemTools::GetFilenamePath(dll);
+      if (std::find(dllDirs.begin(), dllDirs.end(), directory) ==
+          dllDirs.end()) {
+        dllDirs.push_back(directory);
+      }
+    }
+    return cmJoin(dllDirs, ";");
+  }
+} targetRuntimeDllDirsNode;
+
 static const struct CompileFeaturesNode : public cmGeneratorExpressionNode
 {
   CompileFeaturesNode() {} // NOLINT(modernize-use-equals-default)
@@ -2400,7 +3213,7 @@
     }
     context->HadHeadSensitiveCondition = true;
 
-    using LangMap = std::map<std::string, std::vector<std::string>>;
+    using LangMap = std::map<std::string, cmList>;
     static LangMap availableFeatures;
 
     LangMap testedFeatures;
@@ -2422,7 +3235,7 @@
           reportError(context, content->GetOriginalExpression(), error);
           return std::string();
         }
-        cmExpandList(featuresKnown, availableFeatures[lang]);
+        availableFeatures[lang].assign(featuresKnown);
       }
     }
 
@@ -2586,10 +3399,14 @@
 
 class ArtifactDirTag;
 class ArtifactLinkerTag;
+class ArtifactLinkerLibraryTag;
+class ArtifactLinkerImportTag;
 class ArtifactNameTag;
+class ArtifactImportTag;
 class ArtifactPathTag;
 class ArtifactPdbTag;
 class ArtifactSonameTag;
+class ArtifactSonameImportTag;
 class ArtifactBundleDirTag;
 class ArtifactBundleDirNameTag;
 class ArtifactBundleContentDirTag;
@@ -2699,6 +3516,38 @@
 };
 
 template <>
+struct TargetFilesystemArtifactResultCreator<ArtifactSonameImportTag>
+{
+  static std::string Create(cmGeneratorTarget* target,
+                            cmGeneratorExpressionContext* context,
+                            const GeneratorExpressionContent* content)
+  {
+    // The target soname file (.so.1).
+    if (target->IsDLLPlatform()) {
+      ::reportError(context, content->GetOriginalExpression(),
+                    "TARGET_SONAME_IMPORT_FILE is not allowed "
+                    "for DLL target platforms.");
+      return std::string();
+    }
+    if (target->GetType() != cmStateEnums::SHARED_LIBRARY) {
+      ::reportError(context, content->GetOriginalExpression(),
+                    "TARGET_SONAME_IMPORT_FILE is allowed only for "
+                    "SHARED libraries.");
+      return std::string();
+    }
+
+    if (target->HasImportLibrary(context->Config)) {
+      return cmStrCat(target->GetDirectory(
+                        context->Config, cmStateEnums::ImportLibraryArtifact),
+                      '/',
+                      target->GetSOName(context->Config,
+                                        cmStateEnums::ImportLibraryArtifact));
+    }
+    return std::string{};
+  }
+};
+
+template <>
 struct TargetFilesystemArtifactResultCreator<ArtifactPdbTag>
 {
   static std::string Create(cmGeneratorTarget* target,
@@ -2745,7 +3594,8 @@
                             cmGeneratorExpressionContext* context,
                             const GeneratorExpressionContent* content)
   {
-    // The file used to link to the target (.so, .lib, .a).
+    // The file used to link to the target (.so, .lib, .a) or import file
+    // (.lib,  .tbd).
     if (!target->IsLinkable()) {
       ::reportError(context, content->GetOriginalExpression(),
                     "TARGET_LINKER_FILE is allowed only for libraries and "
@@ -2761,6 +3611,55 @@
 };
 
 template <>
+struct TargetFilesystemArtifactResultCreator<ArtifactLinkerLibraryTag>
+{
+  static std::string Create(cmGeneratorTarget* target,
+                            cmGeneratorExpressionContext* context,
+                            const GeneratorExpressionContent* content)
+  {
+    // The file used to link to the target (.dylib, .so, .a).
+    if (!target->IsLinkable() ||
+        target->GetType() == cmStateEnums::EXECUTABLE) {
+      ::reportError(context, content->GetOriginalExpression(),
+                    "TARGET_LINKER_LIBRARY_FILE is allowed only for libraries "
+                    "with ENABLE_EXPORTS.");
+      return std::string();
+    }
+
+    if (!target->IsDLLPlatform() ||
+        target->GetType() == cmStateEnums::STATIC_LIBRARY) {
+      return target->GetFullPath(context->Config,
+                                 cmStateEnums::RuntimeBinaryArtifact);
+    }
+    return std::string{};
+  }
+};
+
+template <>
+struct TargetFilesystemArtifactResultCreator<ArtifactLinkerImportTag>
+{
+  static std::string Create(cmGeneratorTarget* target,
+                            cmGeneratorExpressionContext* context,
+                            const GeneratorExpressionContent* content)
+  {
+    // The file used to link to the target (.lib, .tbd).
+    if (!target->IsLinkable()) {
+      ::reportError(
+        context, content->GetOriginalExpression(),
+        "TARGET_LINKER_IMPORT_FILE is allowed only for libraries and "
+        "executables with ENABLE_EXPORTS.");
+      return std::string();
+    }
+
+    if (target->HasImportLibrary(context->Config)) {
+      return target->GetFullPath(context->Config,
+                                 cmStateEnums::ImportLibraryArtifact);
+    }
+    return std::string{};
+  }
+};
+
+template <>
 struct TargetFilesystemArtifactResultCreator<ArtifactBundleDirTag>
 {
   static std::string Create(cmGeneratorTarget* target,
@@ -2857,6 +3756,21 @@
   }
 };
 
+template <>
+struct TargetFilesystemArtifactResultCreator<ArtifactImportTag>
+{
+  static std::string Create(cmGeneratorTarget* target,
+                            cmGeneratorExpressionContext* context,
+                            const GeneratorExpressionContent* /*unused*/)
+  {
+    if (target->HasImportLibrary(context->Config)) {
+      return target->GetFullPath(context->Config,
+                                 cmStateEnums::ImportLibraryArtifact, true);
+    }
+    return std::string{};
+  }
+};
+
 template <typename ArtifactT>
 struct TargetFilesystemArtifactResultGetter
 {
@@ -2982,12 +3896,24 @@
 static const TargetFilesystemArtifactNodeGroup<ArtifactNameTag>
   targetNodeGroup;
 
+static const TargetFilesystemArtifactNodeGroup<ArtifactImportTag>
+  targetImportNodeGroup;
+
 static const TargetFilesystemArtifactNodeGroup<ArtifactLinkerTag>
   targetLinkerNodeGroup;
 
+static const TargetFilesystemArtifactNodeGroup<ArtifactLinkerLibraryTag>
+  targetLinkerLibraryNodeGroup;
+
+static const TargetFilesystemArtifactNodeGroup<ArtifactLinkerImportTag>
+  targetLinkerImportNodeGroup;
+
 static const TargetFilesystemArtifactNodeGroup<ArtifactSonameTag>
   targetSoNameNodeGroup;
 
+static const TargetFilesystemArtifactNodeGroup<ArtifactSonameImportTag>
+  targetSoNameImportNodeGroup;
+
 static const TargetFilesystemArtifactNodeGroup<ArtifactPdbTag>
   targetPdbNodeGroup;
 
@@ -3027,13 +3953,30 @@
 };
 
 template <>
+struct TargetOutputNameArtifactResultGetter<ArtifactImportTag>
+{
+  static std::string Get(cmGeneratorTarget* target,
+                         cmGeneratorExpressionContext* context,
+                         const GeneratorExpressionContent* /*unused*/)
+  {
+    if (target->HasImportLibrary(context->Config)) {
+      return target->GetOutputName(context->Config,
+                                   cmStateEnums::ImportLibraryArtifact) +
+        target->GetFilePostfix(context->Config);
+    }
+    return std::string{};
+  }
+};
+
+template <>
 struct TargetOutputNameArtifactResultGetter<ArtifactLinkerTag>
 {
   static std::string Get(cmGeneratorTarget* target,
                          cmGeneratorExpressionContext* context,
                          const GeneratorExpressionContent* content)
   {
-    // The file used to link to the target (.so, .lib, .a).
+    // The library file used to link to the target (.so, .lib, .a) or import
+    // file (.lin,  .tbd).
     if (!target->IsLinkable()) {
       ::reportError(context, content->GetOriginalExpression(),
                     "TARGET_LINKER_FILE_BASE_NAME is allowed only for "
@@ -3050,6 +3993,56 @@
 };
 
 template <>
+struct TargetOutputNameArtifactResultGetter<ArtifactLinkerLibraryTag>
+{
+  static std::string Get(cmGeneratorTarget* target,
+                         cmGeneratorExpressionContext* context,
+                         const GeneratorExpressionContent* content)
+  {
+    // The library file used to link to the target (.so, .lib, .a).
+    if (!target->IsLinkable() ||
+        target->GetType() == cmStateEnums::EXECUTABLE) {
+      ::reportError(context, content->GetOriginalExpression(),
+                    "TARGET_LINKER_LIBRARY_FILE_BASE_NAME is allowed only for "
+                    "libraries with ENABLE_EXPORTS.");
+      return std::string();
+    }
+
+    if (!target->IsDLLPlatform() ||
+        target->GetType() == cmStateEnums::STATIC_LIBRARY) {
+      return target->GetOutputName(context->Config,
+                                   cmStateEnums::ImportLibraryArtifact) +
+        target->GetFilePostfix(context->Config);
+    }
+    return std::string{};
+  }
+};
+
+template <>
+struct TargetOutputNameArtifactResultGetter<ArtifactLinkerImportTag>
+{
+  static std::string Get(cmGeneratorTarget* target,
+                         cmGeneratorExpressionContext* context,
+                         const GeneratorExpressionContent* content)
+  {
+    // The import file used to link to the target (.lib, .tbd).
+    if (!target->IsLinkable()) {
+      ::reportError(context, content->GetOriginalExpression(),
+                    "TARGET_LINKER_IMPORT_FILE_BASE_NAME is allowed only for "
+                    "libraries and executables with ENABLE_EXPORTS.");
+      return std::string();
+    }
+
+    if (target->HasImportLibrary(context->Config)) {
+      return target->GetOutputName(context->Config,
+                                   cmStateEnums::ImportLibraryArtifact) +
+        target->GetFilePostfix(context->Config);
+    }
+    return std::string{};
+  }
+};
+
+template <>
 struct TargetOutputNameArtifactResultGetter<ArtifactPdbTag>
 {
   static std::string Get(cmGeneratorTarget* target,
@@ -3120,15 +4113,27 @@
 
 static const TargetFileBaseNameArtifact<ArtifactNameTag>
   targetFileBaseNameNode;
+static const TargetFileBaseNameArtifact<ArtifactImportTag>
+  targetImportFileBaseNameNode;
 static const TargetFileBaseNameArtifact<ArtifactLinkerTag>
   targetLinkerFileBaseNameNode;
+static const TargetFileBaseNameArtifact<ArtifactLinkerLibraryTag>
+  targetLinkerLibraryFileBaseNameNode;
+static const TargetFileBaseNameArtifact<ArtifactLinkerImportTag>
+  targetLinkerImportFileBaseNameNode;
 static const TargetFileBaseNameArtifact<ArtifactPdbTag>
   targetPdbFileBaseNameNode;
 
 class ArtifactFilePrefixTag;
+class ArtifactImportFilePrefixTag;
 class ArtifactLinkerFilePrefixTag;
+class ArtifactLinkerLibraryFilePrefixTag;
+class ArtifactLinkerImportFilePrefixTag;
 class ArtifactFileSuffixTag;
+class ArtifactImportFileSuffixTag;
 class ArtifactLinkerFileSuffixTag;
+class ArtifactLinkerLibraryFileSuffixTag;
+class ArtifactLinkerImportFileSuffixTag;
 
 template <typename ArtifactT>
 struct TargetFileArtifactResultGetter
@@ -3149,6 +4154,20 @@
   }
 };
 template <>
+struct TargetFileArtifactResultGetter<ArtifactImportFilePrefixTag>
+{
+  static std::string Get(cmGeneratorTarget* target,
+                         cmGeneratorExpressionContext* context,
+                         const GeneratorExpressionContent*)
+  {
+    if (target->HasImportLibrary(context->Config)) {
+      return target->GetFilePrefix(context->Config,
+                                   cmStateEnums::ImportLibraryArtifact);
+    }
+    return std::string{};
+  }
+};
+template <>
 struct TargetFileArtifactResultGetter<ArtifactLinkerFilePrefixTag>
 {
   static std::string Get(cmGeneratorTarget* target,
@@ -3156,9 +4175,10 @@
                          const GeneratorExpressionContent* content)
   {
     if (!target->IsLinkable()) {
-      ::reportError(context, content->GetOriginalExpression(),
-                    "TARGET_LINKER_PREFIX is allowed only for libraries and "
-                    "executables with ENABLE_EXPORTS.");
+      ::reportError(
+        context, content->GetOriginalExpression(),
+        "TARGET_LINKER_FILE_PREFIX is allowed only for libraries and "
+        "executables with ENABLE_EXPORTS.");
       return std::string();
     }
 
@@ -3171,6 +4191,52 @@
   }
 };
 template <>
+struct TargetFileArtifactResultGetter<ArtifactLinkerLibraryFilePrefixTag>
+{
+  static std::string Get(cmGeneratorTarget* target,
+                         cmGeneratorExpressionContext* context,
+                         const GeneratorExpressionContent* content)
+  {
+    if (!target->IsLinkable() ||
+        target->GetType() == cmStateEnums::EXECUTABLE) {
+      ::reportError(
+        context, content->GetOriginalExpression(),
+        "TARGET_LINKER_LIBRARY_FILE_PREFIX is allowed only for libraries "
+        "with ENABLE_EXPORTS.");
+      return std::string();
+    }
+
+    if (!target->IsDLLPlatform() ||
+        target->GetType() == cmStateEnums::STATIC_LIBRARY) {
+      return target->GetFilePrefix(context->Config,
+                                   cmStateEnums::RuntimeBinaryArtifact);
+    }
+    return std::string{};
+  }
+};
+template <>
+struct TargetFileArtifactResultGetter<ArtifactLinkerImportFilePrefixTag>
+{
+  static std::string Get(cmGeneratorTarget* target,
+                         cmGeneratorExpressionContext* context,
+                         const GeneratorExpressionContent* content)
+  {
+    if (!target->IsLinkable()) {
+      ::reportError(
+        context, content->GetOriginalExpression(),
+        "TARGET_LINKER_IMPORT_FILE_PREFIX is allowed only for libraries and "
+        "executables with ENABLE_EXPORTS.");
+      return std::string();
+    }
+
+    if (target->HasImportLibrary(context->Config)) {
+      return target->GetFilePrefix(context->Config,
+                                   cmStateEnums::ImportLibraryArtifact);
+    }
+    return std::string{};
+  }
+};
+template <>
 struct TargetFileArtifactResultGetter<ArtifactFileSuffixTag>
 {
   static std::string Get(cmGeneratorTarget* target,
@@ -3181,6 +4247,20 @@
   }
 };
 template <>
+struct TargetFileArtifactResultGetter<ArtifactImportFileSuffixTag>
+{
+  static std::string Get(cmGeneratorTarget* target,
+                         cmGeneratorExpressionContext* context,
+                         const GeneratorExpressionContent*)
+  {
+    if (target->HasImportLibrary(context->Config)) {
+      return target->GetFileSuffix(context->Config,
+                                   cmStateEnums::ImportLibraryArtifact);
+    }
+    return std::string{};
+  }
+};
+template <>
 struct TargetFileArtifactResultGetter<ArtifactLinkerFileSuffixTag>
 {
   static std::string Get(cmGeneratorTarget* target,
@@ -3188,9 +4268,10 @@
                          const GeneratorExpressionContent* content)
   {
     if (!target->IsLinkable()) {
-      ::reportError(context, content->GetOriginalExpression(),
-                    "TARGET_LINKER_SUFFIX is allowed only for libraries and "
-                    "executables with ENABLE_EXPORTS.");
+      ::reportError(
+        context, content->GetOriginalExpression(),
+        "TARGET_LINKER_FILE_SUFFIX is allowed only for libraries and "
+        "executables with ENABLE_EXPORTS.");
       return std::string();
     }
 
@@ -3202,6 +4283,51 @@
     return target->GetFileSuffix(context->Config, artifact);
   }
 };
+template <>
+struct TargetFileArtifactResultGetter<ArtifactLinkerLibraryFileSuffixTag>
+{
+  static std::string Get(cmGeneratorTarget* target,
+                         cmGeneratorExpressionContext* context,
+                         const GeneratorExpressionContent* content)
+  {
+    if (!target->IsLinkable() ||
+        target->GetType() == cmStateEnums::STATIC_LIBRARY) {
+      ::reportError(context, content->GetOriginalExpression(),
+                    "TARGET_LINKER_LIBRARY_FILE_SUFFIX is allowed only for "
+                    "libraries with ENABLE_EXPORTS.");
+      return std::string();
+    }
+
+    if (!target->IsDLLPlatform() ||
+        target->GetType() == cmStateEnums::STATIC_LIBRARY) {
+      return target->GetFileSuffix(context->Config,
+                                   cmStateEnums::RuntimeBinaryArtifact);
+    }
+    return std::string{};
+  }
+};
+template <>
+struct TargetFileArtifactResultGetter<ArtifactLinkerImportFileSuffixTag>
+{
+  static std::string Get(cmGeneratorTarget* target,
+                         cmGeneratorExpressionContext* context,
+                         const GeneratorExpressionContent* content)
+  {
+    if (!target->IsLinkable()) {
+      ::reportError(
+        context, content->GetOriginalExpression(),
+        "TARGET_LINKER_IMPORT_FILE_SUFFIX is allowed only for libraries and "
+        "executables with ENABLE_EXPORTS.");
+      return std::string();
+    }
+
+    if (target->HasImportLibrary(context->Config)) {
+      return target->GetFileSuffix(context->Config,
+                                   cmStateEnums::ImportLibraryArtifact);
+    }
+    return std::string{};
+  }
+};
 
 template <typename ArtifactT>
 struct TargetFileArtifact : public TargetArtifactBase
@@ -3232,11 +4358,23 @@
 };
 
 static const TargetFileArtifact<ArtifactFilePrefixTag> targetFilePrefixNode;
+static const TargetFileArtifact<ArtifactImportFilePrefixTag>
+  targetImportFilePrefixNode;
 static const TargetFileArtifact<ArtifactLinkerFilePrefixTag>
   targetLinkerFilePrefixNode;
+static const TargetFileArtifact<ArtifactLinkerLibraryFilePrefixTag>
+  targetLinkerLibraryFilePrefixNode;
+static const TargetFileArtifact<ArtifactLinkerImportFilePrefixTag>
+  targetLinkerImportFilePrefixNode;
 static const TargetFileArtifact<ArtifactFileSuffixTag> targetFileSuffixNode;
+static const TargetFileArtifact<ArtifactImportFileSuffixTag>
+  targetImportFileSuffixNode;
 static const TargetFileArtifact<ArtifactLinkerFileSuffixTag>
   targetLinkerFileSuffixNode;
+static const TargetFileArtifact<ArtifactLinkerLibraryFileSuffixTag>
+  targetLinkerLibraryFileSuffixNode;
+static const TargetFileArtifact<ArtifactLinkerImportFileSuffixTag>
+  targetLinkerImportFileSuffixNode;
 
 static const struct ShellPathNode : public cmGeneratorExpressionNode
 {
@@ -3248,7 +4386,7 @@
     const GeneratorExpressionContent* content,
     cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override
   {
-    std::vector<std::string> listIn = cmExpandedList(parameters.front());
+    cmList listIn{ parameters.front() };
     if (listIn.empty()) {
       reportError(context, content->GetOriginalExpression(),
                   "\"\" is not an absolute path.");
@@ -3304,23 +4442,52 @@
     { "CONFIGURATION", &configurationNode },
     { "CONFIG", &configurationTestNode },
     { "TARGET_FILE", &targetNodeGroup.File },
+    { "TARGET_IMPORT_FILE", &targetImportNodeGroup.File },
     { "TARGET_LINKER_FILE", &targetLinkerNodeGroup.File },
+    { "TARGET_LINKER_LIBRARY_FILE", &targetLinkerLibraryNodeGroup.File },
+    { "TARGET_LINKER_IMPORT_FILE", &targetLinkerImportNodeGroup.File },
     { "TARGET_SONAME_FILE", &targetSoNameNodeGroup.File },
+    { "TARGET_SONAME_IMPORT_FILE", &targetSoNameImportNodeGroup.File },
     { "TARGET_PDB_FILE", &targetPdbNodeGroup.File },
     { "TARGET_FILE_BASE_NAME", &targetFileBaseNameNode },
+    { "TARGET_IMPORT_FILE_BASE_NAME", &targetImportFileBaseNameNode },
     { "TARGET_LINKER_FILE_BASE_NAME", &targetLinkerFileBaseNameNode },
+    { "TARGET_LINKER_LIBRARY_FILE_BASE_NAME",
+      &targetLinkerLibraryFileBaseNameNode },
+    { "TARGET_LINKER_IMPORT_FILE_BASE_NAME",
+      &targetLinkerImportFileBaseNameNode },
     { "TARGET_PDB_FILE_BASE_NAME", &targetPdbFileBaseNameNode },
     { "TARGET_FILE_PREFIX", &targetFilePrefixNode },
+    { "TARGET_IMPORT_FILE_PREFIX", &targetImportFilePrefixNode },
     { "TARGET_LINKER_FILE_PREFIX", &targetLinkerFilePrefixNode },
+    { "TARGET_LINKER_LIBRARY_FILE_PREFIX",
+      &targetLinkerLibraryFilePrefixNode },
+    { "TARGET_LINKER_IMPORT_FILE_PREFIX", &targetLinkerImportFilePrefixNode },
     { "TARGET_FILE_SUFFIX", &targetFileSuffixNode },
+    { "TARGET_IMPORT_FILE_SUFFIX", &targetImportFileSuffixNode },
     { "TARGET_LINKER_FILE_SUFFIX", &targetLinkerFileSuffixNode },
+    { "TARGET_LINKER_LIBRARY_FILE_SUFFIX",
+      &targetLinkerLibraryFileSuffixNode },
+    { "TARGET_LINKER_IMPORT_FILE_SUFFIX", &targetLinkerImportFileSuffixNode },
     { "TARGET_FILE_NAME", &targetNodeGroup.FileName },
+    { "TARGET_IMPORT_FILE_NAME", &targetImportNodeGroup.FileName },
     { "TARGET_LINKER_FILE_NAME", &targetLinkerNodeGroup.FileName },
+    { "TARGET_LINKER_LIBRARY_FILE_NAME",
+      &targetLinkerLibraryNodeGroup.FileName },
+    { "TARGET_LINKER_IMPORT_FILE_NAME",
+      &targetLinkerImportNodeGroup.FileName },
     { "TARGET_SONAME_FILE_NAME", &targetSoNameNodeGroup.FileName },
+    { "TARGET_SONAME_IMPORT_FILE_NAME",
+      &targetSoNameImportNodeGroup.FileName },
     { "TARGET_PDB_FILE_NAME", &targetPdbNodeGroup.FileName },
     { "TARGET_FILE_DIR", &targetNodeGroup.FileDir },
+    { "TARGET_IMPORT_FILE_DIR", &targetImportNodeGroup.FileDir },
     { "TARGET_LINKER_FILE_DIR", &targetLinkerNodeGroup.FileDir },
+    { "TARGET_LINKER_LIBRARY_FILE_DIR",
+      &targetLinkerLibraryNodeGroup.FileDir },
+    { "TARGET_LINKER_IMPORT_FILE_DIR", &targetLinkerImportNodeGroup.FileDir },
     { "TARGET_SONAME_FILE_DIR", &targetSoNameNodeGroup.FileDir },
+    { "TARGET_SONAME_IMPORT_FILE_DIR", &targetSoNameImportNodeGroup.FileDir },
     { "TARGET_PDB_FILE_DIR", &targetPdbNodeGroup.FileDir },
     { "TARGET_BUNDLE_DIR", &targetBundleDirNode },
     { "TARGET_BUNDLE_DIR_NAME", &targetBundleDirNameNode },
@@ -3330,6 +4497,7 @@
     { "IN_LIST", &inListNode },
     { "FILTER", &filterNode },
     { "REMOVE_DUPLICATES", &removeDuplicatesNode },
+    { "LIST", &listNode },
     { "LOWER_CASE", &lowerCaseNode },
     { "UPPER_CASE", &upperCaseNode },
     { "PATH", &pathNode },
@@ -3348,12 +4516,14 @@
     { "TARGET_NAME_IF_EXISTS", &targetNameIfExistsNode },
     { "TARGET_GENEX_EVAL", &targetGenexEvalNode },
     { "TARGET_RUNTIME_DLLS", &targetRuntimeDllsNode },
+    { "TARGET_RUNTIME_DLL_DIRS", &targetRuntimeDllDirsNode },
     { "GENEX_EVAL", &genexEvalNode },
     { "BUILD_INTERFACE", &buildInterfaceNode },
     { "INSTALL_INTERFACE", &installInterfaceNode },
     { "BUILD_LOCAL_INTERFACE", &buildLocalInterfaceNode },
     { "INSTALL_PREFIX", &installPrefixNode },
     { "JOIN", &joinNode },
+    { "COMPILE_ONLY", &compileOnlyNode },
     { "LINK_ONLY", &linkOnlyNode },
     { "COMPILE_LANG_AND_ID", &languageAndIdNode },
     { "COMPILE_LANGUAGE", &languageNode },
diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx
index 5e352b2..f8455c8 100644
--- a/Source/cmGeneratorTarget.cxx
+++ b/Source/cmGeneratorTarget.cxx
@@ -3,6 +3,7 @@
 #include "cmGeneratorTarget.h"
 
 #include <algorithm>
+#include <array>
 #include <cassert>
 #include <cerrno>
 #include <cstddef>
@@ -26,6 +27,7 @@
 #include "cmAlgorithms.h"
 #include "cmComputeLinkInformation.h"
 #include "cmCustomCommandGenerator.h"
+#include "cmEvaluatedTargetProperty.h"
 #include "cmFileSet.h"
 #include "cmFileTimes.h"
 #include "cmGeneratedFileStream.h"
@@ -34,6 +36,7 @@
 #include "cmGeneratorExpressionDAGChecker.h"
 #include "cmGeneratorExpressionNode.h"
 #include "cmGlobalGenerator.h"
+#include "cmList.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
@@ -56,8 +59,6 @@
 namespace {
 using LinkInterfaceFor = cmGeneratorTarget::LinkInterfaceFor;
 
-const cmsys::RegularExpression FrameworkRegularExpression(
-  "^(.*/)?([^/]*)\\.framework/(.*)$");
 const std::string kINTERFACE_LINK_LIBRARIES = "INTERFACE_LINK_LIBRARIES";
 const std::string kINTERFACE_LINK_LIBRARIES_DIRECT =
   "INTERFACE_LINK_LIBRARIES_DIRECT";
@@ -88,31 +89,38 @@
   return tgt->GetLocation(config);
 }
 
-class cmGeneratorTarget::TargetPropertyEntry
-{
-protected:
-  static cmLinkImplItem NoLinkImplItem;
+cmLinkImplItem cmGeneratorTarget::TargetPropertyEntry::NoLinkImplItem;
 
+class TargetPropertyEntryString : public cmGeneratorTarget::TargetPropertyEntry
+{
 public:
-  TargetPropertyEntry(cmLinkImplItem const& item)
-    : LinkImplItem(item)
+  TargetPropertyEntryString(BT<std::string> propertyValue,
+                            cmLinkImplItem const& item = NoLinkImplItem)
+    : cmGeneratorTarget::TargetPropertyEntry(item)
+    , PropertyValue(std::move(propertyValue))
   {
   }
-  virtual ~TargetPropertyEntry() = default;
 
-  virtual const std::string& Evaluate(
-    cmLocalGenerator* lg, const std::string& config,
-    cmGeneratorTarget const* headTarget,
-    cmGeneratorExpressionDAGChecker* dagChecker,
-    std::string const& language) const = 0;
+  const std::string& Evaluate(cmLocalGenerator*, const std::string&,
+                              cmGeneratorTarget const*,
+                              cmGeneratorExpressionDAGChecker*,
+                              std::string const&) const override
+  {
+    return this->PropertyValue.Value;
+  }
 
-  virtual cmListFileBacktrace GetBacktrace() const = 0;
-  virtual std::string const& GetInput() const = 0;
-  virtual bool GetHadContextSensitiveCondition() const { return false; }
+  cmListFileBacktrace GetBacktrace() const override
+  {
+    return this->PropertyValue.Backtrace;
+  }
+  std::string const& GetInput() const override
+  {
+    return this->PropertyValue.Value;
+  }
 
-  cmLinkImplItem const& LinkImplItem;
+private:
+  BT<std::string> PropertyValue;
 };
-cmLinkImplItem cmGeneratorTarget::TargetPropertyEntry::NoLinkImplItem;
 
 class TargetPropertyEntryGenex : public cmGeneratorTarget::TargetPropertyEntry
 {
@@ -149,37 +157,6 @@
   const std::unique_ptr<cmCompiledGeneratorExpression> ge;
 };
 
-class TargetPropertyEntryString : public cmGeneratorTarget::TargetPropertyEntry
-{
-public:
-  TargetPropertyEntryString(BT<std::string> propertyValue,
-                            cmLinkImplItem const& item = NoLinkImplItem)
-    : cmGeneratorTarget::TargetPropertyEntry(item)
-    , PropertyValue(std::move(propertyValue))
-  {
-  }
-
-  const std::string& Evaluate(cmLocalGenerator*, const std::string&,
-                              cmGeneratorTarget const*,
-                              cmGeneratorExpressionDAGChecker*,
-                              std::string const&) const override
-  {
-    return this->PropertyValue.Value;
-  }
-
-  cmListFileBacktrace GetBacktrace() const override
-  {
-    return this->PropertyValue.Backtrace;
-  }
-  std::string const& GetInput() const override
-  {
-    return this->PropertyValue.Value;
-  }
-
-private:
-  BT<std::string> PropertyValue;
-};
-
 class TargetPropertyEntryFileSet
   : public cmGeneratorTarget::TargetPropertyEntry
 {
@@ -262,6 +239,18 @@
     cm::make_unique<TargetPropertyEntryString>(propertyValue));
 }
 
+cmGeneratorTarget::TargetPropertyEntry::TargetPropertyEntry(
+  cmLinkImplItem const& item)
+  : LinkImplItem(item)
+{
+}
+
+bool cmGeneratorTarget::TargetPropertyEntry::GetHadContextSensitiveCondition()
+  const
+{
+  return false;
+}
+
 static void CreatePropertyGeneratorExpressions(
   cmake& cmakeInstance, cmBTStringRange entries,
   std::vector<std::unique_ptr<cmGeneratorTarget::TargetPropertyEntry>>& items,
@@ -273,69 +262,6 @@
   }
 }
 
-namespace {
-// Represent a target property entry after evaluating generator expressions
-// and splitting up lists.
-struct EvaluatedTargetPropertyEntry
-{
-  EvaluatedTargetPropertyEntry(cmLinkImplItem const& item,
-                               cmListFileBacktrace bt)
-    : LinkImplItem(item)
-    , Backtrace(std::move(bt))
-  {
-  }
-
-  // Move-only.
-  EvaluatedTargetPropertyEntry(EvaluatedTargetPropertyEntry&&) = default;
-  EvaluatedTargetPropertyEntry(EvaluatedTargetPropertyEntry const&) = delete;
-  EvaluatedTargetPropertyEntry& operator=(EvaluatedTargetPropertyEntry&&) =
-    delete;
-  EvaluatedTargetPropertyEntry& operator=(
-    EvaluatedTargetPropertyEntry const&) = delete;
-
-  cmLinkImplItem const& LinkImplItem;
-  cmListFileBacktrace Backtrace;
-  std::vector<std::string> Values;
-  bool ContextDependent = false;
-};
-
-EvaluatedTargetPropertyEntry EvaluateTargetPropertyEntry(
-  cmGeneratorTarget const* thisTarget, std::string const& config,
-  std::string const& lang, cmGeneratorExpressionDAGChecker* dagChecker,
-  cmGeneratorTarget::TargetPropertyEntry& entry)
-{
-  EvaluatedTargetPropertyEntry ee(entry.LinkImplItem, entry.GetBacktrace());
-  cmExpandList(entry.Evaluate(thisTarget->GetLocalGenerator(), config,
-                              thisTarget, dagChecker, lang),
-               ee.Values);
-  if (entry.GetHadContextSensitiveCondition()) {
-    ee.ContextDependent = true;
-  }
-  return ee;
-}
-
-struct EvaluatedTargetPropertyEntries
-{
-  std::vector<EvaluatedTargetPropertyEntry> Entries;
-  bool HadContextSensitiveCondition = false;
-};
-
-EvaluatedTargetPropertyEntries EvaluateTargetPropertyEntries(
-  cmGeneratorTarget const* thisTarget, std::string const& config,
-  std::string const& lang, cmGeneratorExpressionDAGChecker* dagChecker,
-  std::vector<std::unique_ptr<cmGeneratorTarget::TargetPropertyEntry>> const&
-    in)
-{
-  EvaluatedTargetPropertyEntries out;
-  out.Entries.reserve(in.size());
-  for (auto const& entry : in) {
-    out.Entries.emplace_back(EvaluateTargetPropertyEntry(
-      thisTarget, config, lang, dagChecker, *entry));
-  }
-  return out;
-}
-}
-
 cmGeneratorTarget::cmGeneratorTarget(cmTarget* t, cmLocalGenerator* lg)
   : Target(t)
 {
@@ -458,6 +384,11 @@
 const char* cmGeneratorTarget::GetOutputTargetType(
   cmStateEnums::ArtifactType artifact) const
 {
+  if (this->IsFrameworkOnApple() || this->GetGlobalGenerator()->IsXcode()) {
+    // import file (i.e. .tbd file) is always in same location as library
+    artifact = cmStateEnums::RuntimeBinaryArtifact;
+  }
+
   switch (this->GetType()) {
     case cmStateEnums::SHARED_LIBRARY:
       if (this->IsDLLPlatform()) {
@@ -470,9 +401,15 @@
             return "ARCHIVE";
         }
       } else {
-        // For non-DLL platforms shared libraries are treated as
-        // library targets.
-        return "LIBRARY";
+        switch (artifact) {
+          case cmStateEnums::RuntimeBinaryArtifact:
+            // For non-DLL platforms shared libraries are treated as
+            // library targets.
+            return "LIBRARY";
+          case cmStateEnums::ImportLibraryArtifact:
+            // Library import libraries are treated as archive targets.
+            return "ARCHIVE";
+        }
       }
       break;
     case cmStateEnums::STATIC_LIBRARY:
@@ -792,6 +729,29 @@
       BT<std::string>(src, this->Makefile->GetBacktrace()), true));
 }
 
+void cmGeneratorTarget::AddSystemIncludeDirectory(std::string const& inc,
+                                                  std::string const& lang)
+{
+  std::string config_upper;
+  auto const& configs =
+    this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
+
+  for (auto const& config : configs) {
+    std::string inc_with_config = inc;
+    if (!config.empty()) {
+      cmSystemTools::ReplaceString(inc_with_config, "$<CONFIG>", config);
+      config_upper = cmSystemTools::UpperCase(config);
+    }
+    auto const& key = cmStrCat(config_upper, "/", lang);
+    this->Target->AddSystemIncludeDirectories({ inc_with_config });
+    this->SystemIncludesCache[key].emplace_back(inc_with_config);
+
+    // SystemIncludesCache should be sorted so that binary search can be used
+    std::sort(this->SystemIncludesCache[key].begin(),
+              this->SystemIncludesCache[key].end());
+  }
+}
+
 std::vector<cmSourceFile*> const* cmGeneratorTarget::GetSourceDepends(
   cmSourceFile const* sf) const
 {
@@ -808,14 +768,13 @@
                              const std::string& config,
                              cmGeneratorTarget const* headTarget,
                              cmGeneratorExpressionDAGChecker* dagChecker,
-                             std::vector<std::string>& result,
-                             bool excludeImported, std::string const& language)
+                             cmList& result, bool excludeImported,
+                             std::string const& language)
 {
   if (cmValue dirs =
         depTgt->GetProperty("INTERFACE_SYSTEM_INCLUDE_DIRECTORIES")) {
-    cmExpandList(cmGeneratorExpression::Evaluate(*dirs, lg, config, headTarget,
-                                                 dagChecker, depTgt, language),
-                 result);
+    result.append(cmGeneratorExpression::Evaluate(
+      *dirs, lg, config, headTarget, dagChecker, depTgt, language));
   }
   if (!depTgt->GetPropertyAsBool("SYSTEM")) {
     return;
@@ -830,9 +789,17 @@
   }
 
   if (cmValue dirs = depTgt->GetProperty("INTERFACE_INCLUDE_DIRECTORIES")) {
-    cmExpandList(cmGeneratorExpression::Evaluate(*dirs, lg, config, headTarget,
-                                                 dagChecker, depTgt, language),
-                 result);
+    result.append(cmGeneratorExpression::Evaluate(
+      *dirs, lg, config, headTarget, dagChecker, depTgt, language));
+  }
+
+  if (depTgt->Target->IsFrameworkOnApple()) {
+    if (auto fwDescriptor = depTgt->GetGlobalGenerator()->SplitFrameworkPath(
+          depTgt->GetLocation(config),
+          cmGlobalGenerator::FrameworkFormat::Strict)) {
+      result.push_back(fwDescriptor->Directory);
+      result.push_back(fwDescriptor->GetFrameworkPath());
+    }
   }
 }
 }
@@ -1000,12 +967,27 @@
 
 const char* cmGeneratorTarget::GetCustomObjectExtension() const
 {
-  static std::string extension;
-  const bool has_ptx_extension =
-    this->GetPropertyAsBool("CUDA_PTX_COMPILATION");
-  if (has_ptx_extension) {
-    extension = ".ptx";
-    return extension.c_str();
+  struct compiler_mode
+  {
+    std::string variable;
+    std::string extension;
+  };
+  static std::array<compiler_mode, 4> const modes{
+    { { "CUDA_PTX_COMPILATION", ".ptx" },
+      { "CUDA_CUBIN_COMPILATION", ".cubin" },
+      { "CUDA_FATBIN_COMPILATION", ".fatbin" },
+      { "CUDA_OPTIX_COMPILATION", ".optixir" } }
+  };
+
+  std::string const& compiler =
+    this->Makefile->GetSafeDefinition("CMAKE_CUDA_COMPILER_ID");
+  if (!compiler.empty()) {
+    for (const auto& m : modes) {
+      const bool has_extension = this->GetPropertyAsBool(m.variable);
+      if (has_extension) {
+        return m.extension.c_str();
+      }
+    }
   }
   return nullptr;
 }
@@ -1222,10 +1204,12 @@
     case cmStateEnums::GLOBAL_TARGET:
       return true;
     case cmStateEnums::INTERFACE_LIBRARY:
-      // An INTERFACE library is in the build system if it has SOURCES or
-      // HEADER_SETS.
+      // An INTERFACE library is in the build system if it has SOURCES,
+      // HEADER_SETS, or C++ module filesets.
       if (!this->SourceEntries.empty() ||
-          !this->Target->GetHeaderSetsEntries().empty()) {
+          !this->Target->GetHeaderSetsEntries().empty() ||
+          !this->Target->GetCxxModuleSetsEntries().empty() ||
+          !this->Target->GetCxxModuleHeaderSetsEntries().empty()) {
         return true;
       }
       break;
@@ -1235,6 +1219,16 @@
   return false;
 }
 
+bool cmGeneratorTarget::IsNormal() const
+{
+  return this->Target->IsNormal();
+}
+
+bool cmGeneratorTarget::IsSynthetic() const
+{
+  return this->Target->IsSynthetic();
+}
+
 bool cmGeneratorTarget::IsImported() const
 {
   return this->Target->IsImported();
@@ -1299,12 +1293,11 @@
 
     bool excludeImported = this->GetPropertyAsBool("NO_SYSTEM_FROM_IMPORTED");
 
-    std::vector<std::string> result;
+    cmList result;
     for (std::string const& it : this->Target->GetSystemIncludeDirectories()) {
-      cmExpandList(cmGeneratorExpression::Evaluate(it, this->LocalGenerator,
-                                                   config, this, &dagChecker,
-                                                   nullptr, language),
-                   result);
+      result.append(cmGeneratorExpression::Evaluate(it, this->LocalGenerator,
+                                                    config, this, &dagChecker,
+                                                    nullptr, language));
     }
 
     std::vector<cmGeneratorTarget const*> const& deps =
@@ -1568,84 +1561,6 @@
   }
 }
 
-void addInterfaceEntry(cmGeneratorTarget const* headTarget,
-                       std::string const& config, std::string const& prop,
-                       std::string const& lang,
-                       cmGeneratorExpressionDAGChecker* dagChecker,
-                       EvaluatedTargetPropertyEntries& entries,
-                       LinkInterfaceFor interfaceFor,
-                       std::vector<cmLinkImplItem> const& libraries)
-{
-  for (cmLinkImplItem const& lib : libraries) {
-    if (lib.Target) {
-      EvaluatedTargetPropertyEntry ee(lib, lib.Backtrace);
-      // Pretend $<TARGET_PROPERTY:lib.Target,prop> appeared in our
-      // caller's property and hand-evaluate it as if it were compiled.
-      // Create a context as cmCompiledGeneratorExpression::Evaluate does.
-      cmGeneratorExpressionContext context(
-        headTarget->GetLocalGenerator(), config, false, headTarget, headTarget,
-        true, lib.Backtrace, lang);
-      cmExpandList(lib.Target->EvaluateInterfaceProperty(
-                     prop, &context, dagChecker, interfaceFor),
-                   ee.Values);
-      ee.ContextDependent = context.HadContextSensitiveCondition;
-      entries.Entries.emplace_back(std::move(ee));
-    }
-  }
-}
-
-// IncludeRuntimeInterface is used to break the cycle in computing
-// the necessary transitive dependencies of targets that can occur
-// now that we have implicit language runtime targets.
-//
-// To determine the set of languages that a target has we need to iterate
-// all the sources which includes transitive INTERFACE sources.
-// Therefore we can't determine what language runtimes are needed
-// for a target until after all sources are computed.
-//
-// Therefore while computing the applicable INTERFACE_SOURCES we
-// must ignore anything in LanguageRuntimeLibraries or we would
-// create a cycle ( INTERFACE_SOURCES requires LanguageRuntimeLibraries,
-// LanguageRuntimeLibraries requires INTERFACE_SOURCES).
-//
-enum class IncludeRuntimeInterface
-{
-  Yes,
-  No
-};
-void AddInterfaceEntries(
-  cmGeneratorTarget const* headTarget, std::string const& config,
-  std::string const& prop, std::string const& lang,
-  cmGeneratorExpressionDAGChecker* dagChecker,
-  EvaluatedTargetPropertyEntries& entries,
-  IncludeRuntimeInterface searchRuntime,
-  LinkInterfaceFor interfaceFor = LinkInterfaceFor::Usage)
-{
-  if (searchRuntime == IncludeRuntimeInterface::Yes) {
-    if (cmLinkImplementation const* impl =
-          headTarget->GetLinkImplementation(config, interfaceFor)) {
-      entries.HadContextSensitiveCondition =
-        impl->HadContextSensitiveCondition;
-
-      auto runtimeLibIt = impl->LanguageRuntimeLibraries.find(lang);
-      if (runtimeLibIt != impl->LanguageRuntimeLibraries.end()) {
-        addInterfaceEntry(headTarget, config, prop, lang, dagChecker, entries,
-                          interfaceFor, runtimeLibIt->second);
-      }
-      addInterfaceEntry(headTarget, config, prop, lang, dagChecker, entries,
-                        interfaceFor, impl->Libraries);
-    }
-  } else {
-    if (cmLinkImplementationLibraries const* impl =
-          headTarget->GetLinkImplementationLibraries(config, interfaceFor)) {
-      entries.HadContextSensitiveCondition =
-        impl->HadContextSensitiveCondition;
-      addInterfaceEntry(headTarget, config, prop, lang, dagChecker, entries,
-                        interfaceFor, impl->Libraries);
-    }
-  }
-}
-
 void AddObjectEntries(cmGeneratorTarget const* headTarget,
                       std::string const& config,
                       cmGeneratorExpressionDAGChecker* dagChecker,
@@ -1702,7 +1617,8 @@
                                    std::move(entryCge), fileSet);
     entries.Entries.emplace_back(
       EvaluateTargetPropertyEntry(headTarget, config, "", dagChecker, tpe));
-    for (auto const& file : entries.Entries.back().Values) {
+    EvaluatedTargetPropertyEntry const& entry = entries.Entries.back();
+    for (auto const& file : entry.Values) {
       auto* sf = headTarget->Makefile->GetOrCreateSource(file);
       if (fileSet->GetType() == "HEADERS"_s) {
         sf->SetProperty("HEADER_FILE_ONLY", "TRUE");
@@ -1713,13 +1629,11 @@
       std::string w;
       auto path = sf->ResolveFullPath(&e, &w);
       if (!w.empty()) {
-        cm->IssueMessage(MessageType::AUTHOR_WARNING, w,
-                         headTarget->GetBacktrace());
+        cm->IssueMessage(MessageType::AUTHOR_WARNING, w, entry.Backtrace);
       }
       if (path.empty()) {
         if (!e.empty()) {
-          cm->IssueMessage(MessageType::FATAL_ERROR, e,
-                           headTarget->GetBacktrace());
+          cm->IssueMessage(MessageType::FATAL_ERROR, e, entry.Backtrace);
         }
         return;
       }
@@ -1748,20 +1662,20 @@
                        EvaluatedTargetPropertyEntries& entries)
 {
   for (auto const& entry : headTarget->Target->GetHeaderSetsEntries()) {
-    for (auto const& name : cmExpandedList(entry.Value)) {
+    for (auto const& name : cmList{ entry.Value }) {
       auto const* headerSet = headTarget->Target->GetFileSet(name);
       addFileSetEntry(headTarget, config, dagChecker, headerSet, entries);
     }
   }
   for (auto const& entry : headTarget->Target->GetCxxModuleSetsEntries()) {
-    for (auto const& name : cmExpandedList(entry.Value)) {
+    for (auto const& name : cmList{ entry.Value }) {
       auto const* cxxModuleSet = headTarget->Target->GetFileSet(name);
       addFileSetEntry(headTarget, config, dagChecker, cxxModuleSet, entries);
     }
   }
   for (auto const& entry :
        headTarget->Target->GetCxxModuleHeaderSetsEntries()) {
-    for (auto const& name : cmExpandedList(entry.Value)) {
+    for (auto const& name : cmList{ entry.Value }) {
       auto const* cxxModuleHeaderSet = headTarget->Target->GetFileSet(name);
       addFileSetEntry(headTarget, config, dagChecker, cxxModuleHeaderSet,
                       entries);
@@ -1794,11 +1708,11 @@
       std::string fullPath = sf->ResolveFullPath(&e, &w);
       cmake* cm = tgt->GetLocalGenerator()->GetCMakeInstance();
       if (!w.empty()) {
-        cm->IssueMessage(MessageType::AUTHOR_WARNING, w, tgt->GetBacktrace());
+        cm->IssueMessage(MessageType::AUTHOR_WARNING, w, entry.Backtrace);
       }
       if (fullPath.empty()) {
         if (!e.empty()) {
-          cm->IssueMessage(MessageType::FATAL_ERROR, e, tgt->GetBacktrace());
+          cm->IssueMessage(MessageType::FATAL_ERROR, e, entry.Backtrace);
         }
         return contextDependent;
       }
@@ -1854,8 +1768,8 @@
 
     cmBTStringRange sourceEntries = this->Target->GetSourceEntries();
     for (auto const& entry : sourceEntries) {
-      std::vector<std::string> items = cmExpandedList(entry.Value);
-      for (std::string const& item : items) {
+      cmList items{ entry.Value };
+      for (auto const& item : items) {
         if (cmHasLiteralPrefix(item, "$<TARGET_OBJECTS:") &&
             item.back() == '>') {
           continue;
@@ -1866,10 +1780,8 @@
     return files;
   }
 
-  std::vector<std::string> debugProperties;
-  this->Makefile->GetDefExpandList("CMAKE_DEBUG_TARGET_PROPERTIES",
-                                   debugProperties);
-
+  cmList debugProperties{ this->Makefile->GetDefinition(
+    "CMAKE_DEBUG_TARGET_PROPERTIES") };
   bool debugSources =
     !this->DebugSourcesDone && cm::contains(debugProperties, "SOURCES");
 
@@ -2506,7 +2418,8 @@
   return !skip;
 }
 
-std::string cmGeneratorTarget::GetSOName(const std::string& config) const
+std::string cmGeneratorTarget::GetSOName(
+  const std::string& config, cmStateEnums::ArtifactType artifact) const
 {
   if (this->IsImported()) {
     // Lookup the imported soname.
@@ -2519,11 +2432,10 @@
       }
       // Use the soname given if any.
       if (this->IsFrameworkOnApple()) {
-        cmsys::RegularExpressionMatch match;
-        if (FrameworkRegularExpression.find(info->SOName.c_str(), match)) {
-          auto frameworkName = match.match(2);
-          auto fileName = match.match(3);
-          return cmStrCat(frameworkName, ".framework/", fileName);
+        auto fwDescriptor = this->GetGlobalGenerator()->SplitFrameworkPath(
+          info->SOName, cmGlobalGenerator::FrameworkFormat::Strict);
+        if (fwDescriptor) {
+          return fwDescriptor->GetVersionedName();
         }
       }
       if (cmHasLiteralPrefix(info->SOName, "@rpath/")) {
@@ -2534,7 +2446,9 @@
     return "";
   }
   // Compute the soname that will be built.
-  return this->GetLibraryNames(config).SharedObject;
+  return artifact == cmStateEnums::RuntimeBinaryArtifact
+    ? this->GetLibraryNames(config).SharedObject
+    : this->GetLibraryNames(config).ImportLibrary;
 }
 
 namespace {
@@ -3062,6 +2976,16 @@
   }
 }
 
+bool cmGeneratorTarget::IsAIX() const
+{
+  return this->Target->IsAIX();
+}
+
+bool cmGeneratorTarget::IsApple() const
+{
+  return this->Target->IsApple();
+}
+
 bool cmGeneratorTarget::IsDLLPlatform() const
 {
   return this->Target->IsDLLPlatform();
@@ -3206,8 +3130,8 @@
 
     // Queue dependencies added explicitly by the user.
     if (cmValue additionalDeps = sf->GetProperty("OBJECT_DEPENDS")) {
-      std::vector<std::string> objDeps = cmExpandedList(*additionalDeps);
-      for (std::string& objDep : objDeps) {
+      cmList objDeps{ *additionalDeps };
+      for (auto& objDep : objDeps) {
         if (cmSystemTools::FileIsFullPath(objDep)) {
           objDep = cmSystemTools::CollapseFullPath(objDep);
         }
@@ -3397,11 +3321,12 @@
   return "";
 }
 
-void cmGeneratorTarget::GetAppleArchs(const std::string& config,
-                                      std::vector<std::string>& archVec) const
+std::vector<std::string> cmGeneratorTarget::GetAppleArchs(
+  std::string const& config, cm::optional<std::string> lang) const
 {
-  if (!this->Makefile->IsOn("APPLE")) {
-    return;
+  cmList archList;
+  if (!this->IsApple()) {
+    return std::move(archList.data());
   }
   cmValue archs = nullptr;
   if (!config.empty()) {
@@ -3413,11 +3338,18 @@
     archs = this->GetProperty("OSX_ARCHITECTURES");
   }
   if (archs) {
-    cmExpandList(*archs, archVec);
+    archList.assign(*archs);
   }
-  if (archVec.empty()) {
-    this->Makefile->GetDefExpandList("_CMAKE_APPLE_ARCHS_DEFAULT", archVec);
+  if (archList.empty() &&
+      // Fall back to a default architecture if no compiler target is set.
+      (!lang ||
+       this->Makefile
+         ->GetDefinition(cmStrCat("CMAKE_", *lang, "_COMPILER_TARGET"))
+         .IsEmpty())) {
+    archList.assign(
+      this->Makefile->GetDefinition("_CMAKE_APPLE_ARCHS_DEFAULT"));
   }
+  return std::move(archList.data());
 }
 
 void cmGeneratorTarget::AddExplicitLanguageFlags(std::string& flags,
@@ -3526,10 +3458,9 @@
   std::vector<CudaArchitecture> architectures;
 
   {
-    std::vector<std::string> options;
-    cmExpandList(property, options);
+    cmList options(property);
 
-    for (std::string& option : options) {
+    for (auto& option : options) {
       CudaArchitecture architecture;
 
       // Architecture name is up to the first specifier.
@@ -3568,7 +3499,7 @@
 
     for (CudaArchitecture& architecture : architectures) {
       flags +=
-        " --generate-code=arch=compute_" + architecture.name + ",code=[";
+        " \"--generate-code=arch=compute_" + architecture.name + ",code=[";
 
       if (architecture.virtual_) {
         flags += "compute_" + architecture.name;
@@ -3588,7 +3519,7 @@
         flags += "sm_" + architecture.name;
       }
 
-      flags += "]";
+      flags += "]\"";
     }
   } else if (compiler == "Clang") {
     for (CudaArchitecture& architecture : architectures) {
@@ -3620,8 +3551,7 @@
     this->Makefile->GetSafeDefinition("CMAKE_ISPC_COMPILER_ID");
 
   if (compiler == "Intel") {
-    std::vector<std::string> targets;
-    cmExpandList(property, targets);
+    cmList targets(property);
     if (!targets.empty()) {
       flags += cmStrCat(" --target=", cmWrap("", targets, "", ","));
     }
@@ -3643,8 +3573,7 @@
     return;
   }
 
-  std::vector<std::string> options;
-  cmExpandList(property, options);
+  cmList options(property);
 
   for (std::string& option : options) {
     flags += " --offload-arch=" + option;
@@ -3856,10 +3785,8 @@
   cmGeneratorExpressionDAGChecker dagChecker(this, "INCLUDE_DIRECTORIES",
                                              nullptr, nullptr);
 
-  std::vector<std::string> debugProperties;
-  this->Makefile->GetDefExpandList("CMAKE_DEBUG_TARGET_PROPERTIES",
-                                   debugProperties);
-
+  cmList debugProperties{ this->Makefile->GetDefinition(
+    "CMAKE_DEBUG_TARGET_PROPERTIES") };
   bool debugIncludes = !this->DebugIncludesDone &&
     cm::contains(debugProperties, "INCLUDE_DIRECTORIES");
 
@@ -3898,32 +3825,38 @@
   AddInterfaceEntries(this, config, "INTERFACE_INCLUDE_DIRECTORIES", lang,
                       &dagChecker, entries, IncludeRuntimeInterface::Yes);
 
-  if (this->Makefile->IsOn("APPLE")) {
+  processIncludeDirectories(this, entries, includes, uniqueIncludes,
+                            debugIncludes);
+
+  if (this->IsApple()) {
     if (cmLinkImplementationLibraries const* impl =
           this->GetLinkImplementationLibraries(config,
                                                LinkInterfaceFor::Usage)) {
       for (cmLinkImplItem const& lib : impl->Libraries) {
-        std::string libDir = cmSystemTools::CollapseFullPath(
-          lib.AsStr(), this->Makefile->GetHomeOutputDirectory());
-
-        static cmsys::RegularExpression frameworkCheck(
-          "(.*\\.framework)(/Versions/[^/]+)?/[^/]+$");
-        if (!frameworkCheck.find(libDir)) {
+        std::string libDir;
+        if (lib.Target == nullptr) {
+          libDir = cmSystemTools::CollapseFullPath(
+            lib.AsStr(), this->Makefile->GetHomeOutputDirectory());
+        } else if (lib.Target->Target->IsFrameworkOnApple()) {
+          libDir = lib.Target->GetLocation(config);
+        } else {
           continue;
         }
 
-        libDir = frameworkCheck.match(1);
+        auto fwDescriptor =
+          this->GetGlobalGenerator()->SplitFrameworkPath(libDir);
+        if (!fwDescriptor) {
+          continue;
+        }
 
-        EvaluatedTargetPropertyEntry ee(lib, cmListFileBacktrace());
-        ee.Values.emplace_back(std::move(libDir));
-        entries.Entries.emplace_back(std::move(ee));
+        auto fwInclude = fwDescriptor->GetFrameworkPath();
+        if (uniqueIncludes.insert(fwInclude).second) {
+          includes.emplace_back(fwInclude, cmListFileBacktrace());
+        }
       }
     }
   }
 
-  processIncludeDirectories(this, entries, includes, uniqueIncludes,
-                            debugIncludes);
-
   this->IncludeDirectoriesCache.emplace(cacheKey, includes);
   return includes;
 }
@@ -4115,10 +4048,8 @@
   cmGeneratorExpressionDAGChecker dagChecker(this, "COMPILE_OPTIONS", nullptr,
                                              nullptr);
 
-  std::vector<std::string> debugProperties;
-  this->Makefile->GetDefExpandList("CMAKE_DEBUG_TARGET_PROPERTIES",
-                                   debugProperties);
-
+  cmList debugProperties{ this->Makefile->GetDefinition(
+    "CMAKE_DEBUG_TARGET_PROPERTIES") };
   bool debugOptions = !this->DebugCompileOptionsDone &&
     cm::contains(debugProperties, "COMPILE_OPTIONS");
 
@@ -4158,10 +4089,8 @@
   cmGeneratorExpressionDAGChecker dagChecker(this, "COMPILE_FEATURES", nullptr,
                                              nullptr);
 
-  std::vector<std::string> debugProperties;
-  this->Makefile->GetDefExpandList("CMAKE_DEBUG_TARGET_PROPERTIES",
-                                   debugProperties);
-
+  cmList debugProperties{ this->Makefile->GetDefinition(
+    "CMAKE_DEBUG_TARGET_PROPERTIES") };
   bool debugFeatures = !this->DebugCompileFeaturesDone &&
     cm::contains(debugProperties, "COMPILE_FEATURES");
 
@@ -4210,10 +4139,8 @@
   cmGeneratorExpressionDAGChecker dagChecker(this, "COMPILE_DEFINITIONS",
                                              nullptr, nullptr);
 
-  std::vector<std::string> debugProperties;
-  this->Makefile->GetDefExpandList("CMAKE_DEBUG_TARGET_PROPERTIES",
-                                   debugProperties);
-
+  cmList debugProperties{ this->Makefile->GetDefinition(
+    "CMAKE_DEBUG_TARGET_PROPERTIES") };
   bool debugDefines = !this->DebugCompileDefinitionsDone &&
     cm::contains(debugProperties, "COMPILE_DEFINITIONS");
 
@@ -4276,10 +4203,8 @@
   cmGeneratorExpressionDAGChecker dagChecker(this, "PRECOMPILE_HEADERS",
                                              nullptr, nullptr);
 
-  std::vector<std::string> debugProperties;
-  this->Makefile->GetDefExpandList("CMAKE_DEBUG_TARGET_PROPERTIES",
-                                   debugProperties);
-
+  cmList debugProperties{ this->Makefile->GetDefinition(
+    "CMAKE_DEBUG_TARGET_PROPERTIES") };
   bool debugDefines = !this->DebugPrecompileHeadersDone &&
     std::find(debugProperties.begin(), debugProperties.end(),
               "PRECOMPILE_HEADERS") != debugProperties.end();
@@ -4673,10 +4598,8 @@
   cmGeneratorExpressionDAGChecker dagChecker(this, "LINK_OPTIONS", nullptr,
                                              nullptr);
 
-  std::vector<std::string> debugProperties;
-  this->Makefile->GetDefExpandList("CMAKE_DEBUG_TARGET_PROPERTIES",
-                                   debugProperties);
-
+  cmList debugProperties{ this->Makefile->GetDefinition(
+    "CMAKE_DEBUG_TARGET_PROPERTIES") };
   bool debugOptions = !this->DebugLinkOptionsDone &&
     cm::contains(debugProperties, "LINK_OPTIONS");
 
@@ -4700,7 +4623,7 @@
     // wrap host link options
     const std::string wrapper(this->Makefile->GetSafeDefinition(
       "CMAKE_" + language + "_DEVICE_COMPILER_WRAPPER_FLAG"));
-    std::vector<std::string> wrapperFlag = cmExpandedList(wrapper);
+    cmList wrapperFlag{ wrapper };
     const std::string wrapperSep(this->Makefile->GetSafeDefinition(
       "CMAKE_" + language + "_DEVICE_COMPILER_WRAPPER_FLAG_SEP"));
     bool concatFlagAndArgs = true;
@@ -4759,7 +4682,7 @@
     "CMAKE_" + language +
     (this->IsDeviceLink() ? "_DEVICE_LINKER_WRAPPER_FLAG"
                           : "_LINKER_WRAPPER_FLAG")));
-  std::vector<std::string> wrapperFlag = cmExpandedList(wrapper);
+  cmList wrapperFlag{ wrapper };
   const std::string wrapperSep(this->Makefile->GetSafeDefinition(
     "CMAKE_" + language +
     (this->IsDeviceLink() ? "_DEVICE_LINKER_WRAPPER_FLAG_SEP"
@@ -4957,10 +4880,8 @@
   cmGeneratorExpressionDAGChecker dagChecker(this, "LINK_DIRECTORIES", nullptr,
                                              nullptr);
 
-  std::vector<std::string> debugProperties;
-  this->Makefile->GetDefExpandList("CMAKE_DEBUG_TARGET_PROPERTIES",
-                                   debugProperties);
-
+  cmList debugProperties{ this->Makefile->GetDefinition(
+    "CMAKE_DEBUG_TARGET_PROPERTIES") };
   bool debugDirectories = !this->DebugLinkDirectoriesDone &&
     cm::contains(debugProperties, "LINK_DIRECTORIES");
 
@@ -5005,7 +4926,7 @@
 
   EvaluatedTargetPropertyEntries entries;
   if (cmValue linkDepends = this->GetProperty("LINK_DEPENDS")) {
-    std::vector<std::string> depends = cmExpandedList(*linkDepends);
+    cmList depends{ *linkDepends };
     for (const auto& depend : depends) {
       std::unique_ptr<TargetPropertyEntry> entry = CreateTargetPropertyEntry(
         *this->LocalGenerator->GetCMakeInstance(), depend);
@@ -5066,10 +4987,18 @@
     f = cmStrCat(dir, '/', targetNames.PDB);
     gg->AddToManifest(f);
   }
+
+  dir = this->GetDirectory(config, cmStateEnums::ImportLibraryArtifact);
+  if (!targetNames.ImportOutput.empty()) {
+    f = cmStrCat(dir, '/', targetNames.ImportOutput);
+    gg->AddToManifest(f);
+  }
   if (!targetNames.ImportLibrary.empty()) {
-    f =
-      cmStrCat(this->GetDirectory(config, cmStateEnums::ImportLibraryArtifact),
-               '/', targetNames.ImportLibrary);
+    f = cmStrCat(dir, '/', targetNames.ImportLibrary);
+    gg->AddToManifest(f);
+  }
+  if (!targetNames.ImportReal.empty()) {
+    f = cmStrCat(dir, '/', targetNames.ImportReal);
     gg->AddToManifest(f);
   }
 }
@@ -5189,14 +5118,20 @@
       }
       break;
     case cmStateEnums::ImportLibraryArtifact:
-      fpath += this->GetFullName(config, cmStateEnums::ImportLibraryArtifact);
+      if (realname) {
+        fpath +=
+          this->NormalGetRealName(config, cmStateEnums::ImportLibraryArtifact);
+      } else {
+        fpath +=
+          this->GetFullName(config, cmStateEnums::ImportLibraryArtifact);
+      }
       break;
   }
   return fpath;
 }
 
 std::string cmGeneratorTarget::NormalGetRealName(
-  const std::string& config) const
+  const std::string& config, cmStateEnums::ArtifactType artifact) const
 {
   // This should not be called for imported targets.
   // TODO: Split cmTarget into a class hierarchy to get compile-time
@@ -5207,12 +5142,13 @@
     this->LocalGenerator->IssueMessage(MessageType::INTERNAL_ERROR, msg);
   }
 
-  if (this->GetType() == cmStateEnums::EXECUTABLE) {
-    // Compute the real name that will be built.
-    return this->GetExecutableNames(config).Real;
-  }
+  Names names = this->GetType() == cmStateEnums::EXECUTABLE
+    ? this->GetExecutableNames(config)
+    : this->GetLibraryNames(config);
+
   // Compute the real name that will be built.
-  return this->GetLibraryNames(config).Real;
+  return artifact == cmStateEnums::RuntimeBinaryArtifact ? names.Real
+                                                         : names.ImportReal;
 }
 
 cmGeneratorTarget::Names cmGeneratorTarget::GetLibraryNames(
@@ -5257,17 +5193,16 @@
   // The library name.
   targetNames.Base = components.base;
   targetNames.Output =
-    components.prefix + targetNames.Base + components.suffix;
+    cmStrCat(components.prefix, targetNames.Base, components.suffix);
 
   if (this->IsFrameworkOnApple()) {
     targetNames.Real = components.prefix;
     if (!this->Makefile->PlatformIsAppleEmbedded()) {
-      targetNames.Real += "Versions/";
-      targetNames.Real += this->GetFrameworkVersion();
-      targetNames.Real += "/";
+      targetNames.Real +=
+        cmStrCat("Versions/", this->GetFrameworkVersion(), '/');
     }
-    targetNames.Real += targetNames.Base + components.suffix;
-    targetNames.SharedObject = targetNames.Real + components.suffix;
+    targetNames.Real += cmStrCat(targetNames.Base, components.suffix);
+    targetNames.SharedObject = targetNames.Real;
   } else {
     // The library's soname.
     this->ComputeVersionedName(targetNames.SharedObject, components.prefix,
@@ -5280,11 +5215,36 @@
                                targetNames.Output, version);
   }
 
-  // The import library name.
+  // The import library names.
   if (this->GetType() == cmStateEnums::SHARED_LIBRARY ||
       this->GetType() == cmStateEnums::MODULE_LIBRARY) {
-    targetNames.ImportLibrary =
-      this->GetFullNameInternal(config, cmStateEnums::ImportLibraryArtifact);
+    NameComponents const& importComponents =
+      this->GetFullNameInternalComponents(config,
+                                          cmStateEnums::ImportLibraryArtifact);
+    targetNames.ImportOutput = cmStrCat(
+      importComponents.prefix, importComponents.base, importComponents.suffix);
+
+    if (this->IsFrameworkOnApple() && this->IsSharedLibraryWithExports()) {
+      targetNames.ImportReal = components.prefix;
+      if (!this->Makefile->PlatformIsAppleEmbedded()) {
+        targetNames.ImportReal +=
+          cmStrCat("Versions/", this->GetFrameworkVersion(), '/');
+      }
+      targetNames.ImportReal +=
+        cmStrCat(importComponents.base, importComponents.suffix);
+      targetNames.ImportLibrary = targetNames.ImportOutput;
+    } else {
+      // The import library's soname.
+      this->ComputeVersionedName(
+        targetNames.ImportLibrary, importComponents.prefix,
+        importComponents.base, importComponents.suffix,
+        targetNames.ImportOutput, soversion);
+
+      // The import library's real name on disk.
+      this->ComputeVersionedName(
+        targetNames.ImportReal, importComponents.prefix, importComponents.base,
+        importComponents.suffix, targetNames.ImportOutput, version);
+    }
   }
 
   // The program database file name.
@@ -5346,6 +5306,8 @@
   // The import library name.
   targetNames.ImportLibrary =
     this->GetFullNameInternal(config, cmStateEnums::ImportLibraryArtifact);
+  targetNames.ImportReal = targetNames.ImportLibrary;
+  targetNames.ImportOutput = targetNames.ImportLibrary;
 
   // The program database file name.
   targetNames.PDB = this->GetPDBName(config);
@@ -5427,15 +5389,18 @@
   }
 
   // Compute the full name for main target types.
-  const std::string configPostfix = this->GetFilePostfix(config);
+  std::string configPostfix = this->GetFilePostfix(config);
 
-  // frameworks have directory prefix but no suffix
+  // frameworks have directory prefix
   std::string fw_prefix;
   if (this->IsFrameworkOnApple()) {
     fw_prefix =
       cmStrCat(this->GetFrameworkDirectory(config, ContentLevel), '/');
     targetPrefix = cmValue(fw_prefix);
-    targetSuffix = nullptr;
+    if (!isImportedLibraryArtifact) {
+      // no suffix
+      targetSuffix = nullptr;
+    }
   }
 
   if (this->IsCFBundleOnApple()) {
@@ -5454,8 +5419,8 @@
   // When using Xcode, the postfix should be part of the suffix rather than
   // the base, because the suffix ends up being used in Xcode's
   // EXECUTABLE_SUFFIX attribute.
-  if (this->IsFrameworkOnApple() &&
-      this->GetGlobalGenerator()->GetName() == "Xcode") {
+  if (this->IsFrameworkOnApple() && this->GetGlobalGenerator()->IsXcode()) {
+    configPostfix += *targetSuffix;
     targetSuffix = cmValue(configPostfix);
   } else {
     outBase += configPostfix;
@@ -5463,9 +5428,15 @@
 
   // Name shared libraries with their version number on some platforms.
   if (cmValue soversion = this->GetProperty("SOVERSION")) {
+    cmValue dllProp;
+    if (this->IsDLLPlatform()) {
+      dllProp = this->GetProperty("DLL_NAME_WITH_SOVERSION");
+    }
     if (this->GetType() == cmStateEnums::SHARED_LIBRARY &&
         !isImportedLibraryArtifact &&
-        this->Makefile->IsOn("CMAKE_SHARED_LIBRARY_NAME_WITH_VERSION")) {
+        (dllProp.IsOn() ||
+         (!dllProp.IsSet() &&
+          this->Makefile->IsOn("CMAKE_SHARED_LIBRARY_NAME_WITH_VERSION")))) {
       outBase += "-";
       outBase += *soversion;
     }
@@ -5634,8 +5605,8 @@
 
   // Process public headers to mark the source files.
   if (cmValue files = this->GetProperty("PUBLIC_HEADER")) {
-    std::vector<std::string> relFiles = cmExpandedList(*files);
-    for (std::string const& relFile : relFiles) {
+    cmList relFiles{ *files };
+    for (auto const& relFile : relFiles) {
       if (cmSourceFile* sf = this->Makefile->GetSource(relFile)) {
         SourceFileFlags& flags = this->SourceFlagsMap[sf];
         flags.MacFolder = "Headers";
@@ -5647,8 +5618,8 @@
   // Process private headers after public headers so that they take
   // precedence if a file is listed in both.
   if (cmValue files = this->GetProperty("PRIVATE_HEADER")) {
-    std::vector<std::string> relFiles = cmExpandedList(*files);
-    for (std::string const& relFile : relFiles) {
+    cmList relFiles{ *files };
+    for (auto const& relFile : relFiles) {
       if (cmSourceFile* sf = this->Makefile->GetSource(relFile)) {
         SourceFileFlags& flags = this->SourceFlagsMap[sf];
         flags.MacFolder = "PrivateHeaders";
@@ -5659,8 +5630,8 @@
 
   // Mark sources listed as resources.
   if (cmValue files = this->GetProperty("RESOURCE")) {
-    std::vector<std::string> relFiles = cmExpandedList(*files);
-    for (std::string const& relFile : relFiles) {
+    cmList relFiles{ *files };
+    for (auto const& relFile : relFiles) {
       if (cmSourceFile* sf = this->Makefile->GetSource(relFile)) {
         SourceFileFlags& flags = this->SourceFlagsMap[sf];
         flags.MacFolder = "";
@@ -5687,8 +5658,7 @@
     for (cmGeneratorTarget const* li : deps) {
 #define CM_READ_COMPATIBLE_INTERFACE(X, x)                                    \
   if (cmValue prop = li->GetProperty("COMPATIBLE_INTERFACE_" #X)) {           \
-    std::vector<std::string> props;                                           \
-    cmExpandList(*prop, props);                                               \
+    cmList props(*prop);                                                      \
     compat.Props##x.insert(props.begin(), props.end());                       \
   }
       CM_READ_COMPATIBLE_INTERFACE(BOOL, Bool)
@@ -5801,7 +5771,7 @@
     return;
   }
 
-  std::vector<std::string> props = cmExpandedList(*prop);
+  cmList props{ *prop };
   std::string pdir =
     cmStrCat(cmSystemTools::GetCMakeRoot(), "/Help/prop_tgt/");
 
@@ -6745,12 +6715,12 @@
   std::string& vName, std::string const& prefix, std::string const& base,
   std::string const& suffix, std::string const& name, cmValue version) const
 {
-  vName = this->Makefile->IsOn("APPLE") ? (prefix + base) : name;
+  vName = this->IsApple() ? (prefix + base) : name;
   if (version) {
     vName += ".";
     vName += *version;
   }
-  vName += this->Makefile->IsOn("APPLE") ? suffix : std::string();
+  vName += this->IsApple() ? suffix : std::string();
 }
 
 std::vector<std::string> cmGeneratorTarget::GetPropertyKeys() const
@@ -6762,10 +6732,8 @@
   const std::string& p, const std::string& result, const std::string& report,
   const std::string& compatibilityType) const
 {
-  std::vector<std::string> debugProperties;
-  this->Target->GetMakefile()->GetDefExpandList(
-    "CMAKE_DEBUG_TARGET_PROPERTIES", debugProperties);
-
+  cmList debugProperties{ this->Target->GetMakefile()->GetDefinition(
+    "CMAKE_DEBUG_TARGET_PROPERTIES") };
   bool debugOrigin = !this->DebugCompatiblePropertiesDone[p] &&
     cm::contains(debugProperties, p);
 
@@ -6835,6 +6803,7 @@
   // requirements.
   if (interfaceFor == LinkInterfaceFor::Usage) {
     dagChecker.SetTransitivePropertiesOnly();
+    dagChecker.SetTransitivePropertiesOnlyCMP0131();
   }
   cmMakefile const* mf = this->LocalGenerator->GetMakefile();
   LookupLinkItemScope scope{ this->LocalGenerator };
@@ -6843,10 +6812,10 @@
                              entry.Backtrace);
     std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(entry.Value);
     cge->SetEvaluateForBuildsystem(true);
-    std::vector<std::string> libs = cmExpandedList(
-      cge->Evaluate(this->LocalGenerator, config, headTarget, &dagChecker,
-                    this, headTarget->LinkerLanguage));
-    for (std::string const& lib : libs) {
+    cmList libs{ cge->Evaluate(this->LocalGenerator, config, headTarget,
+                               &dagChecker, this,
+                               headTarget->LinkerLanguage) };
+    for (auto const& lib : libs) {
       if (cm::optional<cmLinkItem> maybeItem = this->LookupLinkItem(
             lib, cge->GetBacktrace(), &scope,
             field == LinkInterfaceField::Libraries ? LookupSelf::No
@@ -7064,13 +7033,10 @@
   if (this->IsImported()) {
     auto fullPath = this->Target->ImportedGetFullPath(config, artifact);
     if (this->IsFrameworkOnApple()) {
-      cmsys::RegularExpressionMatch match;
-      if (FrameworkRegularExpression.find(fullPath.c_str(), match)) {
-        auto path = match.match(1);
-        if (!path.empty()) {
-          path.erase(path.length() - 1);
-        }
-        return path;
+      auto fwDescriptor = this->GetGlobalGenerator()->SplitFrameworkPath(
+        fullPath, cmGlobalGenerator::FrameworkFormat::Strict);
+      if (fwDescriptor) {
+        return fwDescriptor->Directory;
       }
     }
     // Return the directory from which the target is imported.
@@ -7103,6 +7069,11 @@
     return nullptr;
   }
 
+  // Synthetic targets don't have output.
+  if (this->IsSynthetic()) {
+    return nullptr;
+  }
+
   // Only libraries and executables have well-defined output files.
   if (!this->HaveWellDefinedOutputFiles()) {
     std::string msg = cmStrCat("cmGeneratorTarget::GetOutputInfo called for ",
@@ -7505,10 +7476,10 @@
     currentTarget->GetRuntimeLinkLibrary(lang, config);
   if (cmValue runtimeLinkOptions = currentTarget->Makefile->GetDefinition(
         "CMAKE_" + lang + "_RUNTIME_LIBRARIES_" + runtimeLibrary)) {
-    std::vector<std::string> libsVec = cmExpandedList(*runtimeLinkOptions);
-    result.reserve(libsVec.size());
+    cmList libsList{ *runtimeLinkOptions };
+    result.reserve(libsList.size());
 
-    for (std::string const& i : libsVec) {
+    for (auto const& i : libsList) {
       cmGeneratorTarget::TargetOrString resolved =
         currentTarget->ResolveTargetReference(i, lg);
       if (resolved.Target) {
@@ -7589,9 +7560,9 @@
     this->ExpandLinkItems(info->LibrariesProp, cmMakeRange(info->Libraries),
                           config, headTarget, interfaceFor,
                           LinkInterfaceField::Libraries, iface);
-    std::vector<std::string> deps = cmExpandedList(info->SharedDeps);
+    cmList deps{ info->SharedDeps };
     LookupLinkItemScope scope{ this->LocalGenerator };
-    for (std::string const& dep : deps) {
+    for (auto const& dep : deps) {
       if (cm::optional<cmLinkItem> maybeItem = this->LookupLinkItem(
             dep, cmListFileBacktrace(), &scope, LookupSelf::No)) {
         iface.SharedDeps.emplace_back(std::move(*maybeItem));
@@ -7912,8 +7883,8 @@
   // behavior of CMP0024 and CMP0026 only.
   cmBTStringRange rng = this->Target->GetSourceEntries();
   for (auto const& entry : rng) {
-    std::vector<std::string> files = cmExpandedList(entry.Value);
-    for (std::string const& li : files) {
+    cmList files{ entry.Value };
+    for (auto const& li : files) {
       if (cmHasLiteralPrefix(li, "$<TARGET_OBJECTS:") && li.back() == '>') {
         std::string objLibName = li.substr(17, li.size() - 18);
 
@@ -8261,13 +8232,13 @@
   cmBTStringRange entryRange = this->Target->GetLinkImplementationEntries();
   // Collect libraries directly linked in this configuration.
   for (auto const& entry : entryRange) {
-    std::vector<std::string> llibs;
     // Keep this logic in sync with ExpandLinkItems.
     cmGeneratorExpressionDAGChecker dagChecker(this, "LINK_LIBRARIES", nullptr,
                                                nullptr);
     // The $<LINK_ONLY> expression may be used to specify link dependencies
     // that are otherwise excluded from usage requirements.
     if (implFor == LinkInterfaceFor::Usage) {
+      dagChecker.SetTransitivePropertiesOnly();
       switch (this->GetPolicyStatusCMP0131()) {
         case cmPolicies::WARN:
         case cmPolicies::OLD:
@@ -8275,7 +8246,7 @@
         case cmPolicies::REQUIRED_IF_USED:
         case cmPolicies::REQUIRED_ALWAYS:
         case cmPolicies::NEW:
-          dagChecker.SetTransitivePropertiesOnly();
+          dagChecker.SetTransitivePropertiesOnlyCMP0131();
           break;
       }
     }
@@ -8288,7 +8259,7 @@
       cge->Evaluate(this->LocalGenerator, config, head, &dagChecker, nullptr,
                     this->LinkerLanguage);
     bool const checkCMP0027 = evaluated != entry.Value;
-    cmExpandList(evaluated, llibs);
+    cmList llibs(evaluated);
     if (cge->GetHadHeadSensitiveCondition()) {
       impl.HadHeadSensitiveCondition = true;
     }
@@ -8299,7 +8270,7 @@
       impl.HadLinkLanguageSensitiveCondition = true;
     }
 
-    for (std::string const& lib : llibs) {
+    for (auto const& lib : llibs) {
       if (this->IsLinkLookupScope(lib, lg)) {
         continue;
       }
@@ -8467,16 +8438,16 @@
 
 std::vector<std::string> cmGeneratorTarget::GetPackageReferences() const
 {
-  std::vector<std::string> packageReferences;
+  cmList packageReferences;
 
   if (this->IsInBuildSystem()) {
     if (cmValue vsPackageReferences =
           this->GetProperty("VS_PACKAGE_REFERENCES")) {
-      cmExpandList(*vsPackageReferences, packageReferences);
+      packageReferences.assign(*vsPackageReferences);
     }
   }
 
-  return packageReferences;
+  return std::move(packageReferences.data());
 }
 
 std::string cmGeneratorTarget::GetPDBDirectory(const std::string& config) const
@@ -8514,19 +8485,38 @@
 
 bool cmGeneratorTarget::IsExecutableWithExports() const
 {
-  return (this->GetType() == cmStateEnums::EXECUTABLE &&
-          this->GetPropertyAsBool("ENABLE_EXPORTS"));
+  return this->Target->IsExecutableWithExports();
+}
+
+bool cmGeneratorTarget::IsSharedLibraryWithExports() const
+{
+  return this->Target->IsSharedLibraryWithExports();
 }
 
 bool cmGeneratorTarget::HasImportLibrary(std::string const& config) const
 {
+  bool generate_Stubs = true;
+  if (this->GetGlobalGenerator()->IsXcode()) {
+    // take care of CMAKE_XCODE_ATTRIBUTE_GENERATE_TEXT_BASED_STUBS variable
+    // as well as XCODE_ATTRIBUTE_GENERATE_TEXT_BASED_STUBS property
+    if (cmValue propGenStubs =
+          this->GetProperty("XCODE_ATTRIBUTE_GENERATE_TEXT_BASED_STUBS")) {
+      generate_Stubs = propGenStubs == "YES";
+    } else if (cmValue varGenStubs = this->Makefile->GetDefinition(
+                 "CMAKE_XCODE_ATTRIBUTE_GENERATE_TEXT_BASED_STUBS")) {
+      generate_Stubs = varGenStubs == "YES";
+    }
+  }
+
   return (this->IsDLLPlatform() &&
           (this->GetType() == cmStateEnums::SHARED_LIBRARY ||
            this->IsExecutableWithExports()) &&
           // Assemblies which have only managed code do not have
           // import libraries.
           this->GetManagedType(config) != ManagedType::Managed) ||
-    (this->Target->IsAIX() && this->IsExecutableWithExports());
+    (this->IsAIX() && this->IsExecutableWithExports()) ||
+    (this->Makefile->PlatformSupportsAppleTextStubs() &&
+     this->IsSharedLibraryWithExports() && generate_Stubs);
 }
 
 bool cmGeneratorTarget::NeedImportLibraryName(std::string const& config) const
@@ -8562,19 +8552,38 @@
           this->IsExecutableWithExports());
 }
 
+bool cmGeneratorTarget::HasLinkDependencyFile(std::string const& config) const
+{
+  if (this->GetType() != cmStateEnums::EXECUTABLE &&
+      this->GetType() != cmStateEnums::SHARED_LIBRARY &&
+      this->GetType() != cmStateEnums::MODULE_LIBRARY) {
+    return false;
+  }
+
+  if (this->Target->GetProperty("LINK_DEPENDS_NO_SHARED").IsOn()) {
+    // Do not use the linker dependency file because it includes shared
+    // libraries as well
+    return false;
+  }
+
+  const std::string depsUseLinker{ "CMAKE_LINK_DEPENDS_USE_LINKER" };
+  auto linkLanguage = this->GetLinkerLanguage(config);
+  const std::string langDepsUseLinker{ cmStrCat("CMAKE_", linkLanguage,
+                                                "_LINK_DEPENDS_USE_LINKER") };
+
+  return (!this->Makefile->IsDefinitionSet(depsUseLinker) ||
+          this->Makefile->IsOn(depsUseLinker)) &&
+    this->Makefile->IsOn(langDepsUseLinker);
+}
+
 bool cmGeneratorTarget::IsFrameworkOnApple() const
 {
-  return ((this->GetType() == cmStateEnums::SHARED_LIBRARY ||
-           this->GetType() == cmStateEnums::STATIC_LIBRARY) &&
-          this->Makefile->IsOn("APPLE") &&
-          this->GetPropertyAsBool("FRAMEWORK"));
+  return this->Target->IsFrameworkOnApple();
 }
 
 bool cmGeneratorTarget::IsAppBundleOnApple() const
 {
-  return (this->GetType() == cmStateEnums::EXECUTABLE &&
-          this->Makefile->IsOn("APPLE") &&
-          this->GetPropertyAsBool("MACOSX_BUNDLE"));
+  return this->Target->IsAppBundleOnApple();
 }
 
 bool cmGeneratorTarget::IsXCTestOnApple() const
@@ -8584,8 +8593,8 @@
 
 bool cmGeneratorTarget::IsCFBundleOnApple() const
 {
-  return (this->GetType() == cmStateEnums::MODULE_LIBRARY &&
-          this->Makefile->IsOn("APPLE") && this->GetPropertyAsBool("BUNDLE"));
+  return (this->GetType() == cmStateEnums::MODULE_LIBRARY && this->IsApple() &&
+          this->GetPropertyAsBool("BUNDLE"));
 }
 
 cmGeneratorTarget::ManagedType cmGeneratorTarget::CheckManagedType(
@@ -8661,7 +8670,7 @@
   const bool all = verifyValue.IsEmpty();
   std::set<std::string> verifySet;
   if (!all) {
-    auto verifyList = cmExpandedList(verifyValue);
+    cmList verifyList{ verifyValue };
     verifySet.insert(verifyList.begin(), verifyList.end());
   }
 
@@ -8674,7 +8683,7 @@
 
   std::set<cmFileSet*> fileSets;
   for (auto const& entry : interfaceFileSetEntries) {
-    for (auto const& name : cmExpandedList(entry.Value)) {
+    for (auto const& name : cmList{ entry.Value }) {
       if (all || verifySet.count(name)) {
         fileSets.insert(this->Target->GetFileSet(name));
         verifySet.erase(name);
@@ -8850,10 +8859,10 @@
 
   cmGeneratedFileStream fout(filename);
   fout.SetCopyIfDifferent(true);
-  // IWYU pragma: associated allows include what you use to
+  // The IWYU "associated" pragma tells include-what-you-use to
   // consider the headerFile as part of the entire language
   // unit within include-what-you-use and as a result allows
-  // one to get IWYU advice for headers :)
+  // one to get IWYU advice for headers.
   fout << "#include <" << headerFilename << "> // IWYU pragma: associated\n";
   fout.close();
 
diff --git a/Source/cmGeneratorTarget.h b/Source/cmGeneratorTarget.h
index afd9da4..78945c3 100644
--- a/Source/cmGeneratorTarget.h
+++ b/Source/cmGeneratorTarget.h
@@ -50,6 +50,8 @@
   cmGlobalGenerator* GetGlobalGenerator() const;
 
   bool IsInBuildSystem() const;
+  bool IsNormal() const;
+  bool IsSynthetic() const;
   bool IsImported() const;
   bool IsImportedGloballyVisible() const;
   bool CanCompileSources() const;
@@ -271,7 +273,9 @@
   std::string NormalGetFullPath(const std::string& config,
                                 cmStateEnums::ArtifactType artifact,
                                 bool realname) const;
-  std::string NormalGetRealName(const std::string& config) const;
+  std::string NormalGetRealName(const std::string& config,
+                                cmStateEnums::ArtifactType artifact =
+                                  cmStateEnums::RuntimeBinaryArtifact) const;
 
   /** Get the names of an object library's object files underneath
       its object file directory.  */
@@ -346,7 +350,9 @@
   const std::string* GetExportMacro() const;
 
   /** Get the soname of the target.  Allowed only for a shared library.  */
-  std::string GetSOName(const std::string& config) const;
+  std::string GetSOName(const std::string& config,
+                        cmStateEnums::ArtifactType artifact =
+                          cmStateEnums::RuntimeBinaryArtifact) const;
 
   struct NameComponents
   {
@@ -387,6 +393,11 @@
   ModuleDefinitionInfo const* GetModuleDefinitionInfo(
     std::string const& config) const;
 
+  /** Return whether or not we are targeting AIX. */
+  bool IsAIX() const;
+  /** Return whether or not we are targeting Apple. */
+  bool IsApple() const;
+
   /** Return whether or not the target is for a DLL platform.  */
   bool IsDLLPlatform() const;
 
@@ -473,8 +484,8 @@
       holding object files for the given configuration.  */
   std::string GetObjectDirectory(std::string const& config) const;
 
-  void GetAppleArchs(const std::string& config,
-                     std::vector<std::string>& archVec) const;
+  std::vector<std::string> GetAppleArchs(std::string const& config,
+                                         cm::optional<std::string> lang) const;
 
   void AddExplicitLanguageFlags(std::string& flags,
                                 cmSourceFile const& sf) const;
@@ -733,6 +744,8 @@
     std::string Base;
     std::string Output;
     std::string Real;
+    std::string ImportOutput;
+    std::string ImportReal;
     std::string ImportLibrary;
     std::string PDB;
     std::string SharedObject;
@@ -779,6 +792,10 @@
 
   bool IsExecutableWithExports() const;
 
+  /* Return whether this target is a shared library with capability to generate
+   * a file describing symbols exported (for example, .tbd file on Apple). */
+  bool IsSharedLibraryWithExports() const;
+
   /** Return whether or not the target has a DLL import library.  */
   bool HasImportLibrary(std::string const& config) const;
 
@@ -788,6 +805,9 @@
   /** Return whether this target may be used to link another target.  */
   bool IsLinkable() const;
 
+  /** Return whether the link step generates a dependency file. */
+  bool HasLinkDependencyFile(std::string const& config) const;
+
   /** Return whether this target is a shared library Framework on
       Apple.  */
   bool IsFrameworkOnApple() const;
@@ -895,6 +915,8 @@
   std::vector<std::string> GetGeneratedISPCObjects(
     std::string const& config) const;
 
+  void AddSystemIncludeDirectory(std::string const& inc,
+                                 std::string const& lang);
   bool AddHeaderSetVerification();
   std::string GenerateHeaderSetVerificationFile(
     cmSourceFile& source, const std::string& dir,
@@ -1274,3 +1296,25 @@
   };
   mutable std::map<std::string, InfoByConfig> Configs;
 };
+
+class cmGeneratorTarget::TargetPropertyEntry
+{
+protected:
+  static cmLinkImplItem NoLinkImplItem;
+
+public:
+  TargetPropertyEntry(cmLinkImplItem const& item);
+  virtual ~TargetPropertyEntry() = default;
+
+  virtual const std::string& Evaluate(
+    cmLocalGenerator* lg, const std::string& config,
+    cmGeneratorTarget const* headTarget,
+    cmGeneratorExpressionDAGChecker* dagChecker,
+    std::string const& language) const = 0;
+
+  virtual cmListFileBacktrace GetBacktrace() const = 0;
+  virtual std::string const& GetInput() const = 0;
+  virtual bool GetHadContextSensitiveCondition() const;
+
+  cmLinkImplItem const& LinkImplItem;
+};
diff --git a/Source/cmGetTestPropertyCommand.cxx b/Source/cmGetTestPropertyCommand.cxx
index a4ac9f6..36446c9 100644
--- a/Source/cmGetTestPropertyCommand.cxx
+++ b/Source/cmGetTestPropertyCommand.cxx
@@ -25,7 +25,7 @@
       prop = test->GetProperty(args[1]);
     }
     if (prop) {
-      mf.AddDefinition(var, prop->c_str());
+      mf.AddDefinition(var, prop);
       return true;
     }
   }
diff --git a/Source/cmGhsMultiTargetGenerator.cxx b/Source/cmGhsMultiTargetGenerator.cxx
index 8471dfe..a1e0650 100644
--- a/Source/cmGhsMultiTargetGenerator.cxx
+++ b/Source/cmGhsMultiTargetGenerator.cxx
@@ -17,6 +17,7 @@
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGhsMultiGenerator.h"
 #include "cmLinkLineComputer.h" // IWYU pragma: keep
+#include "cmList.h"
 #include "cmLocalGenerator.h"
 #include "cmLocalGhsMultiGenerator.h"
 #include "cmMakefile.h"
@@ -484,7 +485,7 @@
 {
   cmValue prop = sf->GetProperty(propName);
   if (prop) {
-    std::vector<std::string> list = cmExpandedList(*prop);
+    cmList list{ *prop };
     for (const std::string& p : list) {
       fout << "    " << propFlag << p << '\n';
     }
diff --git a/Source/cmGlobalCommonGenerator.cxx b/Source/cmGlobalCommonGenerator.cxx
index 7a44452..513e3bf 100644
--- a/Source/cmGlobalCommonGenerator.cxx
+++ b/Source/cmGlobalCommonGenerator.cxx
@@ -34,8 +34,8 @@
 {
   std::map<std::string, DirectoryTarget> dirTargets;
   for (const auto& lg : this->LocalGenerators) {
-    std::string const& currentBinaryDir(
-      lg->GetStateSnapshot().GetDirectory().GetCurrentBinary());
+    std::string currentBinaryDir =
+      lg->GetStateSnapshot().GetDirectory().GetCurrentBinary();
     DirectoryTarget& dirTarget = dirTargets[currentBinaryDir];
     dirTarget.LG = lg.get();
     const std::vector<std::string>& configs =
@@ -68,7 +68,7 @@
           for (cmStateSnapshot dir =
                  lg->GetStateSnapshot().GetBuildsystemDirectoryParent();
                dir.IsValid(); dir = dir.GetBuildsystemDirectoryParent()) {
-            std::string const& d = dir.GetDirectory().GetCurrentBinary();
+            std::string d = dir.GetDirectory().GetCurrentBinary();
             dirTargets[d].Targets.emplace_back(t);
           }
         }
diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx
index b55fcb8..f50c4cb 100644
--- a/Source/cmGlobalGenerator.cxx
+++ b/Source/cmGlobalGenerator.cxx
@@ -42,10 +42,12 @@
 #include "cmInstallGenerator.h"
 #include "cmInstallRuntimeDependencySet.h"
 #include "cmLinkLineComputer.h"
+#include "cmList.h"
 #include "cmLocalGenerator.h"
 #include "cmMSVC60LinkLineComputer.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
+#include "cmOutputConverter.h"
 #include "cmPolicies.h"
 #include "cmRange.h"
 #include "cmSourceFile.h"
@@ -72,6 +74,23 @@
 
 class cmInstalledFile;
 
+namespace detail {
+std::string GeneratedMakeCommand::QuotedPrintable() const
+{
+  std::string output;
+  const char* sep = "";
+  int flags = 0;
+#if !defined(_WIN32)
+  flags |= cmOutputConverter::Shell_Flag_IsUnix;
+#endif
+  for (auto const& arg : this->PrimaryCommand) {
+    output += cmStrCat(sep, cmOutputConverter::EscapeForShell(arg, flags));
+    sep = " ";
+  }
+  return output;
+}
+}
+
 bool cmTarget::StrictTargetComparison::operator()(cmTarget const* t1,
                                                   cmTarget const* t2) const
 {
@@ -110,8 +129,6 @@
   this->ConfigureDoneCMP0026AndCMP0024 = false;
   this->FirstTimeProgress = 0.0f;
 
-  this->RecursionDepth = 0;
-
   cm->GetState()->SetIsGeneratorMultiConfig(false);
   cm->GetState()->SetMinGWMake(false);
   cm->GetState()->SetMSYSShell(false);
@@ -240,10 +257,10 @@
     this->GetCMakeInstance()->GetState()->GetInitializedCacheValue(langComp);
 
   // Split compiler from arguments
-  std::vector<std::string> cnameArgVec;
+  cmList cnameArgList;
   if (cname && !cname->empty()) {
-    cmExpandList(*cname, cnameArgVec);
-    cname = cmValue(cnameArgVec.front());
+    cnameArgList.assign(*cname);
+    cname = cmValue(cnameArgList.front());
   }
 
   std::string changeVars;
@@ -269,7 +286,7 @@
       changeVars += ";";
       changeVars += *cname;
       this->GetCMakeInstance()->GetState()->SetGlobalProperty(
-        "__CMAKE_DELETE_CACHE_CHANGE_VARS_", changeVars.c_str());
+        "__CMAKE_DELETE_CACHE_CHANGE_VARS_", changeVars);
     }
   }
 }
@@ -672,7 +689,7 @@
     mf->GetState()->SetInTopLevelIncludes(true);
     std::string includes =
       mf->GetSafeDefinition("CMAKE_PROJECT_TOP_LEVEL_INCLUDES");
-    std::vector<std::string> includesList = cmExpandedList(includes);
+    cmList includesList{ includes };
     for (std::string const& setupFile : includesList) {
       std::string absSetupFile = cmSystemTools::CollapseFullPath(
         setupFile, mf->GetCurrentSourceDirectory());
@@ -1236,7 +1253,7 @@
   std::string ignoreExtensionsVar =
     std::string("CMAKE_") + std::string(l) + std::string("_IGNORE_EXTENSIONS");
   std::string ignoreExts = mf->GetSafeDefinition(ignoreExtensionsVar);
-  std::vector<std::string> extensionList = cmExpandedList(ignoreExts);
+  cmList extensionList{ ignoreExts };
   for (std::string const& i : extensionList) {
     this->IgnoreExtensions[i] = true;
   }
@@ -1248,7 +1265,7 @@
   std::string extensionsVar = std::string("CMAKE_") + std::string(l) +
     std::string("_SOURCE_FILE_EXTENSIONS");
   const std::string& exts = mf->GetSafeDefinition(extensionsVar);
-  std::vector<std::string> extensionList = cmExpandedList(exts);
+  cmList extensionList{ exts };
   for (std::string const& i : extensionList) {
     this->ExtensionToLanguage[i] = l;
   }
@@ -1327,6 +1344,16 @@
   this->BinaryDirectories.insert(
     this->CMakeInstance->GetHomeOutputDirectory());
 
+  if (this->ExtraGenerator && !this->CMakeInstance->GetIsInTryCompile()) {
+    this->CMakeInstance->IssueMessage(
+      MessageType::DEPRECATION_WARNING,
+      cmStrCat("Support for \"Extra Generators\" like\n  ",
+               this->ExtraGenerator->GetName(),
+               "\nis deprecated and will be removed from a future version "
+               "of CMake.  IDEs may use the cmake-file-api(7) to view "
+               "CMake-generated project build trees."));
+  }
+
   // now do it
   this->ConfigureDoneCMP0026AndCMP0024 = false;
   dirMf->Configure();
@@ -1348,11 +1375,9 @@
 
   // update the cache entry for the number of local generators, this is used
   // for progress
-  char num[100];
-  snprintf(num, sizeof(num), "%d", static_cast<int>(this->Makefiles.size()));
-  this->GetCMakeInstance()->AddCacheEntry("CMAKE_NUMBER_OF_MAKEFILES", num,
-                                          "number of local generators",
-                                          cmStateEnums::INTERNAL);
+  this->GetCMakeInstance()->AddCacheEntry(
+    "CMAKE_NUMBER_OF_MAKEFILES", std::to_string(this->Makefiles.size()),
+    "number of local generators", cmStateEnums::INTERNAL);
 
   auto endTime = std::chrono::steady_clock::now();
 
@@ -1535,10 +1560,13 @@
     return false;
   }
 
-  // Iterate through all targets and set up AUTOMOC, AUTOUIC and AUTORCC
-  if (!this->QtAutoGen()) {
+#ifndef CMAKE_BOOTSTRAP
+  this->QtAutoGen =
+    cm::make_unique<cmQtAutoGenGlobalInitializer>(this->LocalGenerators);
+  if (!this->QtAutoGen->InitializeCustomTargets()) {
     return false;
   }
+#endif
 
   // Add generator specific helper commands
   for (const auto& localGen : this->LocalGenerators) {
@@ -1619,6 +1647,17 @@
 
   this->CMakeInstance->UpdateProgress("Generating", 0.1f);
 
+#ifndef CMAKE_BOOTSTRAP
+  if (!this->QtAutoGen->SetupCustomTargets()) {
+    if (!cmSystemTools::GetErrorOccurredFlag()) {
+      this->GetCMakeInstance()->IssueMessage(
+        MessageType::FATAL_ERROR,
+        "Problem setting up custom targets for QtAutoGen");
+    }
+    return;
+  }
+#endif
+
   // Generate project files
   for (unsigned int i = 0; i < this->LocalGenerators.size(); ++i) {
     this->SetCurrentMakefile(this->LocalGenerators[i]->GetMakefile());
@@ -1755,16 +1794,6 @@
   entry->second = index++;
 }
 
-bool cmGlobalGenerator::QtAutoGen()
-{
-#ifndef CMAKE_BOOTSTRAP
-  cmQtAutoGenGlobalInitializer initializer(this->LocalGenerators);
-  return initializer.generate();
-#else
-  return true;
-#endif
-}
-
 bool cmGlobalGenerator::AddHeaderSetVerification()
 {
   for (auto const& gen : this->LocalGenerators) {
@@ -1875,10 +1904,9 @@
         "CMAKE_" + li + "_STANDARD_INCLUDE_DIRECTORIES";
       std::string const& standardIncludesStr =
         mf->GetSafeDefinition(standardIncludesVar);
-      std::vector<std::string> standardIncludesVec =
-        cmExpandedList(standardIncludesStr);
-      standardIncludesSet.insert(standardIncludesVec.begin(),
-                                 standardIncludesVec.end());
+      cmList standardIncludesList{ standardIncludesStr };
+      standardIncludesSet.insert(standardIncludesList.begin(),
+                                 standardIncludesList.end());
     }
     mf->AddSystemIncludeDirectories(standardIncludesSet);
   }
@@ -1973,7 +2001,6 @@
           notFoundMap[varName] = text;
         }
       }
-      std::vector<std::string> incs;
       cmValue incDirProp = target.second.GetProperty("INCLUDE_DIRECTORIES");
       if (!incDirProp) {
         continue;
@@ -1982,7 +2009,7 @@
       std::string incDirs = cmGeneratorExpression::Preprocess(
         *incDirProp, cmGeneratorExpression::StripAllGeneratorExpressions);
 
-      cmExpandList(incDirs, incs);
+      cmList incs(incDirs);
 
       for (std::string const& incDir : incs) {
         if (incDir.size() > 9 && cmIsNOTFOUND(incDir)) {
@@ -2047,9 +2074,12 @@
     mf->GetSafeDefinition("CMAKE_TRY_COMPILE_CONFIGURATION");
   cmBuildOptions defaultBuildOptions(false, fast, PackageResolveMode::Disable);
 
-  return this->Build(jobs, srcdir, bindir, projectName, newTarget, output, "",
-                     config, defaultBuildOptions, true,
-                     this->TryCompileTimeout);
+  std::stringstream ostr;
+  auto ret =
+    this->Build(jobs, srcdir, bindir, projectName, newTarget, ostr, "", config,
+                defaultBuildOptions, true, this->TryCompileTimeout);
+  output = ostr.str();
+  return ret;
 }
 
 std::vector<cmGlobalGenerator::GeneratedMakeCommand>
@@ -2074,7 +2104,7 @@
 int cmGlobalGenerator::Build(
   int jobs, const std::string& /*unused*/, const std::string& bindir,
   const std::string& projectName, const std::vector<std::string>& targets,
-  std::string& output, const std::string& makeCommandCSTR,
+  std::ostream& ostr, const std::string& makeCommandCSTR,
   const std::string& config, const cmBuildOptions& buildOptions, bool verbose,
   cmDuration timeout, cmSystemTools::OutputOption outputflag,
   std::vector<std::string> const& nativeOptions)
@@ -2085,16 +2115,13 @@
    * Run an executable command and put the stdout in output.
    */
   cmWorkingDirectory workdir(bindir);
-  output += "Change Dir: ";
-  output += bindir;
-  output += "\n";
+  ostr << "Change Dir: '" << bindir << '\'' << std::endl;
   if (workdir.Failed()) {
     cmSystemTools::SetRunCommandHideConsole(hideconsole);
     std::string err = cmStrCat("Failed to change directory: ",
                                std::strerror(workdir.GetLastResult()));
     cmSystemTools::Error(err);
-    output += err;
-    output += "\n";
+    ostr << err << std::endl;
     return 1;
   }
   std::string realConfig = config;
@@ -2123,9 +2150,8 @@
       this->GenerateBuildCommand(makeCommandCSTR, projectName, bindir,
                                  { "clean" }, realConfig, jobs, verbose,
                                  buildOptions);
-    output += "\nRun Clean Command:";
-    output += cleanCommand.front().Printable();
-    output += "\n";
+    ostr << "\nRun Clean Command: " << cleanCommand.front().QuotedPrintable()
+         << std::endl;
     if (cleanCommand.size() != 1) {
       this->GetCMakeInstance()->IssueMessage(MessageType::INTERNAL_ERROR,
                                              "The generator did not produce "
@@ -2138,27 +2164,33 @@
                                          nullptr, outputflag, timeout)) {
       cmSystemTools::SetRunCommandHideConsole(hideconsole);
       cmSystemTools::Error("Generator: execution of make clean failed.");
-      output += *outputPtr;
-      output += "\nGenerator: execution of make clean failed.\n";
+      ostr << *outputPtr << "\nGenerator: execution of make clean failed."
+           << std::endl;
 
       return 1;
     }
-    output += *outputPtr;
+    ostr << *outputPtr;
   }
 
   // now build
   std::string makeCommandStr;
-  output += "\nRun Build Command(s):";
+  std::string outputMakeCommandStr;
+  bool isWatcomWMake = this->CMakeInstance->GetState()->UseWatcomWMake();
+  bool needBuildOutput = isWatcomWMake;
+  std::string buildOutput;
+  ostr << "\nRun Build Command(s): ";
 
   retVal = 0;
   for (auto command = makeCommand.begin();
        command != makeCommand.end() && retVal == 0; ++command) {
     makeCommandStr = command->Printable();
-    if (command != makeCommand.end()) {
+    outputMakeCommandStr = command->QuotedPrintable();
+    if ((command + 1) != makeCommand.end()) {
       makeCommandStr += " && ";
+      outputMakeCommandStr += " && ";
     }
 
-    output += makeCommandStr;
+    ostr << outputMakeCommandStr << std::endl;
     if (!cmSystemTools::RunSingleCommand(command->PrimaryCommand, outputPtr,
                                          outputPtr, &retVal, nullptr,
                                          outputflag, timeout)) {
@@ -2166,21 +2198,24 @@
       cmSystemTools::Error(
         "Generator: execution of make failed. Make command was: " +
         makeCommandStr);
-      output += *outputPtr;
-      output += "\nGenerator: execution of make failed. Make command was: " +
-        makeCommandStr + "\n";
+      ostr << *outputPtr
+           << "\nGenerator: execution of make failed. Make command was: "
+           << outputMakeCommandStr << std::endl;
 
       return 1;
     }
-    output += *outputPtr;
+    ostr << *outputPtr << std::flush;
+    if (needBuildOutput) {
+      buildOutput += *outputPtr;
+    }
   }
-  output += "\n";
+  ostr << std::endl;
   cmSystemTools::SetRunCommandHideConsole(hideconsole);
 
   // The OpenWatcom tools do not return an error code when a link
   // library is not found!
-  if (this->CMakeInstance->GetState()->UseWatcomWMake() && retVal == 0 &&
-      output.find("W1008: cannot open") != std::string::npos) {
+  if (isWatcomWMake && retVal == 0 &&
+      buildOutput.find("W1008: cannot open") != std::string::npos) {
     retVal = 1;
   }
 
@@ -2587,14 +2622,14 @@
   // or (/path/to/)?FwName.framework/FwName(.tbd)?
   // or (/path/to/)?FwName.framework/Versions/*/FwName(.tbd)?
   static cmsys::RegularExpression frameworkPath(
-    "((.+)/)?(.+)\\.framework(/Versions/[^/]+)?(/(.+))?$");
+    "((.+)/)?([^/]+)\\.framework(/Versions/([^/]+))?(/(.+))?$");
 
   auto ext = cmSystemTools::GetFilenameLastExtension(path);
   if ((ext.empty() || ext == ".tbd" || ext == ".framework") &&
       frameworkPath.find(path)) {
     auto name = frameworkPath.match(3);
     auto libname =
-      cmSystemTools::GetFilenameWithoutExtension(frameworkPath.match(6));
+      cmSystemTools::GetFilenameWithoutExtension(frameworkPath.match(7));
     if (format == FrameworkFormat::Strict && libname.empty()) {
       return cm::nullopt;
     }
@@ -2603,11 +2638,12 @@
     }
 
     if (libname.empty() || name.size() == libname.size()) {
-      return FrameworkDescriptor{ frameworkPath.match(2), name };
+      return FrameworkDescriptor{ frameworkPath.match(2),
+                                  frameworkPath.match(5), name };
     }
 
-    return FrameworkDescriptor{ frameworkPath.match(2), name,
-                                libname.substr(name.size()) };
+    return FrameworkDescriptor{ frameworkPath.match(2), frameworkPath.match(5),
+                                name, libname.substr(name.size()) };
   }
 
   if (format == FrameworkFormat::Extended) {
@@ -2777,11 +2813,9 @@
   cmCustomCommandLine singleLine;
   singleLine.push_back(cmSystemTools::GetCTestCommand());
   singleLine.push_back("--force-new-ctest-process");
-  std::vector<std::string> args;
-  if (mf->GetDefExpandList("CMAKE_CTEST_ARGUMENTS", args)) {
-    for (auto const& arg : args) {
-      singleLine.push_back(arg);
-    }
+  cmList args(mf->GetDefinition("CMAKE_CTEST_ARGUMENTS"));
+  for (auto const& arg : args) {
+    singleLine.push_back(arg);
   }
   if (cmNonempty(cmakeCfgIntDir) && cmakeCfgIntDir[0] != '.') {
     singleLine.push_back("-C");
@@ -2910,7 +2944,7 @@
         singleLine.push_back(cfgArg);
         cfgArg = "-DEFFECTIVE_PLATFORM_NAME=$(EFFECTIVE_PLATFORM_NAME)";
       } else {
-        cfgArg += *mf->GetDefinition("CMAKE_CFG_INTDIR");
+        cfgArg += this->GetCMakeCFGIntDir();
       }
       singleLine.push_back(cfgArg);
     }
@@ -3336,12 +3370,12 @@
     cmSystemTools::MakeDirectory(dir);
     cmGeneratedFileStream fout(file);
 
-    std::vector<std::string> labels;
+    cmList labels;
 
     // List the target-wide labels.  All sources in the target get
     // these labels.
     if (targetLabels) {
-      cmExpandList(*targetLabels, labels);
+      labels.assign(*targetLabels);
       if (!labels.empty()) {
         fout << "# Target labels\n";
         for (std::string const& l : labels) {
@@ -3352,27 +3386,27 @@
     }
 
     // List directory labels
-    std::vector<std::string> directoryLabelsList;
-    std::vector<std::string> cmakeDirectoryLabelsList;
+    cmList directoryLabelsList;
+    cmList cmakeDirectoryLabelsList;
 
     if (directoryLabels) {
-      cmExpandList(*directoryLabels, directoryLabelsList);
+      directoryLabelsList.assign(*directoryLabels);
     }
 
     if (cmakeDirectoryLabels) {
-      cmExpandList(*cmakeDirectoryLabels, cmakeDirectoryLabelsList);
+      cmakeDirectoryLabelsList.assign(*cmakeDirectoryLabels);
     }
 
     if (!directoryLabelsList.empty() || !cmakeDirectoryLabelsList.empty()) {
       fout << "# Directory labels\n";
     }
 
-    for (std::string const& li : directoryLabelsList) {
+    for (auto const& li : directoryLabelsList) {
       fout << " " << li << "\n";
       lj_target_labels.append(li);
     }
 
-    for (std::string const& li : cmakeDirectoryLabelsList) {
+    for (auto const& li : cmakeDirectoryLabelsList) {
       fout << " " << li << "\n";
       lj_target_labels.append(li);
     }
@@ -3393,10 +3427,9 @@
       fout << sfp << "\n";
       lj_source["file"] = sfp;
       if (cmValue svalue = sf->GetProperty("LABELS")) {
-        labels.clear();
         Json::Value& lj_source_labels = lj_source["labels"] = Json::arrayValue;
-        cmExpandList(*svalue, labels);
-        for (std::string const& label : labels) {
+        labels.assign(*svalue);
+        for (auto const& label : labels) {
           fout << " " << label << "\n";
           lj_source_labels.append(label);
         }
@@ -3534,3 +3567,32 @@
   }
   return it->second;
 }
+
+cmGlobalGenerator::StripCommandStyle cmGlobalGenerator::GetStripCommandStyle(
+  std::string const& strip)
+{
+#ifdef __APPLE__
+  auto i = this->StripCommandStyleMap.find(strip);
+  if (i == this->StripCommandStyleMap.end()) {
+    StripCommandStyle style = StripCommandStyle::Default;
+
+    // Try running strip tool with Apple-specific options.
+    std::vector<std::string> cmd{ strip, "-u", "-r" };
+    std::string out;
+    std::string err;
+    int ret;
+    if (cmSystemTools::RunSingleCommand(cmd, &out, &err, &ret, nullptr,
+                                        cmSystemTools::OUTPUT_NONE) &&
+        // Check for Apple-specific output.
+        ret != 0 && cmHasLiteralPrefix(err, "fatal error: /") &&
+        err.find("/usr/bin/strip: no files specified") != std::string::npos) {
+      style = StripCommandStyle::Apple;
+    }
+    i = this->StripCommandStyleMap.emplace(strip, style).first;
+  }
+  return i->second;
+#else
+  static_cast<void>(strip);
+  return StripCommandStyle::Default;
+#endif
+}
diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h
index f4862a0..01afabd 100644
--- a/Source/cmGlobalGenerator.h
+++ b/Source/cmGlobalGenerator.h
@@ -50,6 +50,7 @@
 class cmLocalGenerator;
 class cmMakefile;
 class cmOutputConverter;
+class cmQtAutoGenGlobalInitializer;
 class cmSourceFile;
 class cmState;
 class cmStateDirectory;
@@ -84,6 +85,7 @@
   }
 
   std::string Printable() const { return cmJoin(this->PrimaryCommand, " "); }
+  std::string QuotedPrintable() const;
 
   std::vector<std::string> PrimaryCommand;
   bool RequiresOutputForward = false;
@@ -232,7 +234,7 @@
   int Build(
     int jobs, const std::string& srcdir, const std::string& bindir,
     const std::string& projectName,
-    std::vector<std::string> const& targetNames, std::string& output,
+    std::vector<std::string> const& targetNames, std::ostream& ostr,
     const std::string& makeProgram, const std::string& config,
     const cmBuildOptions& buildOptions, bool verbose, cmDuration timeout,
     cmSystemTools::OutputOption outputflag = cmSystemTools::OUTPUT_NONE,
@@ -383,9 +385,17 @@
       , Name(std::move(name))
     {
     }
-    FrameworkDescriptor(std::string directory, std::string name,
-                        std::string suffix)
+    FrameworkDescriptor(std::string directory, std::string version,
+                        std::string name)
       : Directory(std::move(directory))
+      , Version(std::move(version))
+      , Name(std::move(name))
+    {
+    }
+    FrameworkDescriptor(std::string directory, std::string version,
+                        std::string name, std::string suffix)
+      : Directory(std::move(directory))
+      , Version(std::move(version))
       , Name(std::move(name))
       , Suffix(std::move(suffix))
     {
@@ -399,6 +409,13 @@
     {
       return cmStrCat(this->Name, ".framework/"_s, this->Name, this->Suffix);
     }
+    std::string GetVersionedName() const
+    {
+      return this->Version.empty()
+        ? this->GetFullName()
+        : cmStrCat(this->Name, ".framework/Versions/"_s, this->Version, '/',
+                   this->Name, this->Suffix);
+    }
     std::string GetFrameworkPath() const
     {
       return this->Directory.empty()
@@ -411,8 +428,15 @@
         ? this->GetFullName()
         : cmStrCat(this->Directory, '/', this->GetFullName());
     }
+    std::string GetVersionedPath() const
+    {
+      return this->Directory.empty()
+        ? this->GetVersionedName()
+        : cmStrCat(this->Directory, '/', this->GetVersionedName());
+    }
 
     const std::string Directory;
+    const std::string Version;
     const std::string Name;
     const std::string Suffix;
   };
@@ -545,6 +569,8 @@
     return cm::nullopt;
   }
 
+  virtual bool SupportsLinkerDependencyFile() const { return false; }
+
   std::string GetSharedLibFlagsForLanguage(std::string const& lang) const;
 
   /** Generate an <output>.rule file path for a given command output.  */
@@ -591,7 +617,7 @@
 
   std::string MakeSilentFlag;
 
-  int RecursionDepth;
+  size_t RecursionDepth = 0;
 
   virtual void GetQtAutoGenConfigs(std::vector<std::string>& configs) const
   {
@@ -607,6 +633,13 @@
   cmInstallRuntimeDependencySet* GetNamedRuntimeDependencySet(
     const std::string& name);
 
+  enum class StripCommandStyle
+  {
+    Default,
+    Apple,
+  };
+  StripCommandStyle GetStripCommandStyle(std::string const& strip);
+
 protected:
   // for a project collect all its targets by following depend
   // information, and also collect all the targets
@@ -630,10 +663,6 @@
 
   void CxxModuleSupportCheck() const;
 
-  /// @brief Qt AUTOMOC/UIC/RCC target generation
-  /// @return true on success
-  bool QtAutoGen();
-
   bool AddHeaderSetVerification();
 
   bool AddAutomaticSources();
@@ -680,6 +709,11 @@
   cmake* CMakeInstance;
   std::vector<std::unique_ptr<cmMakefile>> Makefiles;
   LocalGeneratorVector LocalGenerators;
+
+#ifndef CMAKE_BOOTSTRAP
+  std::unique_ptr<cmQtAutoGenGlobalInitializer> QtAutoGen;
+#endif
+
   cmMakefile* CurrentConfigureMakefile;
   // map from project name to vector of local generators in that project
   std::map<std::string, std::vector<cmLocalGenerator*>> ProjectMap;
@@ -737,6 +771,10 @@
   std::map<std::string, int> LanguageToLinkerPreference;
   std::map<std::string, std::string> LanguageToOriginalSharedLibFlags;
 
+#ifdef __APPLE__
+  std::map<std::string, StripCommandStyle> StripCommandStyleMap;
+#endif
+
   mutable bool DiagnosedCxxModuleSupport = false;
 
   // Deferral id generation.
diff --git a/Source/cmGlobalGhsMultiGenerator.cxx b/Source/cmGlobalGhsMultiGenerator.cxx
index 3da15f6..2453bfc 100644
--- a/Source/cmGlobalGhsMultiGenerator.cxx
+++ b/Source/cmGlobalGhsMultiGenerator.cxx
@@ -18,11 +18,11 @@
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratorTarget.h"
 #include "cmGhsMultiGpj.h"
+#include "cmList.h"
 #include "cmLocalGenerator.h"
 #include "cmLocalGhsMultiGenerator.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
-#include "cmPolicies.h"
 #include "cmSourceFile.h"
 #include "cmState.h"
 #include "cmStateTypes.h"
@@ -101,11 +101,11 @@
 
   /* check if the toolset changed from last generate */
   if (cmNonempty(prevTool) && !cmSystemTools::ComparePath(gbuild, *prevTool)) {
-    std::string const& e =
-      cmStrCat("toolset build tool: ", gbuild,
-               "\nDoes not match the previously used build tool: ", *prevTool,
-               "\nEither remove the CMakeCache.txt file and CMakeFiles "
-               "directory or choose a different binary directory.");
+    std::string const& e = cmStrCat(
+      "toolset build tool: ", gbuild, '\n',
+      "Does not match the previously used build tool: ", *prevTool, '\n',
+      "Either remove the CMakeCache.txt file and CMakeFiles "
+      "directory or choose a different binary directory.");
     mf->IssueMessage(MessageType::FATAL_ERROR, e);
     return false;
   }
@@ -532,7 +532,7 @@
   fout << "macro PROJ_NAME=" << root->GetProjectName() << '\n';
   cmValue ghsGpjMacros = root->GetMakefile()->GetDefinition("GHS_GPJ_MACROS");
   if (ghsGpjMacros) {
-    std::vector<std::string> expandedList = cmExpandedList(*ghsGpjMacros);
+    cmList expandedList{ *ghsGpjMacros };
     for (std::string const& arg : expandedList) {
       fout << "macro " << arg << '\n';
     }
@@ -717,7 +717,6 @@
     cc->SetDepends(listFiles);
     cc->SetCommandLines(commandLines);
     cc->SetComment("Checking Build System");
-    cc->SetCMP0116Status(cmPolicies::NEW);
     cc->SetEscapeOldStyle(false);
     cc->SetStdPipesUTF8(true);
 
@@ -747,7 +746,6 @@
       // Use no actual command lines so that the target itself is not
       // considered always out of date.
       auto cc = cm::make_unique<cmCustomCommand>();
-      cc->SetCMP0116Status(cmPolicies::NEW);
       cc->SetEscapeOldStyle(false);
       cc->SetComment("Build all projects");
       cmTarget* allBuild = gen[0]->AddUtilityCommand(this->GetAllTargetName(),
diff --git a/Source/cmGlobalNinjaGenerator.cxx b/Source/cmGlobalNinjaGenerator.cxx
index f8e97db..8698e77 100644
--- a/Source/cmGlobalNinjaGenerator.cxx
+++ b/Source/cmGlobalNinjaGenerator.cxx
@@ -32,6 +32,7 @@
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
 #include "cmLinkLineComputer.h"
+#include "cmList.h"
 #include "cmListFileCache.h"
 #include "cmLocalGenerator.h"
 #include "cmLocalNinjaGenerator.h"
@@ -330,12 +331,6 @@
     }
   }
 
-  if (build.Variables.count("dyndep") > 0) {
-    // The ninja 'cleandead' operation does not account for outputs
-    // discovered by 'dyndep' bindings.  Avoid removing them.
-    this->DisableCleandead = true;
-  }
-
   os << buildStr << arguments << assignments << "\n";
 }
 
@@ -512,8 +507,18 @@
     return;
   }
 
+  std::string val;
+  static std::unordered_set<std::string> const variablesShouldNotBeTrimmed = {
+    "CODE_CHECK", "LAUNCHER"
+  };
+  if (variablesShouldNotBeTrimmed.find(name) ==
+      variablesShouldNotBeTrimmed.end()) {
+    val = cmTrimWhitespace(value);
+  } else {
+    val = value;
+  }
+
   // Do not add a variable if the value is empty.
-  std::string val = cmTrimWhitespace(value);
   if (val.empty()) {
     return;
   }
@@ -589,6 +594,7 @@
                                            msg.str());
     return;
   }
+  this->InitOutputPathPrefix();
   if (!this->OpenBuildFileStreams()) {
     return;
   }
@@ -600,10 +606,8 @@
     it.second.TargetDependsClosures.clear();
   }
 
-  this->InitOutputPathPrefix();
   this->TargetAll = this->NinjaOutputPath("all");
   this->CMakeCacheFile = this->NinjaOutputPath("CMakeCache.txt");
-  this->DisableCleandead = false;
   this->DiagnosedCxxModuleNinjaSupport = false;
   this->ClangTidyExportFixesDirs.clear();
   this->ClangTidyExportFixesFiles.clear();
@@ -1286,6 +1290,10 @@
     }
       CM_FALLTHROUGH;
     case cmStateEnums::EXECUTABLE: {
+      if (target->IsApple() && target->HasImportLibrary(config)) {
+        outputs.push_back(this->ConvertToNinjaPath(target->GetFullPath(
+          config, cmStateEnums::ImportLibraryArtifact, realname)));
+      }
       outputs.push_back(this->ConvertToNinjaPath(target->GetFullPath(
         config, cmStateEnums::RuntimeBinaryArtifact, realname)));
       break;
@@ -1372,17 +1380,7 @@
 }
 
 void cmGlobalNinjaGenerator::AppendTargetDependsClosure(
-  cmGeneratorTarget const* target, cmNinjaDeps& outputs,
-  const std::string& config, const std::string& fileConfig, bool genexOutput)
-{
-  cmNinjaOuts outs;
-  this->AppendTargetDependsClosure(target, outs, config, fileConfig,
-                                   genexOutput, true);
-  cm::append(outputs, outs);
-}
-
-void cmGlobalNinjaGenerator::AppendTargetDependsClosure(
-  cmGeneratorTarget const* target, cmNinjaOuts& outputs,
+  cmGeneratorTarget const* target, std::unordered_set<std::string>& outputs,
   const std::string& config, const std::string& fileConfig, bool genexOutput,
   bool omit_self)
 {
@@ -1403,7 +1401,8 @@
     // relevant for filling the cache entries properly isolated and a global
     // result set that is relevant for the result of the top level call to
     // AppendTargetDependsClosure.
-    cmNinjaOuts this_outs; // this will be the new cache entry
+    std::unordered_set<std::string>
+      this_outs; // this will be the new cache entry
 
     for (auto const& dep_target : this->GetTargetDirectDepends(target)) {
       if (!dep_target->IsInBuildSystem()) {
@@ -2076,9 +2075,10 @@
       build.Outputs.front() = this->BuildAlias(
         this->NinjaOutputPath(this->GetCleanTargetName()), config);
       if (this->IsMultiConfig()) {
-        build.Variables["TARGETS"] =
-          cmStrCat(this->BuildAlias(GetByproductsForCleanTargetName(), config),
-                   " ", GetByproductsForCleanTargetName());
+        build.Variables["TARGETS"] = cmStrCat(
+          this->BuildAlias(
+            this->NinjaOutputPath(GetByproductsForCleanTargetName()), config),
+          " ", this->NinjaOutputPath(GetByproductsForCleanTargetName()));
       }
       build.ExplicitDeps.clear();
       if (additionalFiles) {
@@ -2093,7 +2093,8 @@
         if (this->IsMultiConfig()) {
           build.Variables["FILE_ARG"] = cmStrCat(
             "-f ",
-            cmGlobalNinjaMultiGenerator::GetNinjaImplFilename(fileConfig));
+            this->NinjaOutputPath(
+              cmGlobalNinjaMultiGenerator::GetNinjaImplFilename(fileConfig)));
         }
         this->WriteBuild(*this->GetImplFileStream(fileConfig), build);
       }
@@ -2115,8 +2116,8 @@
       std::vector<std::string> byproducts;
       byproducts.reserve(this->CrossConfigs.size());
       for (auto const& config : this->CrossConfigs) {
-        byproducts.push_back(
-          this->BuildAlias(GetByproductsForCleanTargetName(), config));
+        byproducts.push_back(this->BuildAlias(
+          this->NinjaOutputPath(GetByproductsForCleanTargetName()), config));
       }
       byproducts.emplace_back(GetByproductsForCleanTargetName());
       build.Variables["TARGETS"] = cmJoin(byproducts, " ");
@@ -2124,7 +2125,8 @@
       for (auto const& fileConfig : configs) {
         build.Variables["FILE_ARG"] = cmStrCat(
           "-f ",
-          cmGlobalNinjaMultiGenerator::GetNinjaImplFilename(fileConfig));
+          this->NinjaOutputPath(
+            cmGlobalNinjaMultiGenerator::GetNinjaImplFilename(fileConfig)));
         this->WriteBuild(*this->GetImplFileStream(fileConfig), build);
       }
     }
@@ -2234,9 +2236,9 @@
       depfile = $DEP_FILE
       command = gfortran -cpp $DEFINES $INCLUDES $FLAGS -E $in -o $out &&
                 cmake -E cmake_ninja_depends \
-                  --tdi=FortranDependInfo.json --pp=$out --dep=$DEP_FILE \
-                  --obj=$OBJ_FILE --ddi=$DYNDEP_INTERMEDIATE_FILE \
-                  --lang=Fortran
+                  --tdi=FortranDependInfo.json --lang=Fortran \
+                  --src=$out --out=$out --dep=$DEP_FILE --obj=$OBJ_FILE \
+                  --ddi=$DYNDEP_INTERMEDIATE_FILE
 
     build src.f90-pp.f90 | src.f90.o.ddi: Fortran_PREPROCESS src.f90
       OBJ_FILE = src.f90.o
@@ -2304,14 +2306,15 @@
 };
 
 cm::optional<cmSourceInfo> cmcmd_cmake_ninja_depends_fortran(
-  std::string const& arg_tdi, std::string const& arg_pp);
+  std::string const& arg_tdi, std::string const& arg_src);
 }
 
 int cmcmd_cmake_ninja_depends(std::vector<std::string>::const_iterator argBeg,
                               std::vector<std::string>::const_iterator argEnd)
 {
   std::string arg_tdi;
-  std::string arg_pp;
+  std::string arg_src;
+  std::string arg_out;
   std::string arg_dep;
   std::string arg_obj;
   std::string arg_ddi;
@@ -2319,8 +2322,10 @@
   for (std::string const& arg : cmMakeRange(argBeg, argEnd)) {
     if (cmHasLiteralPrefix(arg, "--tdi=")) {
       arg_tdi = arg.substr(6);
-    } else if (cmHasLiteralPrefix(arg, "--pp=")) {
-      arg_pp = arg.substr(5);
+    } else if (cmHasLiteralPrefix(arg, "--src=")) {
+      arg_src = arg.substr(6);
+    } else if (cmHasLiteralPrefix(arg, "--out=")) {
+      arg_out = arg.substr(6);
     } else if (cmHasLiteralPrefix(arg, "--dep=")) {
       arg_dep = arg.substr(6);
     } else if (cmHasLiteralPrefix(arg, "--obj=")) {
@@ -2329,6 +2334,10 @@
       arg_ddi = arg.substr(6);
     } else if (cmHasLiteralPrefix(arg, "--lang=")) {
       arg_lang = arg.substr(7);
+    } else if (cmHasLiteralPrefix(arg, "--pp=")) {
+      // CMake 3.26 and below used '--pp=' instead of '--src=' and '--out='.
+      arg_src = arg.substr(5);
+      arg_out = arg_src;
     } else {
       cmSystemTools::Error(
         cmStrCat("-E cmake_ninja_depends unknown argument: ", arg));
@@ -2339,8 +2348,12 @@
     cmSystemTools::Error("-E cmake_ninja_depends requires value for --tdi=");
     return 1;
   }
-  if (arg_pp.empty()) {
-    cmSystemTools::Error("-E cmake_ninja_depends requires value for --pp=");
+  if (arg_src.empty()) {
+    cmSystemTools::Error("-E cmake_ninja_depends requires value for --src=");
+    return 1;
+  }
+  if (arg_out.empty()) {
+    cmSystemTools::Error("-E cmake_ninja_depends requires value for --out=");
     return 1;
   }
   if (arg_dep.empty()) {
@@ -2362,7 +2375,7 @@
 
   cm::optional<cmSourceInfo> info;
   if (arg_lang == "Fortran") {
-    info = cmcmd_cmake_ninja_depends_fortran(arg_tdi, arg_pp);
+    info = cmcmd_cmake_ninja_depends_fortran(arg_tdi, arg_src);
   } else {
     cmSystemTools::Error(
       cmStrCat("-E cmake_ninja_depends does not understand the ", arg_lang,
@@ -2379,7 +2392,7 @@
 
   {
     cmGeneratedFileStream depfile(arg_dep);
-    depfile << cmSystemTools::ConvertToUnixOutputPath(arg_pp) << ":";
+    depfile << cmSystemTools::ConvertToUnixOutputPath(arg_out) << ":";
     for (std::string const& include : info->Includes) {
       depfile << " \\\n " << cmSystemTools::ConvertToUnixOutputPath(include);
     }
@@ -2397,7 +2410,7 @@
 namespace {
 
 cm::optional<cmSourceInfo> cmcmd_cmake_ninja_depends_fortran(
-  std::string const& arg_tdi, std::string const& arg_pp)
+  std::string const& arg_tdi, std::string const& arg_src)
 {
   cm::optional<cmSourceInfo> info;
   cmFortranCompiler fc;
@@ -2449,9 +2462,9 @@
   cmFortranSourceInfo finfo;
   std::set<std::string> defines;
   cmFortranParser parser(fc, includes, defines, finfo);
-  if (!cmFortranParser_FilePush(&parser, arg_pp.c_str())) {
+  if (!cmFortranParser_FilePush(&parser, arg_src.c_str())) {
     cmSystemTools::Error(
-      cmStrCat("-E cmake_ninja_depends failed to open ", arg_pp));
+      cmStrCat("-E cmake_ninja_depends failed to open ", arg_src));
     return info;
   }
   if (cmFortran_yyparse(parser.Scanner) != 0) {
@@ -2523,7 +2536,12 @@
   CxxModuleUsage usages;
 
   // Map from module name to module file path, if known.
-  std::map<std::string, std::string> mod_files;
+  struct AvailableModuleInfo
+  {
+    std::string BmiPath;
+    bool IsPrivate;
+  };
+  std::map<std::string, AvailableModuleInfo> mod_files;
 
   // Populate the module map with those provided by linked targets first.
   for (std::string const& linked_target_dir : linked_target_dirs) {
@@ -2547,7 +2565,15 @@
       Json::Value const& target_modules = ltm["modules"];
       if (target_modules.isObject()) {
         for (auto i = target_modules.begin(); i != target_modules.end(); ++i) {
-          mod_files[i.key().asString()] = i->asString();
+          Json::Value const& visible_module = *i;
+          if (visible_module.isObject()) {
+            Json::Value const& bmi_path = visible_module["bmi"];
+            Json::Value const& is_private = visible_module["is-private"];
+            mod_files[i.key().asString()] = AvailableModuleInfo{
+              bmi_path.asString(),
+              is_private.asBool(),
+            };
+          }
         }
       }
       Json::Value const& target_modules_references = ltm["references"];
@@ -2628,8 +2654,15 @@
         cmSystemTools::ReplaceString(safe_logical_name, ":", "-");
         mod = cmStrCat(module_dir, safe_logical_name, module_ext);
       }
-      mod_files[p.LogicalName] = mod;
-      target_modules[p.LogicalName] = mod;
+      mod_files[p.LogicalName] = AvailableModuleInfo{
+        mod,
+        false, // Always visible within our own target.
+      };
+      Json::Value& module_info = target_modules[p.LogicalName] =
+        Json::objectValue;
+      module_info["bmi"] = mod;
+      module_info["is-private"] =
+        cmDyndepCollation::IsObjectPrivate(object.PrimaryOutput, export_info);
     }
   }
 
@@ -2649,12 +2682,15 @@
       return path;
     };
     locs.BmiLocationForModule =
-      [&mod_files](std::string const& logical) -> cm::optional<std::string> {
+      [&mod_files](std::string const& logical) -> CxxBmiLocation {
       auto m = mod_files.find(logical);
       if (m != mod_files.end()) {
-        return m->second;
+        if (m->second.IsPrivate) {
+          return CxxBmiLocation::Private();
+        }
+        return CxxBmiLocation::Known(m->second.BmiPath);
       }
-      return {};
+      return CxxBmiLocation::Unknown();
     };
 
     // Insert information about the current target's modules.
@@ -2676,13 +2712,14 @@
       build.ImplicitOuts.clear();
       for (auto const& p : object.Provides) {
         build.ImplicitOuts.push_back(
-          this->ConvertToNinjaPath(mod_files[p.LogicalName]));
+          this->ConvertToNinjaPath(mod_files[p.LogicalName].BmiPath));
       }
       build.ImplicitDeps.clear();
       for (auto const& r : object.Requires) {
         auto mit = mod_files.find(r.LogicalName);
         if (mit != mod_files.end()) {
-          build.ImplicitDeps.push_back(this->ConvertToNinjaPath(mit->second));
+          build.ImplicitDeps.push_back(
+            this->ConvertToNinjaPath(mit->second.BmiPath));
         }
       }
       build.Variables.clear();
@@ -2748,7 +2785,7 @@
     [mod_files](std::string const& name) -> cm::optional<std::string> {
     auto m = mod_files.find(name);
     if (m != mod_files.end()) {
-      return m->second;
+      return m->second.BmiPath;
     }
     return {};
   };
@@ -2924,7 +2961,8 @@
   *this->DefaultFileStream << "# Build using rules for '"
                            << this->DefaultFileConfig << "'.\n\n"
                            << "include "
-                           << GetNinjaImplFilename(this->DefaultFileConfig)
+                           << this->NinjaOutputPath(
+                                GetNinjaImplFilename(this->DefaultFileConfig))
                            << "\n\n";
 
   // Write a comment about this file.
@@ -2957,7 +2995,8 @@
       *this->ConfigFileStreams[config]
         << "# This file contains aliases specific to the \"" << config
         << "\"\n# configuration.\n\n"
-        << "include " << GetNinjaImplFilename(config) << "\n\n";
+        << "include " << this->NinjaOutputPath(GetNinjaImplFilename(config))
+        << "\n\n";
 
       return true;
     });
@@ -3038,19 +3077,17 @@
 
 bool cmGlobalNinjaMultiGenerator::InspectConfigTypeVariables()
 {
-  std::vector<std::string> configsVec;
-  cmExpandList(
-    this->Makefiles.front()->GetSafeDefinition("CMAKE_CONFIGURATION_TYPES"),
-    configsVec);
-  if (configsVec.empty()) {
-    configsVec.emplace_back();
+  cmList configsList{ this->Makefiles.front()->GetDefinition(
+    "CMAKE_CONFIGURATION_TYPES") };
+  if (configsList.empty()) {
+    configsList.emplace_back();
   }
-  std::set<std::string> configs(configsVec.cbegin(), configsVec.cend());
+  std::set<std::string> configs(configsList.cbegin(), configsList.cend());
 
   this->DefaultFileConfig =
     this->Makefiles.front()->GetSafeDefinition("CMAKE_DEFAULT_BUILD_TYPE");
   if (this->DefaultFileConfig.empty()) {
-    this->DefaultFileConfig = configsVec.front();
+    this->DefaultFileConfig = configsList.front();
   }
   if (!configs.count(this->DefaultFileConfig)) {
     std::ostringstream msg;
@@ -3062,11 +3099,9 @@
     return false;
   }
 
-  std::vector<std::string> crossConfigsVec;
-  cmExpandList(
-    this->Makefiles.front()->GetSafeDefinition("CMAKE_CROSS_CONFIGS"),
-    crossConfigsVec);
-  auto crossConfigs = ListSubsetWithAll(configs, configs, crossConfigsVec);
+  cmList crossConfigsList{ this->Makefiles.front()->GetSafeDefinition(
+    "CMAKE_CROSS_CONFIGS") };
+  auto crossConfigs = ListSubsetWithAll(configs, configs, crossConfigsList);
   if (!crossConfigs) {
     std::ostringstream msg;
     msg << "CMAKE_CROSS_CONFIGS is not a subset of "
@@ -3093,12 +3128,11 @@
     return false;
   }
 
-  std::vector<std::string> defaultConfigsVec;
-  cmExpandList(defaultConfigsString, defaultConfigsVec);
+  cmList defaultConfigsList(defaultConfigsString);
   if (!this->DefaultFileConfig.empty()) {
     auto defaultConfigs =
       ListSubsetWithAll(this->GetCrossConfigs(this->DefaultFileConfig),
-                        this->CrossConfigs, defaultConfigsVec);
+                        this->CrossConfigs, defaultConfigsList);
     if (!defaultConfigs) {
       std::ostringstream msg;
       msg << "CMAKE_DEFAULT_CONFIGS is not a subset of CMAKE_CROSS_CONFIGS";
diff --git a/Source/cmGlobalNinjaGenerator.h b/Source/cmGlobalNinjaGenerator.h
index 7f01b09..bfbe57f 100644
--- a/Source/cmGlobalNinjaGenerator.h
+++ b/Source/cmGlobalNinjaGenerator.h
@@ -236,6 +236,8 @@
     return cmDepfileFormat::GccDepfile;
   }
 
+  bool SupportsLinkerDependencyFile() const override { return true; }
+
   virtual cmGeneratedFileStream* GetImplFileStream(
     const std::string& /*config*/) const
   {
@@ -351,15 +353,10 @@
                            const std::string& fileConfig,
                            cmNinjaTargetDepends depends);
   void AppendTargetDependsClosure(cmGeneratorTarget const* target,
-                                  cmNinjaDeps& outputs,
+                                  std::unordered_set<std::string>& outputs,
                                   const std::string& config,
                                   const std::string& fileConfig,
-                                  bool genexOutput);
-  void AppendTargetDependsClosure(cmGeneratorTarget const* target,
-                                  cmNinjaOuts& outputs,
-                                  const std::string& config,
-                                  const std::string& fileConfig,
-                                  bool genexOutput, bool omit_self);
+                                  bool genexOutput, bool omit_self = true);
 
   void AppendDirectoryForConfig(const std::string& prefix,
                                 const std::string& config,
@@ -602,7 +599,6 @@
   std::string OutputPathPrefix;
   std::string TargetAll;
   std::string CMakeCacheFile;
-  bool DisableCleandead = false;
 
   struct ByConfig
   {
@@ -618,7 +614,8 @@
       bool GenexOutput;
     };
 
-    std::map<TargetDependsClosureKey, cmNinjaOuts> TargetDependsClosures;
+    std::map<TargetDependsClosureKey, std::unordered_set<std::string>>
+      TargetDependsClosures;
 
     TargetAliasMap TargetAliases;
 
diff --git a/Source/cmGlobalUnixMakefileGenerator3.h b/Source/cmGlobalUnixMakefileGenerator3.h
index 214ba2a..760679a 100644
--- a/Source/cmGlobalUnixMakefileGenerator3.h
+++ b/Source/cmGlobalUnixMakefileGenerator3.h
@@ -99,6 +99,12 @@
    */
   bool SupportsCustomCommandDepfile() const override { return true; }
 
+  /**
+   * Utilized to determine if this generator
+   * supports linker dependency file.
+   */
+  bool SupportsLinkerDependencyFile() const override { return true; }
+
   /** Get the documentation entry for this generator.  */
   static cmDocumentationEntry GetDocumentation();
 
diff --git a/Source/cmGlobalVisualStudio10Generator.cxx b/Source/cmGlobalVisualStudio10Generator.cxx
index 1e01dd6..321f377 100644
--- a/Source/cmGlobalVisualStudio10Generator.cxx
+++ b/Source/cmGlobalVisualStudio10Generator.cxx
@@ -525,6 +525,30 @@
   return false;
 }
 
+bool cmGlobalVisualStudio10Generator::InitializePlatform(cmMakefile* mf)
+{
+  if (this->SystemName == "Windows" || this->SystemName == "WindowsStore") {
+    if (!this->InitializePlatformWindows(mf)) {
+      return false;
+    }
+  } else if (!this->SystemName.empty() &&
+             !this->VerifyNoGeneratorPlatformVersion(mf)) {
+    return false;
+  }
+  return this->cmGlobalVisualStudio8Generator::InitializePlatform(mf);
+}
+
+bool cmGlobalVisualStudio10Generator::InitializePlatformWindows(cmMakefile*)
+{
+  return true;
+}
+
+bool cmGlobalVisualStudio10Generator::VerifyNoGeneratorPlatformVersion(
+  cmMakefile*, cm::optional<std::string>) const
+{
+  return true;
+}
+
 bool cmGlobalVisualStudio10Generator::SelectWindowsPhoneToolset(
   std::string& toolset) const
 {
diff --git a/Source/cmGlobalVisualStudio10Generator.h b/Source/cmGlobalVisualStudio10Generator.h
index deed206..38942cb 100644
--- a/Source/cmGlobalVisualStudio10Generator.h
+++ b/Source/cmGlobalVisualStudio10Generator.h
@@ -149,6 +149,8 @@
 
   virtual bool IsUtf8EncodingSupported() const { return false; }
 
+  virtual bool IsScanDependenciesSupported() const { return false; }
+
   static std::string GetInstalledNsightTegraVersion();
 
   /** Return the first two components of CMAKE_SYSTEM_VERSION.  */
@@ -183,6 +185,11 @@
   virtual bool InitializeTegraAndroid(cmMakefile* mf);
   virtual bool InitializeAndroid(cmMakefile* mf);
 
+  bool InitializePlatform(cmMakefile* mf) override;
+  virtual bool InitializePlatformWindows(cmMakefile* mf);
+  virtual bool VerifyNoGeneratorPlatformVersion(
+    cmMakefile* mf, cm::optional<std::string> reason = cm::nullopt) const;
+
   virtual bool ProcessGeneratorToolsetField(std::string const& key,
                                             std::string const& value);
 
diff --git a/Source/cmGlobalVisualStudio14Generator.cxx b/Source/cmGlobalVisualStudio14Generator.cxx
index 7424ca3..4300d5c 100644
--- a/Source/cmGlobalVisualStudio14Generator.cxx
+++ b/Source/cmGlobalVisualStudio14Generator.cxx
@@ -12,6 +12,7 @@
 #include "cmGlobalVisualStudioGenerator.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
+#include "cmPolicies.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmValue.h"
@@ -137,12 +138,36 @@
   return false;
 }
 
-bool cmGlobalVisualStudio14Generator::InitializeWindows(cmMakefile* mf)
+bool cmGlobalVisualStudio14Generator::InitializePlatformWindows(cmMakefile* mf)
 {
   if (cmHasLiteralPrefix(this->SystemVersion, "10.0")) {
-    return this->SelectWindows10SDK(mf, false);
+    return this->SelectWindows10SDK(mf);
   }
-  return true;
+  return this->VerifyNoGeneratorPlatformVersion(mf);
+}
+
+bool cmGlobalVisualStudio14Generator::VerifyNoGeneratorPlatformVersion(
+  cmMakefile* mf, cm::optional<std::string> reason) const
+{
+  if (!this->GeneratorPlatformVersion) {
+    return true;
+  }
+  std::ostringstream e;
+  /* clang-format off */
+  e <<
+    "Generator\n"
+    "  " << this->GetName() << "\n"
+    "given platform specification containing a\n"
+    "  version=" << *this->GeneratorPlatformVersion << "\n"
+    "field.  The version field is not supported when targeting\n"
+    "  " << this->SystemName << " " << this->SystemVersion << "\n"
+    ;
+  /* clang-format on */
+  if (reason) {
+    e << *reason << ".";
+  }
+  mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
+  return false;
 }
 
 bool cmGlobalVisualStudio14Generator::InitializeWindowsStore(cmMakefile* mf)
@@ -162,9 +187,6 @@
     mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
     return false;
   }
-  if (cmHasLiteralPrefix(this->SystemVersion, "10.0")) {
-    return this->SelectWindows10SDK(mf, true);
-  }
   return true;
 }
 
@@ -173,19 +195,51 @@
   return true;
 }
 
-bool cmGlobalVisualStudio14Generator::SelectWindows10SDK(cmMakefile* mf,
-                                                         bool required)
+bool cmGlobalVisualStudio14Generator::ProcessGeneratorPlatformField(
+  std::string const& key, std::string const& value)
 {
+  if (key == "version") {
+    this->GeneratorPlatformVersion = value;
+    return true;
+  }
+  return false;
+}
+
+bool cmGlobalVisualStudio14Generator::SelectWindows10SDK(cmMakefile* mf)
+{
+  if (this->GeneratorPlatformVersion &&
+      this->GeneratorPlatformVersion->empty()) {
+    mf->IssueMessage(
+      MessageType::FATAL_ERROR,
+      cmStrCat("Generator\n  ", this->GetName(),
+               "\ngiven platform specification with empty\n  version=\n"
+               "field."));
+    return false;
+  }
+
   // Find the default version of the Windows 10 SDK.
   std::string const version = this->GetWindows10SDKVersion(mf);
 
-  if (required && version.empty()) {
-    std::ostringstream e;
-    e << "Could not find an appropriate version of the Windows 10 SDK"
-      << " installed on this machine";
-    mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
-    return false;
+  if (version.empty()) {
+    if (this->GeneratorPlatformVersion) {
+      mf->IssueMessage(
+        MessageType::FATAL_ERROR,
+        cmStrCat("Generator\n  ", this->GetName(),
+                 "\ngiven platform specification with\n  version=",
+                 *this->GeneratorPlatformVersion,
+                 "\nfield, but no Windows SDK with that version was found."));
+      return false;
+    }
+
+    if (this->SystemName == "WindowsStore") {
+      mf->IssueMessage(
+        MessageType::FATAL_ERROR,
+        "Could not find an appropriate version of the Windows 10 SDK"
+        " installed on this machine");
+      return false;
+    }
   }
+
   this->SetWindowsTargetPlatformVersion(version, mf);
   return true;
 }
@@ -302,6 +356,16 @@
   cmMakefile* mf)
 {
 #if defined(_WIN32) && !defined(__CYGWIN__)
+  // Accept specific version requests as-is.
+  if (this->GeneratorPlatformVersion) {
+    std::string const& ver = *this->GeneratorPlatformVersion;
+
+    // VS 2019 and above support specifying plain "10.0".
+    if (this->Version >= VSVersion::VS16 && ver == "10.0") {
+      return ver;
+    }
+  }
+
   std::vector<std::string> win10Roots;
 
   {
@@ -360,10 +424,35 @@
   // Sort the results to make sure we select the most recent one.
   std::sort(sdks.begin(), sdks.end(), cmSystemTools::VersionCompareGreater);
 
-  // Look for a SDK exactly matching the requested target version.
-  for (std::string const& i : sdks) {
-    if (cmSystemTools::VersionCompareEqual(i, this->SystemVersion)) {
-      return i;
+  // Look for a SDK exactly matching the requested version, if any.
+  if (this->GeneratorPlatformVersion) {
+    for (std::string const& i : sdks) {
+      if (cmSystemTools::VersionCompareEqual(
+            i, *this->GeneratorPlatformVersion)) {
+        return i;
+      }
+    }
+    // An exact version was requested but not found.
+    // Our caller will issue the error message.
+    return std::string();
+  }
+
+  if (mf->GetPolicyStatus(cmPolicies::CMP0149) == cmPolicies::NEW) {
+    if (cm::optional<std::string> const envVer =
+          cmSystemTools::GetEnvVar("WindowsSDKVersion")) {
+      // Look for a SDK exactly matching the environment variable.
+      for (std::string const& i : sdks) {
+        if (cmSystemTools::VersionCompareEqual(i, *envVer)) {
+          return i;
+        }
+      }
+    }
+  } else {
+    // Look for a SDK exactly matching the target Windows version.
+    for (std::string const& i : sdks) {
+      if (cmSystemTools::VersionCompareEqual(i, this->SystemVersion)) {
+        return i;
+      }
     }
   }
 
diff --git a/Source/cmGlobalVisualStudio14Generator.h b/Source/cmGlobalVisualStudio14Generator.h
index 7fb9b4b..f59a323 100644
--- a/Source/cmGlobalVisualStudio14Generator.h
+++ b/Source/cmGlobalVisualStudio14Generator.h
@@ -7,6 +7,8 @@
 #include <memory>
 #include <string>
 
+#include <cm/optional>
+
 #include "cmGlobalVisualStudio12Generator.h"
 
 class cmGlobalGeneratorFactory;
@@ -30,7 +32,6 @@
   cmGlobalVisualStudio14Generator(cmake* cm, const std::string& name,
                                   std::string const& platformInGeneratorName);
 
-  bool InitializeWindows(cmMakefile* mf) override;
   bool InitializeWindowsStore(cmMakefile* mf) override;
   bool InitializeAndroid(cmMakefile* mf) override;
   bool SelectWindowsStoreToolset(std::string& toolset) const override;
@@ -39,6 +40,14 @@
   // of the toolset is installed
   bool IsWindowsStoreToolsetInstalled() const;
 
+  bool InitializePlatformWindows(cmMakefile* mf) override;
+  bool VerifyNoGeneratorPlatformVersion(
+    cmMakefile* mf,
+    cm::optional<std::string> reason = cm::nullopt) const override;
+
+  bool ProcessGeneratorPlatformField(std::string const& key,
+                                     std::string const& value) override;
+
   // Used to adjust the max-SDK-version calculation to accommodate user
   // configuration.
   std::string GetWindows10SDKMaxVersion(cmMakefile* mf) const;
@@ -47,7 +56,7 @@
   // version of the toolset.
   virtual std::string GetWindows10SDKMaxVersionDefault(cmMakefile* mf) const;
 
-  virtual bool SelectWindows10SDK(cmMakefile* mf, bool required);
+  virtual bool SelectWindows10SDK(cmMakefile* mf);
 
   void SetWindowsTargetPlatformVersion(std::string const& version,
                                        cmMakefile* mf);
@@ -61,4 +70,6 @@
 private:
   class Factory;
   friend class Factory;
+
+  cm::optional<std::string> GeneratorPlatformVersion;
 };
diff --git a/Source/cmGlobalVisualStudio71Generator.cxx b/Source/cmGlobalVisualStudio71Generator.cxx
index de13924..bcb26cc 100644
--- a/Source/cmGlobalVisualStudio71Generator.cxx
+++ b/Source/cmGlobalVisualStudio71Generator.cxx
@@ -8,6 +8,7 @@
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
 #include "cmGlobalVisualStudioGenerator.h"
+#include "cmList.h"
 #include "cmListFileCache.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
@@ -205,12 +206,12 @@
     !platformMapping.empty() ? platformMapping : this->GetPlatformName();
   std::string guid = this->GetGUID(name);
   for (std::string const& i : configs) {
-    std::vector<std::string> mapConfig;
+    cmList mapConfig;
     const char* dstConfig = i.c_str();
     if (target.GetProperty("EXTERNAL_MSPROJECT")) {
       if (cmValue m = target.GetProperty("MAP_IMPORTED_CONFIG_" +
                                          cmSystemTools::UpperCase(i))) {
-        cmExpandList(*m, mapConfig);
+        mapConfig.assign(*m);
         if (!mapConfig.empty()) {
           dstConfig = mapConfig[0].c_str();
         }
diff --git a/Source/cmGlobalVisualStudio7Generator.cxx b/Source/cmGlobalVisualStudio7Generator.cxx
index d483135..b254777 100644
--- a/Source/cmGlobalVisualStudio7Generator.cxx
+++ b/Source/cmGlobalVisualStudio7Generator.cxx
@@ -17,6 +17,7 @@
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
+#include "cmList.h"
 #include "cmLocalGenerator.h"
 #include "cmLocalVisualStudio7Generator.h"
 #include "cmMakefile.h"
@@ -309,6 +310,26 @@
                                 GetSLNFile(this->LocalGenerators[0].get()));
   }
 
+  if (this->Version == VSVersion::VS9 &&
+      !this->CMakeInstance->GetIsInTryCompile()) {
+    std::string cmakeWarnVS9;
+    if (cmValue cached = this->CMakeInstance->GetState()->GetCacheEntryValue(
+          "CMAKE_WARN_VS9")) {
+      this->CMakeInstance->MarkCliAsUsed("CMAKE_WARN_VS9");
+      cmakeWarnVS9 = *cached;
+    } else {
+      cmSystemTools::GetEnv("CMAKE_WARN_VS9", cmakeWarnVS9);
+    }
+    if (cmakeWarnVS9.empty() || !cmIsOff(cmakeWarnVS9)) {
+      this->CMakeInstance->IssueMessage(
+        MessageType::WARNING,
+        "The \"Visual Studio 9 2008\" generator is deprecated "
+        "and will be removed in a future version of CMake."
+        "\n"
+        "Add CMAKE_WARN_VS9=OFF to the cache to disable this warning.");
+    }
+  }
+
   if (this->Version == VSVersion::VS11 &&
       !this->CMakeInstance->GetIsInTryCompile()) {
     std::string cmakeWarnVS11;
@@ -560,7 +581,7 @@
         }
         fout << "\tGlobalSection(" << name << ") = " << sectionType << "\n";
         cmValue p = root->GetMakefile()->GetProperty(it);
-        std::vector<std::string> keyValuePairs = cmExpandedList(p ? *p : "");
+        cmList keyValuePairs{ *p };
         for (std::string const& itPair : keyValuePairs) {
           const std::string::size_type posEqual = itPair.find('=');
           if (posEqual != std::string::npos) {
@@ -695,7 +716,7 @@
   cmGeneratorTarget const* target)
 {
   std::set<std::string> activeConfigs;
-  // if it is a utilitiy target then only make it part of the
+  // if it is a utility target then only make it part of the
   // default build if another target depends on it
   int type = target->GetType();
   if (type == cmStateEnums::GLOBAL_TARGET) {
diff --git a/Source/cmGlobalVisualStudio8Generator.cxx b/Source/cmGlobalVisualStudio8Generator.cxx
index 647fc2d..819bb09 100644
--- a/Source/cmGlobalVisualStudio8Generator.cxx
+++ b/Source/cmGlobalVisualStudio8Generator.cxx
@@ -20,6 +20,7 @@
 #include "cmGlobalGenerator.h"
 #include "cmGlobalVisualStudio7Generator.h"
 #include "cmGlobalVisualStudioGenerator.h"
+#include "cmList.h"
 #include "cmListFileCache.h"
 #include "cmLocalGenerator.h"
 #include "cmLocalVisualStudio7Generator.h"
@@ -94,7 +95,9 @@
     return this->cmGlobalVisualStudio7Generator::SetGeneratorPlatform(p, mf);
   }
 
-  this->GeneratorPlatform = p;
+  if (!this->ParseGeneratorPlatform(p, mf)) {
+    return false;
+  }
 
   // FIXME: Add CMAKE_GENERATOR_PLATFORM field to set the framework.
   // For now, just report the generator's default, if any.
@@ -114,12 +117,100 @@
                       *targetFrameworkTargetsVersion);
   }
 
+  if (!this->InitializePlatform(mf)) {
+    return false;
+  }
+
   // The generator name does not contain the platform name, and so supports
   // explicit platform specification.  We handled that above, so pass an
   // empty platform name to our base class implementation so it does not error.
   return this->cmGlobalVisualStudio7Generator::SetGeneratorPlatform("", mf);
 }
 
+bool cmGlobalVisualStudio8Generator::ParseGeneratorPlatform(
+  std::string const& p, cmMakefile* mf)
+{
+  this->GeneratorPlatform.clear();
+
+  std::vector<std::string> const fields = cmTokenize(p, ",");
+  auto fi = fields.begin();
+  if (fi == fields.end()) {
+    return true;
+  }
+
+  // The first field may be the VS platform.
+  if (fi->find('=') == fi->npos) {
+    this->GeneratorPlatform = *fi;
+    ++fi;
+  }
+
+  std::set<std::string> handled;
+
+  // The rest of the fields must be key=value pairs.
+  for (; fi != fields.end(); ++fi) {
+    std::string::size_type pos = fi->find('=');
+    if (pos == fi->npos) {
+      std::ostringstream e;
+      /* clang-format off */
+      e <<
+        "Generator\n"
+        "  " << this->GetName() << "\n"
+        "given platform specification\n"
+        "  " << p << "\n"
+        "that contains a field after the first ',' with no '='."
+        ;
+      /* clang-format on */
+      mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
+      return false;
+    }
+    std::string const key = fi->substr(0, pos);
+    std::string const value = fi->substr(pos + 1);
+    if (!handled.insert(key).second) {
+      std::ostringstream e;
+      /* clang-format off */
+      e <<
+        "Generator\n"
+        "  " << this->GetName() << "\n"
+        "given platform specification\n"
+        "  " << p << "\n"
+        "that contains duplicate field key '" << key << "'."
+        ;
+      /* clang-format on */
+      mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
+      return false;
+    }
+    if (!this->ProcessGeneratorPlatformField(key, value)) {
+      std::ostringstream e;
+      /* clang-format off */
+      e <<
+        "Generator\n"
+        "  " << this->GetName() << "\n"
+        "given platform specification\n"
+        "  " << p << "\n"
+        "that contains invalid field '" << *fi << "'."
+        ;
+      /* clang-format on */
+      mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool cmGlobalVisualStudio8Generator::ProcessGeneratorPlatformField(
+  std::string const& key, std::string const& value)
+{
+  static_cast<void>(key);
+  static_cast<void>(value);
+  return false;
+}
+
+bool cmGlobalVisualStudio8Generator::InitializePlatform(cmMakefile*)
+{
+  return true;
+}
+
 cm::optional<std::string> const&
 cmGlobalVisualStudio8Generator::GetTargetFrameworkVersion() const
 {
@@ -169,10 +260,36 @@
     cm::static_reference_cast<cmLocalVisualStudio7Generator>(generators[0]);
 
   auto cc = cm::make_unique<cmCustomCommand>();
-  cc->SetCMP0116Status(cmPolicies::NEW);
   cmTarget* tgt = lg.AddUtilityCommand(CMAKE_CHECK_BUILD_SYSTEM_TARGET, false,
                                        std::move(cc));
 
+  // Collect the input files used to generate all targets in this
+  // project.
+  std::vector<std::string> listFiles;
+  for (const auto& gen : generators) {
+    cm::append(listFiles, gen->GetMakefile()->GetListFiles());
+  }
+  // Sort the list of input files and remove duplicates.
+  std::sort(listFiles.begin(), listFiles.end(), std::less<std::string>());
+  auto new_end = std::unique(listFiles.begin(), listFiles.end());
+  listFiles.erase(new_end, listFiles.end());
+
+  // Add all cmake input files which are used by the project
+  // so Visual Studio does not close them when reloading it.
+  for (const std::string& listFile : listFiles) {
+    if (listFile.find("/CMakeFiles/") != std::string::npos) {
+      continue;
+    }
+    if (!cmSystemTools::IsSubDirectory(listFile,
+                                       lg.GetMakefile()->GetHomeDirectory()) &&
+        !cmSystemTools::IsSubDirectory(
+          listFile, lg.GetMakefile()->GetHomeOutputDirectory())) {
+      continue;
+    }
+
+    tgt->AddSource(listFile);
+  }
+
   auto ptr = cm::make_unique<cmGeneratorTarget>(tgt, &lg);
   auto* gt = ptr.get();
   lg.AddGeneratorTarget(std::move(ptr));
@@ -206,13 +323,6 @@
     // The custom rule runs cmake so set UTF-8 pipes.
     bool stdPipesUTF8 = true;
 
-    // Collect the input files used to generate all targets in this
-    // project.
-    std::vector<std::string> listFiles;
-    for (const auto& gen : generators) {
-      cm::append(listFiles, gen->GetMakefile()->GetListFiles());
-    }
-
     // Add a custom prebuild target to run the VerifyGlobs script.
     cmake* cm = this->GetCMakeInstance();
     if (cm->DoWriteGlobVerifyTarget()) {
@@ -225,7 +335,6 @@
       cc->SetByproducts(byproducts);
       cc->SetCommandLines(verifyCommandLines);
       cc->SetComment("Checking File Globs");
-      cc->SetCMP0116Status(cmPolicies::NEW);
       cc->SetStdPipesUTF8(stdPipesUTF8);
       lg.AddCustomCommandToTarget(CMAKE_CHECK_BUILD_SYSTEM_TARGET,
                                   cmCustomCommandType::PRE_BUILD,
@@ -237,11 +346,6 @@
       listFiles.push_back(cm->GetGlobVerifyStamp());
     }
 
-    // Sort the list of input files and remove duplicates.
-    std::sort(listFiles.begin(), listFiles.end(), std::less<std::string>());
-    auto new_end = std::unique(listFiles.begin(), listFiles.end());
-    listFiles.erase(new_end, listFiles.end());
-
     // Create a rule to re-run CMake.
     std::string argS = cmStrCat("-S", lg.GetSourceDirectory());
     std::string argB = cmStrCat("-B", lg.GetBinaryDirectory());
@@ -260,7 +364,6 @@
     cc->SetDepends(listFiles);
     cc->SetCommandLines(commandLines);
     cc->SetComment("Checking Build System");
-    cc->SetCMP0116Status(cmPolicies::NEW);
     cc->SetEscapeOldStyle(false);
     cc->SetStdPipesUTF8(stdPipesUTF8);
     if (cmSourceFile* file =
@@ -309,12 +412,12 @@
 {
   std::string guid = this->GetGUID(name);
   for (std::string const& i : configs) {
-    std::vector<std::string> mapConfig;
+    cmList mapConfig;
     const char* dstConfig = i.c_str();
     if (target.GetProperty("EXTERNAL_MSPROJECT")) {
       if (cmValue m = target.GetProperty("MAP_IMPORTED_CONFIG_" +
                                          cmSystemTools::UpperCase(i))) {
-        cmExpandList(*m, mapConfig);
+        mapConfig.assign(*m);
         if (!mapConfig.empty()) {
           dstConfig = mapConfig[0].c_str();
         }
diff --git a/Source/cmGlobalVisualStudio8Generator.h b/Source/cmGlobalVisualStudio8Generator.h
index fe57c54..5555e9b 100644
--- a/Source/cmGlobalVisualStudio8Generator.h
+++ b/Source/cmGlobalVisualStudio8Generator.h
@@ -60,6 +60,11 @@
   cmGlobalVisualStudio8Generator(cmake* cm, const std::string& name,
                                  std::string const& platformInGeneratorName);
 
+  virtual bool InitializePlatform(cmMakefile* mf);
+
+  virtual bool ProcessGeneratorPlatformField(std::string const& key,
+                                             std::string const& value);
+
   void AddExtraIDETargets() override;
 
   std::string FindDevEnvCommand() override;
@@ -96,4 +101,7 @@
   cm::optional<std::string> DefaultTargetFrameworkVersion;
   cm::optional<std::string> DefaultTargetFrameworkIdentifier;
   cm::optional<std::string> DefaultTargetFrameworkTargetsVersion;
+
+private:
+  bool ParseGeneratorPlatform(std::string const& is, cmMakefile* mf);
 };
diff --git a/Source/cmGlobalVisualStudio9Generator.cxx b/Source/cmGlobalVisualStudio9Generator.cxx
index 9f6550b..e396405 100644
--- a/Source/cmGlobalVisualStudio9Generator.cxx
+++ b/Source/cmGlobalVisualStudio9Generator.cxx
@@ -64,7 +64,7 @@
   cmDocumentationEntry GetDocumentation() const override
   {
     return { std::string(vs9generatorName) + " [arch]",
-             "Generates Visual Studio 2008 project files.  "
+             "Deprecated.  Generates Visual Studio 2008 project files.  "
              "Optional [arch] can be \"Win64\" or \"IA64\"." };
   }
 
diff --git a/Source/cmGlobalVisualStudioGenerator.cxx b/Source/cmGlobalVisualStudioGenerator.cxx
index 31f6f77..702199d 100644
--- a/Source/cmGlobalVisualStudioGenerator.cxx
+++ b/Source/cmGlobalVisualStudioGenerator.cxx
@@ -201,7 +201,6 @@
       // Use no actual command lines so that the target itself is not
       // considered always out of date.
       auto cc = cm::make_unique<cmCustomCommand>();
-      cc->SetCMP0116Status(cmPolicies::NEW);
       cc->SetEscapeOldStyle(false);
       cc->SetComment("Build all projects");
       cmTarget* allBuild =
@@ -545,12 +544,12 @@
   cmSystemTools::ConvertToUnixSlashes(s1);
 
   std::string keyname;
-  HKEY hkey = NULL;
+  HKEY hkey = nullptr;
   LONG result = ERROR_SUCCESS;
   DWORD index = 0;
 
   keyname = regKeyBase + "\\OtherProjects7";
-  hkey = NULL;
+  hkey = nullptr;
   result =
     RegOpenKeyExW(HKEY_CURRENT_USER, cmsys::Encoding::ToWide(keyname).c_str(),
                   0, KEY_READ, &hkey);
@@ -568,7 +567,7 @@
            RegEnumKeyExW(hkey, index, subkeyname, &cch_subkeyname, 0, keyclass,
                          &cch_keyclass, &lastWriteTime)) {
       // Open the subkey and query the values of interest:
-      HKEY hsubkey = NULL;
+      HKEY hsubkey = nullptr;
       result = RegOpenKeyExW(hkey, subkeyname, 0, KEY_READ, &hsubkey);
       if (ERROR_SUCCESS == result) {
         DWORD valueType = REG_SZ;
@@ -642,7 +641,7 @@
   nextAvailableSubKeyName = std::to_string(index);
 
   keyname = regKeyBase + "\\RecordingProject7";
-  hkey = NULL;
+  hkey = nullptr;
   result =
     RegOpenKeyExW(HKEY_CURRENT_USER, cmsys::Encoding::ToWide(keyname).c_str(),
                   0, KEY_READ, &hkey);
@@ -688,13 +687,13 @@
                                     const std::string& regKeyBase)
 {
   std::string keyname = regKeyBase + "\\OtherProjects7";
-  HKEY hkey = NULL;
+  HKEY hkey = nullptr;
   LONG result =
     RegOpenKeyExW(HKEY_CURRENT_USER, cmsys::Encoding::ToWide(keyname).c_str(),
                   0, KEY_READ | KEY_WRITE, &hkey);
   if (ERROR_SUCCESS == result) {
     // Create the subkey and set the values of interest:
-    HKEY hsubkey = NULL;
+    HKEY hsubkey = nullptr;
     wchar_t lpClass[] = L"";
     result = RegCreateKeyExW(
       hkey, cmsys::Encoding::ToWide(nextAvailableSubKeyName).c_str(), 0,
@@ -961,13 +960,13 @@
 static bool OpenSolution(std::string const& sln)
 {
   HRESULT comInitialized =
-    CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
+    CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
   if (FAILED(comInitialized)) {
     return false;
   }
 
-  HINSTANCE hi =
-    ShellExecuteA(NULL, "open", sln.c_str(), NULL, NULL, SW_SHOWNORMAL);
+  HINSTANCE hi = ShellExecuteA(nullptr, "open", sln.c_str(), nullptr, nullptr,
+                               SW_SHOWNORMAL);
 
   CoUninitialize();
 
diff --git a/Source/cmGlobalVisualStudioVersionedGenerator.cxx b/Source/cmGlobalVisualStudioVersionedGenerator.cxx
index 415eb7c..602b42f 100644
--- a/Source/cmGlobalVisualStudioVersionedGenerator.cxx
+++ b/Source/cmGlobalVisualStudioVersionedGenerator.cxx
@@ -739,6 +739,22 @@
           cmSystemTools::VersionCompareGreaterEq(*vsVer, vsVer16_10_P2));
 }
 
+bool cmGlobalVisualStudioVersionedGenerator::IsScanDependenciesSupported()
+  const
+{
+  // Supported from Visual Studio 17.6 Preview 7.
+  if (this->Version > cmGlobalVisualStudioGenerator::VSVersion::VS17) {
+    return true;
+  }
+  if (this->Version < cmGlobalVisualStudioGenerator::VSVersion::VS17) {
+    return false;
+  }
+  static std::string const vsVer17_6_P7 = "17.6.33706.43";
+  cm::optional<std::string> vsVer = this->GetVSInstanceVersion();
+  return (vsVer &&
+          cmSystemTools::VersionCompareGreaterEq(*vsVer, vsVer17_6_P7));
+}
+
 const char*
 cmGlobalVisualStudioVersionedGenerator::GetAndroidApplicationTypeRevision()
   const
@@ -885,7 +901,8 @@
   return AuxToolset::PropsMissing;
 }
 
-bool cmGlobalVisualStudioVersionedGenerator::InitializeWindows(cmMakefile* mf)
+bool cmGlobalVisualStudioVersionedGenerator::InitializePlatformWindows(
+  cmMakefile* mf)
 {
   // If the Win 8.1 SDK is installed then we can select a SDK matching
   // the target Windows version.
@@ -894,13 +911,14 @@
     if (this->Version >= cmGlobalVisualStudioGenerator::VSVersion::VS16 &&
         !cmSystemTools::VersionCompareGreater(this->SystemVersion, "8.1")) {
       this->SetWindowsTargetPlatformVersion("8.1", mf);
-      return true;
+      return this->VerifyNoGeneratorPlatformVersion(
+        mf, "with the Windows 8.1 SDK installed");
     }
-    return cmGlobalVisualStudio14Generator::InitializeWindows(mf);
+    return cmGlobalVisualStudio14Generator::InitializePlatformWindows(mf);
   }
   // Otherwise we must choose a Win 10 SDK even if we are not targeting
   // Windows 10.
-  return this->SelectWindows10SDK(mf, false);
+  return this->SelectWindows10SDK(mf);
 }
 
 bool cmGlobalVisualStudioVersionedGenerator::SelectWindowsStoreToolset(
diff --git a/Source/cmGlobalVisualStudioVersionedGenerator.h b/Source/cmGlobalVisualStudioVersionedGenerator.h
index 45aca74..558ea7c 100644
--- a/Source/cmGlobalVisualStudioVersionedGenerator.h
+++ b/Source/cmGlobalVisualStudioVersionedGenerator.h
@@ -44,6 +44,8 @@
 
   bool IsUtf8EncodingSupported() const override;
 
+  bool IsScanDependenciesSupported() const override;
+
   const char* GetAndroidApplicationTypeRevision() const override;
 
   bool CheckCxxModuleSupport() override
@@ -61,7 +63,6 @@
     VSVersion version, cmake* cm, const std::string& name,
     std::string const& platformInGeneratorName);
 
-  bool InitializeWindows(cmMakefile* mf) override;
   bool SelectWindowsStoreToolset(std::string& toolset) const override;
 
   // Used to verify that the Desktop toolset for the current generator is
@@ -72,6 +73,8 @@
   // of the toolset is installed
   bool IsWindowsStoreToolsetInstalled() const;
 
+  bool InitializePlatformWindows(cmMakefile* mf) override;
+
   // Check for a Win 8 SDK known to the registry or VS installer tool.
   bool IsWin81SDKInstalled() const;
 
diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx
index 4746507..110933e 100644
--- a/Source/cmGlobalXCodeGenerator.cxx
+++ b/Source/cmGlobalXCodeGenerator.cxx
@@ -30,6 +30,7 @@
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGeneratorFactory.h"
 #include "cmLinkItem.h"
+#include "cmList.h"
 #include "cmListFileCache.h"
 #include "cmLocalGenerator.h"
 #include "cmLocalXCodeGenerator.h"
@@ -615,7 +616,6 @@
   auto cc = cm::make_unique<cmCustomCommand>();
   cc->SetCommandLines(
     cmMakeSingleCommandLine({ "echo", "Build all projects" }));
-  cc->SetCMP0116Status(cmPolicies::NEW);
   cmTarget* allbuild =
     root->AddUtilityCommand("ALL_BUILD", true, std::move(cc));
 
@@ -655,7 +655,6 @@
     cmSystemTools::ReplaceString(file, "\\ ", " ");
     cc = cm::make_unique<cmCustomCommand>();
     cc->SetCommandLines(cmMakeSingleCommandLine({ "make", "-f", file }));
-    cc->SetCMP0116Status(cmPolicies::NEW);
     cmTarget* check = root->AddUtilityCommand(CMAKE_CHECK_BUILD_SYSTEM_TARGET,
                                               true, std::move(cc));
 
@@ -687,7 +686,6 @@
         cc->SetCommandLines(legacyDependHelperCommandLines);
         cc->SetComment("Depend check for xcode");
         cc->SetWorkingDirectory(legacyDependHelperDir.c_str());
-        cc->SetCMP0116Status(cmPolicies::NEW);
         gen->AddCustomCommandToTarget(
           target->GetName(), cmCustomCommandType::POST_BUILD, std::move(cc),
           cmObjectLibraryCommands::Accept);
@@ -1030,7 +1028,7 @@
   cmValue extraFileAttributes = sf->GetProperty("XCODE_FILE_ATTRIBUTES");
   if (extraFileAttributes) {
     // Expand the list of attributes.
-    std::vector<std::string> attributes = cmExpandedList(*extraFileAttributes);
+    cmList attributes{ *extraFileAttributes };
 
     // Store the attributes.
     for (const auto& attribute : attributes) {
@@ -1174,7 +1172,7 @@
 std::string GetTargetObjectDirArch(T const& target,
                                    const std::string& defaultVal)
 {
-  auto archs = cmExpandedList(target.GetSafeProperty("OSX_ARCHITECTURES"));
+  cmList archs{ target.GetSafeProperty("OSX_ARCHITECTURES") };
   if (archs.size() > 1) {
     return "$(CURRENT_ARCH)";
   } else if (archs.size() == 1) {
@@ -1742,7 +1740,7 @@
     std::string str_so_file =
       cmStrCat("$<TARGET_SONAME_FILE:", gtgt->GetName(), '>');
     std::string str_link_file =
-      cmStrCat("$<TARGET_LINKER_FILE:", gtgt->GetName(), '>');
+      cmStrCat("$<TARGET_LINKER_LIBRARY_FILE:", gtgt->GetName(), '>');
     cmCustomCommandLines cmd = cmMakeSingleCommandLine(
       { cmSystemTools::GetCMakeCommand(), "-E", "cmake_symlink_library",
         str_file, str_so_file, str_link_file });
@@ -1757,6 +1755,27 @@
     postbuild.push_back(std::move(command));
   }
 
+  if (gtgt->HasImportLibrary("") && !gtgt->IsFrameworkOnApple()) {
+    // create symbolic links for .tbd file
+    std::string file = cmStrCat("$<TARGET_IMPORT_FILE:", gtgt->GetName(), '>');
+    std::string soFile =
+      cmStrCat("$<TARGET_SONAME_IMPORT_FILE:", gtgt->GetName(), '>');
+    std::string linkFile =
+      cmStrCat("$<TARGET_LINKER_IMPORT_FILE:", gtgt->GetName(), '>');
+    cmCustomCommandLines symlink_command = cmMakeSingleCommandLine(
+      { cmSystemTools::GetCMakeCommand(), "-E", "cmake_symlink_library", file,
+        soFile, linkFile });
+
+    cmCustomCommand command;
+    command.SetCommandLines(symlink_command);
+    command.SetComment("Creating import symlinks");
+    command.SetWorkingDirectory("");
+    command.SetBacktrace(this->CurrentMakefile->GetBacktrace());
+    command.SetStdPipesUTF8(true);
+
+    postbuild.push_back(std::move(command));
+  }
+
   cmXCodeObject* legacyCustomCommandsBuildPhase = nullptr;
   cmXCodeObject* preBuildPhase = nullptr;
   cmXCodeObject* preLinkPhase = nullptr;
@@ -2495,8 +2514,8 @@
   }
 
   // Set target-specific architectures.
-  std::vector<std::string> archs;
-  gtgt->GetAppleArchs(configName, archs);
+  std::vector<std::string> archs =
+    gtgt->GetAppleArchs(configName, cm::nullopt);
 
   if (!archs.empty()) {
     // Enable ARCHS attribute.
@@ -2685,6 +2704,12 @@
 
       buildSettings->AddAttribute("LIBRARY_STYLE",
                                   this->CreateString("DYNAMIC"));
+
+      if (gtgt->HasImportLibrary(configName)) {
+        // Request .tbd file generation
+        buildSettings->AddAttribute("GENERATE_TEXT_BASED_STUBS",
+                                    this->CreateString("YES"));
+      }
       break;
     }
     case cmStateEnums::EXECUTABLE: {
@@ -2740,7 +2765,7 @@
       if (emitted.insert(frameworkDir).second) {
         std::string incpath = this->XCodeEscapePath(frameworkDir);
         if (emitSystemIncludes &&
-            gtgt->IsSystemIncludeDirectory(include, configName,
+            gtgt->IsSystemIncludeDirectory(frameworkDir, configName,
                                            langForIncludes)) {
           sysfdirs.Add(incpath);
         } else {
@@ -3103,8 +3128,8 @@
 std::string cmGlobalXCodeGenerator::AddConfigurations(cmXCodeObject* target,
                                                       cmGeneratorTarget* gtgt)
 {
-  std::vector<std::string> const configVector = cmExpandedList(
-    this->CurrentMakefile->GetRequiredDefinition("CMAKE_CONFIGURATION_TYPES"));
+  cmList const configList{ this->CurrentMakefile->GetRequiredDefinition(
+    "CMAKE_CONFIGURATION_TYPES") };
   cmXCodeObject* configlist =
     this->CreateObject(cmXCodeObject::XCConfigurationList);
   cmXCodeObject* buildConfigurations =
@@ -3116,7 +3141,7 @@
   configlist->SetComment(comment);
   target->AddAttribute("buildConfigurationList",
                        this->CreateObjectReference(configlist));
-  for (auto const& i : configVector) {
+  for (auto const& i : configList) {
     cmXCodeObject* config =
       this->CreateObject(cmXCodeObject::XCBuildConfiguration);
     buildConfigurations->AddObject(config);
@@ -3129,12 +3154,12 @@
 
     this->CreateTargetXCConfigSettings(gtgt, config, i);
   }
-  if (!configVector.empty()) {
+  if (!configList.empty()) {
     configlist->AddAttribute("defaultConfigurationName",
-                             this->CreateString(configVector[0]));
+                             this->CreateString(configList[0]));
     configlist->AddAttribute("defaultConfigurationIsVisible",
                              this->CreateString("0"));
-    return configVector[0];
+    return configList[0];
   }
   return "";
 }
@@ -3885,8 +3910,6 @@
       // otherwise we end up hard-coding a path to the wrong SDK for
       // SDK-provided frameworks that are added by their full path.
       std::set<std::string> emitted(cli->GetFrameworkPathsEmitted());
-      const auto& fwPaths = cli->GetFrameworkPaths();
-      emitted.insert(fwPaths.begin(), fwPaths.end());
       BuildObjectListOrString libPaths(this, true);
       BuildObjectListOrString fwSearchPaths(this, true);
       for (auto const& libItem : configItemMap[configName]) {
@@ -4005,7 +4028,7 @@
                                     this->CreateString("0"));
   cmXCodeObject* buildFiles = this->CreateObject(cmXCodeObject::OBJECT_LIST);
   // Collect all embedded frameworks and dylibs and add them to build phase
-  std::vector<std::string> relFiles = cmExpandedList(*files);
+  cmList relFiles{ *files };
   for (std::string const& relFile : relFiles) {
     cmXCodeObject* buildFile{ nullptr };
     std::string filePath = relFile;
@@ -4575,13 +4598,12 @@
 void cmGlobalXCodeGenerator::ComputeArchitectures(cmMakefile* mf)
 {
   this->Architectures.clear();
-  cmValue sysroot = mf->GetDefinition("CMAKE_OSX_SYSROOT");
-  if (sysroot) {
-    mf->GetDefExpandList("CMAKE_OSX_ARCHITECTURES", this->Architectures);
-  }
+  cmList::append(this->Architectures,
+                 mf->GetDefinition("CMAKE_OSX_ARCHITECTURES"));
 
   if (this->Architectures.empty()) {
-    mf->GetDefExpandList("_CMAKE_APPLE_ARCHS_DEFAULT", this->Architectures);
+    cmList::append(this->Architectures,
+                   mf->GetDefinition("_CMAKE_APPLE_ARCHS_DEFAULT"));
   }
 
   if (this->Architectures.empty()) {
@@ -4984,7 +5006,7 @@
   }
 
   // Expand the list of definitions.
-  std::vector<std::string> defines = cmExpandedList(defines_list);
+  cmList defines{ defines_list };
 
   // Store the definitions in the string.
   this->AppendDefines(defs, defines, dflag);
diff --git a/Source/cmGraphVizWriter.cxx b/Source/cmGraphVizWriter.cxx
index 2bb438d..a8a7abb 100644
--- a/Source/cmGraphVizWriter.cxx
+++ b/Source/cmGraphVizWriter.cxx
@@ -15,6 +15,7 @@
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
 #include "cmLinkItem.h"
+#include "cmList.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
 #include "cmState.h"
@@ -255,9 +256,8 @@
 
   this->TargetsToIgnoreRegex.clear();
   if (!ignoreTargetsRegexes.empty()) {
-    std::vector<std::string> ignoreTargetsRegExVector =
-      cmExpandedList(ignoreTargetsRegexes);
-    for (std::string const& currentRegexString : ignoreTargetsRegExVector) {
+    cmList ignoreTargetsRegExList{ ignoreTargetsRegexes };
+    for (std::string const& currentRegexString : ignoreTargetsRegExList) {
       cmsys::RegularExpression currentRegex;
       if (!currentRegex.compile(currentRegexString)) {
         std::cerr << "Could not compile bad regex \"" << currentRegexString
diff --git a/Source/cmIDEOptions.cxx b/Source/cmIDEOptions.cxx
index 9468d4a..f94faf8 100644
--- a/Source/cmIDEOptions.cxx
+++ b/Source/cmIDEOptions.cxx
@@ -12,6 +12,7 @@
 #include "cmsys/String.h"
 
 #include "cmIDEFlagTable.h"
+#include "cmList.h"
 #include "cmStringAlgorithms.h"
 
 cmIDEOptions::cmIDEOptions()
diff --git a/Source/cmIncludeCommand.cxx b/Source/cmIncludeCommand.cxx
index 9242344..3695a9b 100644
--- a/Source/cmIncludeCommand.cxx
+++ b/Source/cmIncludeCommand.cxx
@@ -20,7 +20,12 @@
 {
   static std::map<std::string, cmPolicies::PolicyID> DeprecatedModules;
   if (DeprecatedModules.empty()) {
+    DeprecatedModules["Dart"] = cmPolicies::CMP0145;
     DeprecatedModules["Documentation"] = cmPolicies::CMP0106;
+    DeprecatedModules["FindCUDA"] = cmPolicies::CMP0146;
+    DeprecatedModules["FindDart"] = cmPolicies::CMP0145;
+    DeprecatedModules["FindPythonInterp"] = cmPolicies::CMP0148;
+    DeprecatedModules["FindPythonLibs"] = cmPolicies::CMP0148;
     DeprecatedModules["WriteCompilerDetectionHeader"] = cmPolicies::CMP0120;
   }
 
diff --git a/Source/cmInstallCommand.cxx b/Source/cmInstallCommand.cxx
index 82adca8..7f5f15b 100644
--- a/Source/cmInstallCommand.cxx
+++ b/Source/cmInstallCommand.cxx
@@ -39,6 +39,7 @@
 #include "cmInstallRuntimeDependencySetGenerator.h"
 #include "cmInstallScriptGenerator.h"
 #include "cmInstallTargetGenerator.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
 #include "cmPolicies.h"
@@ -359,13 +360,15 @@
     } else if (doing_script) {
       doing_script = false;
       std::string script = arg;
-      if (!cmSystemTools::FileIsFullPath(script)) {
-        script =
-          cmStrCat(helper.Makefile->GetCurrentSourceDirectory(), '/', arg);
-      }
-      if (cmSystemTools::FileIsDirectory(script)) {
-        status.SetError("given a directory as value of SCRIPT argument.");
-        return false;
+      if (!cmHasLiteralPrefix(script, "$<INSTALL_PREFIX>")) {
+        if (!cmSystemTools::FileIsFullPath(script)) {
+          script =
+            cmStrCat(helper.Makefile->GetCurrentSourceDirectory(), '/', arg);
+        }
+        if (cmSystemTools::FileIsDirectory(script)) {
+          status.SetError("given a directory as value of SCRIPT argument.");
+          return false;
+        }
       }
       helper.Makefile->AddInstallGenerator(
         cm::make_unique<cmInstallScriptGenerator>(
@@ -551,34 +554,35 @@
 
   // Enforce argument rules too complex to specify for the
   // general-purpose parser.
-  if (archiveArgs.GetNamelinkOnly() || runtimeArgs.GetNamelinkOnly() ||
-      objectArgs.GetNamelinkOnly() || frameworkArgs.GetNamelinkOnly() ||
-      bundleArgs.GetNamelinkOnly() || privateHeaderArgs.GetNamelinkOnly() ||
+  if (runtimeArgs.GetNamelinkOnly() || objectArgs.GetNamelinkOnly() ||
+      frameworkArgs.GetNamelinkOnly() || bundleArgs.GetNamelinkOnly() ||
+      privateHeaderArgs.GetNamelinkOnly() ||
       publicHeaderArgs.GetNamelinkOnly() || resourceArgs.GetNamelinkOnly() ||
       std::any_of(fileSetArgs.begin(), fileSetArgs.end(),
                   [](const cmInstallCommandFileSetArguments& fileSetArg)
                     -> bool { return fileSetArg.GetNamelinkOnly(); }) ||
       cxxModuleBmiArgs.GetNamelinkOnly()) {
     status.SetError(
-      "TARGETS given NAMELINK_ONLY option not in LIBRARY group.  "
-      "The NAMELINK_ONLY option may be specified only following LIBRARY.");
+      "TARGETS given NAMELINK_ONLY option not in LIBRARY or ARCHIVE group.  "
+      "The NAMELINK_ONLY option may be specified only following LIBRARY or "
+      "ARCHIVE.");
     return false;
   }
-  if (archiveArgs.GetNamelinkSkip() || runtimeArgs.GetNamelinkSkip() ||
-      objectArgs.GetNamelinkSkip() || frameworkArgs.GetNamelinkSkip() ||
-      bundleArgs.GetNamelinkSkip() || privateHeaderArgs.GetNamelinkSkip() ||
+  if (runtimeArgs.GetNamelinkSkip() || objectArgs.GetNamelinkSkip() ||
+      frameworkArgs.GetNamelinkSkip() || bundleArgs.GetNamelinkSkip() ||
+      privateHeaderArgs.GetNamelinkSkip() ||
       publicHeaderArgs.GetNamelinkSkip() || resourceArgs.GetNamelinkSkip() ||
       std::any_of(fileSetArgs.begin(), fileSetArgs.end(),
                   [](const cmInstallCommandFileSetArguments& fileSetArg)
                     -> bool { return fileSetArg.GetNamelinkSkip(); }) ||
       cxxModuleBmiArgs.GetNamelinkSkip()) {
     status.SetError(
-      "TARGETS given NAMELINK_SKIP option not in LIBRARY group.  "
-      "The NAMELINK_SKIP option may be specified only following LIBRARY.");
+      "TARGETS given NAMELINK_SKIP option not in LIBRARY or ARCHIVE group.  "
+      "The NAMELINK_SKIP option may be specified only following LIBRARY or "
+      "ARCHIVE.");
     return false;
   }
-  if (archiveArgs.HasNamelinkComponent() ||
-      runtimeArgs.HasNamelinkComponent() ||
+  if (runtimeArgs.HasNamelinkComponent() ||
       objectArgs.HasNamelinkComponent() ||
       frameworkArgs.HasNamelinkComponent() ||
       bundleArgs.HasNamelinkComponent() ||
@@ -590,9 +594,9 @@
                     -> bool { return fileSetArg.HasNamelinkComponent(); }) ||
       cxxModuleBmiArgs.HasNamelinkComponent()) {
     status.SetError(
-      "TARGETS given NAMELINK_COMPONENT option not in LIBRARY group.  "
-      "The NAMELINK_COMPONENT option may be specified only following "
-      "LIBRARY.");
+      "TARGETS given NAMELINK_COMPONENT option not in LIBRARY or ARCHIVE "
+      "group.  The NAMELINK_COMPONENT option may be specified only following "
+      "LIBRARY or ARCHIVE.");
     return false;
   }
   if (libraryArgs.GetNamelinkOnly() && libraryArgs.GetNamelinkSkip()) {
@@ -672,6 +676,14 @@
   } else if (libraryArgs.GetNamelinkSkip()) {
     namelinkMode = cmInstallTargetGenerator::NamelinkModeSkip;
   }
+  // Select the mode for installing symlinks to versioned imported libraries.
+  cmInstallTargetGenerator::NamelinkModeType importlinkMode =
+    cmInstallTargetGenerator::NamelinkModeNone;
+  if (archiveArgs.GetNamelinkOnly()) {
+    importlinkMode = cmInstallTargetGenerator::NamelinkModeOnly;
+  } else if (archiveArgs.GetNamelinkSkip()) {
+    importlinkMode = cmInstallTargetGenerator::NamelinkModeSkip;
+  }
 
   // Check if there is something to do.
   if (targetList.empty()) {
@@ -723,6 +735,7 @@
   bool installsArchive = false;
   bool installsLibrary = false;
   bool installsNamelink = false;
+  bool installsImportlink = false;
   bool installsRuntime = false;
   bool installsObject = false;
   bool installsFramework = false;
@@ -740,6 +753,7 @@
     std::unique_ptr<cmInstallTargetGenerator> archiveGenerator;
     std::unique_ptr<cmInstallTargetGenerator> libraryGenerator;
     std::unique_ptr<cmInstallTargetGenerator> namelinkGenerator;
+    std::unique_ptr<cmInstallTargetGenerator> importlinkGenerator;
     std::unique_ptr<cmInstallTargetGenerator> runtimeGenerator;
     std::unique_ptr<cmInstallTargetGenerator> objectGenerator;
     std::unique_ptr<cmInstallTargetGenerator> frameworkGenerator;
@@ -883,6 +897,32 @@
             }
             namelinkOnly =
               (namelinkMode == cmInstallTargetGenerator::NamelinkModeOnly);
+
+            if (target.GetMakefile()->PlatformSupportsAppleTextStubs() &&
+                target.IsSharedLibraryWithExports()) {
+              // Apple .tbd files use the ARCHIVE properties
+              if (!archiveArgs.GetDestination().empty()) {
+                artifactsSpecified = true;
+              }
+              if (importlinkMode !=
+                  cmInstallTargetGenerator::NamelinkModeOnly) {
+                archiveGenerator = CreateInstallTargetGenerator(
+                  target, archiveArgs, true, helper.Makefile->GetBacktrace(),
+                  helper.GetLibraryDestination(&archiveArgs));
+                archiveGenerator->SetImportlinkMode(
+                  cmInstallTargetGenerator::NamelinkModeSkip);
+              }
+              if (importlinkMode !=
+                  cmInstallTargetGenerator::NamelinkModeSkip) {
+                importlinkGenerator = CreateInstallTargetGenerator(
+                  target, archiveArgs, true, helper.Makefile->GetBacktrace(),
+                  helper.GetLibraryDestination(&archiveArgs), false, true);
+                importlinkGenerator->SetImportlinkMode(
+                  cmInstallTargetGenerator::NamelinkModeOnly);
+              }
+              namelinkOnly =
+                (importlinkMode == cmInstallTargetGenerator::NamelinkModeOnly);
+            }
           }
           if (runtimeDependencySet && libraryGenerator) {
             runtimeDependencySet->AddLibrary(libraryGenerator.get());
@@ -1040,7 +1080,7 @@
     if (createInstallGeneratorsForTargetFileSets && !namelinkOnly) {
       cmValue files = target.GetProperty("PRIVATE_HEADER");
       if (cmNonempty(files)) {
-        std::vector<std::string> relFiles = cmExpandedList(*files);
+        cmList relFiles{ *files };
         std::vector<std::string> absFiles;
         if (!helper.MakeFilesFullPath("PRIVATE_HEADER", relFiles, absFiles)) {
           return false;
@@ -1062,7 +1102,7 @@
 
       files = target.GetProperty("PUBLIC_HEADER");
       if (cmNonempty(files)) {
-        std::vector<std::string> relFiles = cmExpandedList(*files);
+        cmList relFiles{ *files };
         std::vector<std::string> absFiles;
         if (!helper.MakeFilesFullPath("PUBLIC_HEADER", relFiles, absFiles)) {
           return false;
@@ -1084,7 +1124,7 @@
 
       files = target.GetProperty("RESOURCE");
       if (cmNonempty(files)) {
-        std::vector<std::string> relFiles = cmExpandedList(*files);
+        cmList relFiles{ *files };
         std::vector<std::string> absFiles;
         if (!helper.MakeFilesFullPath("RESOURCE", relFiles, absFiles)) {
           return false;
@@ -1106,8 +1146,8 @@
     if (!namelinkOnly) {
       for (std::size_t i = 0; i < fileSetArgs.size(); i++) {
         if (auto* fileSet = target.GetFileSet(fileSetArgs[i].GetFileSet())) {
-          auto interfaceFileSetEntries = cmExpandedList(target.GetSafeProperty(
-            cmTarget::GetInterfaceFileSetsPropertyName(fileSet->GetType())));
+          cmList interfaceFileSetEntries{ target.GetSafeProperty(
+            cmTarget::GetInterfaceFileSetsPropertyName(fileSet->GetType())) };
           if (std::find(interfaceFileSetEntries.begin(),
                         interfaceFileSetEntries.end(),
                         fileSetArgs[i].GetFileSet()) !=
@@ -1155,6 +1195,7 @@
     installsArchive = installsArchive || archiveGenerator;
     installsLibrary = installsLibrary || libraryGenerator;
     installsNamelink = installsNamelink || namelinkGenerator;
+    installsImportlink = installsImportlink || importlinkGenerator;
     installsRuntime = installsRuntime || runtimeGenerator;
     installsObject = installsObject || objectGenerator;
     installsFramework = installsFramework || frameworkGenerator;
@@ -1167,6 +1208,7 @@
     helper.Makefile->AddInstallGenerator(std::move(archiveGenerator));
     helper.Makefile->AddInstallGenerator(std::move(libraryGenerator));
     helper.Makefile->AddInstallGenerator(std::move(namelinkGenerator));
+    helper.Makefile->AddInstallGenerator(std::move(importlinkGenerator));
     helper.Makefile->AddInstallGenerator(std::move(runtimeGenerator));
     helper.Makefile->AddInstallGenerator(std::move(objectGenerator));
     helper.Makefile->AddInstallGenerator(std::move(frameworkGenerator));
@@ -1201,6 +1243,10 @@
     helper.Makefile->GetGlobalGenerator()->AddInstallComponent(
       libraryArgs.GetNamelinkComponent());
   }
+  if (installsImportlink) {
+    helper.Makefile->GetGlobalGenerator()->AddInstallComponent(
+      archiveArgs.GetNamelinkComponent());
+  }
   if (installsRuntime) {
     helper.Makefile->GetGlobalGenerator()->AddInstallComponent(
       runtimeArgs.GetComponent());
diff --git a/Source/cmInstallDirectoryGenerator.cxx b/Source/cmInstallDirectoryGenerator.cxx
index d358763..6aa9910 100644
--- a/Source/cmInstallDirectoryGenerator.cxx
+++ b/Source/cmInstallDirectoryGenerator.cxx
@@ -6,6 +6,7 @@
 
 #include "cmGeneratorExpression.h"
 #include "cmInstallType.h"
+#include "cmList.h"
 #include "cmListFileCache.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
@@ -53,17 +54,16 @@
 std::vector<std::string> cmInstallDirectoryGenerator::GetDirectories(
   std::string const& config) const
 {
-  std::vector<std::string> directories;
+  cmList directories;
   if (this->ActionsPerConfig) {
     for (std::string const& f : this->Directories) {
-      cmExpandList(
-        cmGeneratorExpression::Evaluate(f, this->LocalGenerator, config),
-        directories);
+      directories.append(
+        cmGeneratorExpression::Evaluate(f, this->LocalGenerator, config));
     }
   } else {
     directories = this->Directories;
   }
-  return directories;
+  return std::move(directories.data());
 }
 
 void cmInstallDirectoryGenerator::GenerateScriptActions(std::ostream& os,
diff --git a/Source/cmInstallFilesGenerator.cxx b/Source/cmInstallFilesGenerator.cxx
index 18a852b..43dc656 100644
--- a/Source/cmInstallFilesGenerator.cxx
+++ b/Source/cmInstallFilesGenerator.cxx
@@ -6,8 +6,8 @@
 
 #include "cmGeneratorExpression.h"
 #include "cmInstallType.h"
+#include "cmList.h"
 #include "cmListFileCache.h"
-#include "cmStringAlgorithms.h"
 
 class cmLocalGenerator;
 
@@ -69,17 +69,15 @@
 std::vector<std::string> cmInstallFilesGenerator::GetFiles(
   std::string const& config) const
 {
-  std::vector<std::string> files;
   if (this->ActionsPerConfig) {
+    cmList files;
     for (std::string const& f : this->Files) {
-      cmExpandList(
-        cmGeneratorExpression::Evaluate(f, this->LocalGenerator, config),
-        files);
+      files.append(
+        cmGeneratorExpression::Evaluate(f, this->LocalGenerator, config));
     }
-  } else {
-    files = this->Files;
+    return std::move(files.data());
   }
-  return files;
+  return this->Files;
 }
 
 void cmInstallFilesGenerator::AddFilesInstallRule(
diff --git a/Source/cmInstallRuntimeDependencySetGenerator.cxx b/Source/cmInstallRuntimeDependencySetGenerator.cxx
index 44f03e1..1e2e663 100644
--- a/Source/cmInstallRuntimeDependencySetGenerator.cxx
+++ b/Source/cmInstallRuntimeDependencySetGenerator.cxx
@@ -256,8 +256,7 @@
   if (!strip.empty()) {
     os << indent << "if(CMAKE_INSTALL_DO_STRIP)\n"
        << indent.Next() << "execute_process(COMMAND \"" << strip << "\" ";
-    if (this->LocalGenerator->GetMakefile()->GetSafeDefinition(
-          "CMAKE_HOST_SYSTEM_NAME") == "Darwin") {
+    if (this->LocalGenerator->GetMakefile()->IsOn("APPLE")) {
       os << "-x ";
     }
     os << "\""
diff --git a/Source/cmInstallScriptGenerator.cxx b/Source/cmInstallScriptGenerator.cxx
index a5625fe..af531f2 100644
--- a/Source/cmInstallScriptGenerator.cxx
+++ b/Source/cmInstallScriptGenerator.cxx
@@ -56,12 +56,12 @@
 std::string cmInstallScriptGenerator::GetScript(
   std::string const& config) const
 {
-  std::string script;
+  std::string script = this->Script;
   if (this->AllowGenex && this->ActionsPerConfig) {
-    script = cmGeneratorExpression::Evaluate(this->Script,
-                                             this->LocalGenerator, config);
-  } else {
-    script = this->Script;
+    cmGeneratorExpression::ReplaceInstallPrefix(script,
+                                                "${CMAKE_INSTALL_PREFIX}");
+    script =
+      cmGeneratorExpression::Evaluate(script, this->LocalGenerator, config);
   }
   return script;
 }
diff --git a/Source/cmInstallTargetGenerator.cxx b/Source/cmInstallTargetGenerator.cxx
index 16c5002..3ac100d 100644
--- a/Source/cmInstallTargetGenerator.cxx
+++ b/Source/cmInstallTargetGenerator.cxx
@@ -4,12 +4,15 @@
 
 #include <algorithm>
 #include <cassert>
+#include <functional>
 #include <map>
 #include <set>
 #include <sstream>
 #include <utility>
 #include <vector>
 
+#include <cm/optional>
+
 #include "cmComputeLinkInformation.h"
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorTarget.h"
@@ -40,6 +43,84 @@
   objectDir += gt->GetName();
   return objectDir;
 }
+
+void computeFilesToInstall(
+  cmInstallTargetGenerator::Files& files,
+  cmInstallTargetGenerator::NamelinkModeType namelinkMode,
+  std::string const& fromDirConfig, std::string const& output,
+  std::string const& library, std::string const& real,
+  cm::optional<std::function<void(std::string const&)>> GNUToMS = cm::nullopt)
+{
+  bool haveNamelink = false;
+  auto convert = [&GNUToMS](std::string const& file) {
+    if (GNUToMS) {
+      (*GNUToMS)(file);
+    }
+  };
+
+  // Library link name.
+  std::string fromName = cmStrCat(fromDirConfig, output);
+  std::string toName = output;
+
+  // Library interface name.
+  std::string fromSOName;
+  std::string toSOName;
+  if (library != output) {
+    haveNamelink = true;
+    fromSOName = cmStrCat(fromDirConfig, library);
+    toSOName = library;
+  }
+
+  // Library implementation name.
+  std::string fromRealName;
+  std::string toRealName;
+  if (real != output && real != library) {
+    haveNamelink = true;
+    fromRealName = cmStrCat(fromDirConfig, real);
+    toRealName = real;
+  }
+
+  // Add the names based on the current namelink mode.
+  if (haveNamelink) {
+    files.NamelinkMode = namelinkMode;
+    // With a namelink we need to check the mode.
+    if (namelinkMode == cmInstallTargetGenerator::NamelinkModeOnly) {
+      // Install the namelink only.
+      files.From.emplace_back(fromName);
+      files.To.emplace_back(toName);
+      convert(output);
+    } else {
+      // Install the real file if it has its own name.
+      if (!fromRealName.empty()) {
+        files.From.emplace_back(fromRealName);
+        files.To.emplace_back(toRealName);
+        convert(real);
+      }
+
+      // Install the soname link if it has its own name.
+      if (!fromSOName.empty()) {
+        files.From.emplace_back(fromSOName);
+        files.To.emplace_back(toSOName);
+        convert(library);
+      }
+
+      // Install the namelink if it is not to be skipped.
+      if (namelinkMode != cmInstallTargetGenerator::NamelinkModeSkip) {
+        files.From.emplace_back(fromName);
+        files.To.emplace_back(toName);
+        convert(output);
+      }
+    }
+  } else {
+    // Without a namelink there will be only one file.  Install it
+    // if this is not a namelink-only rule.
+    if (namelinkMode != cmInstallTargetGenerator::NamelinkModeOnly) {
+      files.From.emplace_back(fromName);
+      files.To.emplace_back(toName);
+      convert(output);
+    }
+  }
+}
 }
 
 cmInstallTargetGenerator::cmInstallTargetGenerator(
@@ -56,6 +137,7 @@
 {
   this->ActionsPerConfig = true;
   this->NamelinkMode = NamelinkModeNone;
+  this->ImportlinkMode = NamelinkModeNone;
 }
 
 cmInstallTargetGenerator::~cmInstallTargetGenerator() = default;
@@ -247,18 +329,21 @@
       this->Target->GetLibraryNames(config);
     if (this->ImportLibrary) {
       // There is a bug in cmInstallCommand if this fails.
-      assert(this->NamelinkMode == NamelinkModeNone);
+      assert(this->Target->Makefile->PlatformSupportsAppleTextStubs() ||
+             this->ImportlinkMode == NamelinkModeNone);
 
-      std::string from1 = fromDirConfig + targetNames.ImportLibrary;
-      std::string to1 = targetNames.ImportLibrary;
-      files.From.emplace_back(std::move(from1));
-      files.To.emplace_back(std::move(to1));
-      std::string targetNameImportLib;
-      if (this->Target->GetImplibGNUtoMS(config, targetNames.ImportLibrary,
-                                         targetNameImportLib)) {
-        files.From.emplace_back(fromDirConfig + targetNameImportLib);
-        files.To.emplace_back(targetNameImportLib);
-      }
+      auto GNUToMS = [this, &config, &files,
+                      &fromDirConfig](const std::string& lib) {
+        std::string importLib;
+        if (this->Target->GetImplibGNUtoMS(config, lib, importLib)) {
+          files.From.emplace_back(fromDirConfig + importLib);
+          files.To.emplace_back(importLib);
+        }
+      };
+
+      computeFilesToInstall(
+        files, this->ImportlinkMode, fromDirConfig, targetNames.ImportOutput,
+        targetNames.ImportLibrary, targetNames.ImportReal, GNUToMS);
 
       // An import library looks like a static library.
       files.Type = cmInstallType_STATIC_LIBRARY;
@@ -318,66 +403,9 @@
       files.From.emplace_back(std::move(from1));
       files.To.emplace_back(std::move(to1));
     } else {
-      bool haveNamelink = false;
-
-      // Library link name.
-      std::string fromName = fromDirConfig + targetNames.Output;
-      std::string toName = targetNames.Output;
-
-      // Library interface name.
-      std::string fromSOName;
-      std::string toSOName;
-      if (targetNames.SharedObject != targetNames.Output) {
-        haveNamelink = true;
-        fromSOName = fromDirConfig + targetNames.SharedObject;
-        toSOName = targetNames.SharedObject;
-      }
-
-      // Library implementation name.
-      std::string fromRealName;
-      std::string toRealName;
-      if (targetNames.Real != targetNames.Output &&
-          targetNames.Real != targetNames.SharedObject) {
-        haveNamelink = true;
-        fromRealName = fromDirConfig + targetNames.Real;
-        toRealName = targetNames.Real;
-      }
-
-      // Add the names based on the current namelink mode.
-      if (haveNamelink) {
-        files.NamelinkMode = this->NamelinkMode;
-        // With a namelink we need to check the mode.
-        if (this->NamelinkMode == NamelinkModeOnly) {
-          // Install the namelink only.
-          files.From.emplace_back(fromName);
-          files.To.emplace_back(toName);
-        } else {
-          // Install the real file if it has its own name.
-          if (!fromRealName.empty()) {
-            files.From.emplace_back(fromRealName);
-            files.To.emplace_back(toRealName);
-          }
-
-          // Install the soname link if it has its own name.
-          if (!fromSOName.empty()) {
-            files.From.emplace_back(fromSOName);
-            files.To.emplace_back(toSOName);
-          }
-
-          // Install the namelink if it is not to be skipped.
-          if (this->NamelinkMode != NamelinkModeSkip) {
-            files.From.emplace_back(fromName);
-            files.To.emplace_back(toName);
-          }
-        }
-      } else {
-        // Without a namelink there will be only one file.  Install it
-        // if this is not a namelink-only rule.
-        if (this->NamelinkMode != NamelinkModeOnly) {
-          files.From.emplace_back(fromName);
-          files.To.emplace_back(toName);
-        }
-      }
+      computeFilesToInstall(files, this->NamelinkMode, fromDirConfig,
+                            targetNames.Output, targetNames.SharedObject,
+                            targetNames.Real);
     }
   }
 
@@ -425,6 +453,12 @@
                                     "${CMAKE_IMPORT_LIBRARY_SUFFIX}")) {
         fname = targetNames.ImportLibrary;
       }
+    } else if (nameType == NameImplibReal) {
+      // Use the import library name.
+      if (!target->GetImplibGNUtoMS(config, targetNames.ImportReal, fname,
+                                    "${CMAKE_IMPORT_LIBRARY_SUFFIX}")) {
+        fname = targetNames.ImportReal;
+      }
     } else if (nameType == NameReal) {
       // Use the canonical name.
       fname = targetNames.Real;
@@ -434,11 +468,14 @@
     }
   } else {
     cmGeneratorTarget::Names targetNames = target->GetLibraryNames(config);
-    if (nameType == NameImplib) {
+    if (nameType == NameImplib || nameType == NameImplibReal) {
+      const auto& importName = nameType == NameImplib
+        ? targetNames.ImportLibrary
+        : targetNames.ImportReal;
       // Use the import library name.
-      if (!target->GetImplibGNUtoMS(config, targetNames.ImportLibrary, fname,
+      if (!target->GetImplibGNUtoMS(config, importName, fname,
                                     "${CMAKE_IMPORT_LIBRARY_SUFFIX}")) {
-        fname = targetNames.ImportLibrary;
+        fname = importName;
       }
     } else if (nameType == NameSO) {
       // Use the soname.
@@ -492,7 +529,7 @@
   std::ostream& os, Indent indent, const std::string& config,
   std::string const& toDestDirPath)
 {
-  if (this->ImportLibrary ||
+  if (this->ImportLibrary || this->NamelinkMode == NamelinkModeOnly ||
       !(this->Target->GetType() == cmStateEnums::SHARED_LIBRARY ||
         this->Target->GetType() == cmStateEnums::MODULE_LIBRARY ||
         this->Target->GetType() == cmStateEnums::EXECUTABLE)) {
@@ -589,7 +626,8 @@
   std::string const& toDestDirPath)
 {
   // Skip the chrpath if the target does not need it.
-  if (this->ImportLibrary || !this->Target->IsChrpathUsed(config)) {
+  if (this->ImportLibrary || this->NamelinkMode == NamelinkModeOnly ||
+      !this->Target->IsChrpathUsed(config)) {
     return;
   }
   // Skip if on Apple
@@ -640,7 +678,8 @@
   std::string const& toDestDirPath)
 {
   // Skip the chrpath if the target does not need it.
-  if (this->ImportLibrary || !this->Target->IsChrpathUsed(config)) {
+  if (this->ImportLibrary || this->NamelinkMode == NamelinkModeOnly ||
+      !this->Target->IsChrpathUsed(config)) {
     return;
   }
 
@@ -779,36 +818,39 @@
   // don't strip static and import libraries, because it removes the only
   // symbol table they have so you can't link to them anymore
   if (this->Target->GetType() == cmStateEnums::STATIC_LIBRARY ||
-      this->ImportLibrary) {
+      this->ImportLibrary || this->NamelinkMode == NamelinkModeOnly) {
     return;
   }
 
   // Don't handle OSX Bundles.
-  if (this->Target->Target->GetMakefile()->IsOn("APPLE") &&
+  if (this->Target->IsApple() &&
       this->Target->GetPropertyAsBool("MACOSX_BUNDLE")) {
     return;
   }
 
-  if (!this->Target->Target->GetMakefile()->IsSet("CMAKE_STRIP")) {
+  std::string const& strip =
+    this->Target->Target->GetMakefile()->GetSafeDefinition("CMAKE_STRIP");
+  if (strip.empty()) {
     return;
   }
 
   std::string stripArgs;
-
-  // macOS 'strip' is picky, executables need '-u -r' and dylibs need '-x'.
-  if (this->Target->Target->GetMakefile()->IsOn("APPLE")) {
+  if (this->Target->IsApple()) {
     if (this->Target->GetType() == cmStateEnums::SHARED_LIBRARY ||
         this->Target->GetType() == cmStateEnums::MODULE_LIBRARY) {
+      // Strip tools need '-x' to strip Apple dylibs correctly.
       stripArgs = "-x ";
-    } else if (this->Target->GetType() == cmStateEnums::EXECUTABLE) {
+    } else if (this->Target->GetType() == cmStateEnums::EXECUTABLE &&
+               this->Target->GetGlobalGenerator()->GetStripCommandStyle(
+                 strip) == cmGlobalGenerator::StripCommandStyle::Apple) {
+      // Apple's strip tool needs '-u -r' to strip executables correctly.
       stripArgs = "-u -r ";
     }
   }
 
   os << indent << "if(CMAKE_INSTALL_DO_STRIP)\n";
-  os << indent << "  execute_process(COMMAND \""
-     << this->Target->Target->GetMakefile()->GetSafeDefinition("CMAKE_STRIP")
-     << "\" " << stripArgs << "\"" << toDestDirPath << "\")\n";
+  os << indent << "  execute_process(COMMAND \"" << strip << "\" " << stripArgs
+     << "\"" << toDestDirPath << "\")\n";
   os << indent << "endif()\n";
 }
 
@@ -822,7 +864,7 @@
 
   // Perform post-installation processing on the file depending
   // on its type.
-  if (!this->Target->Target->GetMakefile()->IsOn("APPLE")) {
+  if (!this->Target->IsApple()) {
     return;
   }
 
diff --git a/Source/cmInstallTargetGenerator.h b/Source/cmInstallTargetGenerator.h
index 3fc4b59..2f41163 100644
--- a/Source/cmInstallTargetGenerator.h
+++ b/Source/cmInstallTargetGenerator.h
@@ -39,6 +39,10 @@
     NamelinkModeSkip
   };
   void SetNamelinkMode(NamelinkModeType mode) { this->NamelinkMode = mode; }
+  void SetImportlinkMode(NamelinkModeType mode)
+  {
+    this->ImportlinkMode = mode;
+  }
 
   std::string GetInstallFilename(const std::string& config) const;
 
@@ -50,7 +54,8 @@
     NameNormal,
     NameImplib,
     NameSO,
-    NameReal
+    NameReal,
+    NameImplibReal
   };
 
   static std::string GetInstallFilename(const cmGeneratorTarget* target,
@@ -121,6 +126,7 @@
   cmGeneratorTarget* Target = nullptr;
   std::string const FilePermissions;
   NamelinkModeType NamelinkMode;
+  NamelinkModeType ImportlinkMode;
   bool const ImportLibrary;
   bool const Optional;
 };
diff --git a/Source/cmInstalledFile.cxx b/Source/cmInstalledFile.cxx
index 5bf8320..381c91b 100644
--- a/Source/cmInstalledFile.cxx
+++ b/Source/cmInstalledFile.cxx
@@ -5,9 +5,9 @@
 #include <utility>
 
 #include "cmGeneratorExpression.h"
+#include "cmList.h"
 #include "cmListFileCache.h"
 #include "cmMakefile.h"
-#include "cmStringAlgorithms.h"
 #include "cmValue.h"
 
 cmInstalledFile::cmInstalledFile() = default;
@@ -97,12 +97,11 @@
   return isSet && cmIsOn(value);
 }
 
-void cmInstalledFile::GetPropertyAsList(const std::string& prop,
-                                        std::vector<std::string>& list) const
+std::vector<std::string> cmInstalledFile::GetPropertyAsList(
+  const std::string& prop) const
 {
   std::string value;
   this->GetProperty(prop, value);
 
-  list.clear();
-  cmExpandList(value, list);
+  return std::move(cmList(value).data());
 }
diff --git a/Source/cmInstalledFile.h b/Source/cmInstalledFile.h
index 82474f5..373c349 100644
--- a/Source/cmInstalledFile.h
+++ b/Source/cmInstalledFile.h
@@ -59,8 +59,7 @@
 
   bool GetPropertyAsBool(const std::string& prop) const;
 
-  void GetPropertyAsList(const std::string& prop,
-                         std::vector<std::string>& list) const;
+  std::vector<std::string> GetPropertyAsList(const std::string& prop) const;
 
   void SetName(cmMakefile* mf, const std::string& name);
 
diff --git a/Source/cmJSONHelpers.h b/Source/cmJSONHelpers.h
index f7151b5..94641de 100644
--- a/Source/cmJSONHelpers.h
+++ b/Source/cmJSONHelpers.h
@@ -2,9 +2,12 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #pragma once
 
+#include "cmConfigure.h" // IWYU pragma: keep
+
 #include <algorithm>
 #include <cstddef>
 #include <functional>
+#include <iostream>
 #include <map>
 #include <string>
 #include <vector>
@@ -14,20 +17,129 @@
 
 #include <cm3p/json/value.h>
 
-template <typename T, typename E, typename... CallState>
-using cmJSONHelper =
-  std::function<E(T& out, const Json::Value* value, CallState&&... state)>;
+#include "cmJSONState.h"
 
-template <typename E, typename... CallState>
+template <typename T>
+using cmJSONHelper =
+  std::function<bool(T& out, const Json::Value* value, cmJSONState* state)>;
+
+using ErrorGenerator = std::function<void(const Json::Value*, cmJSONState*)>;
+
+namespace JsonErrors {
+enum ObjectError
+{
+  RequiredMissing,
+  InvalidObject,
+  ExtraField,
+  MissingRequired
+};
+using ErrorGenerator = std::function<void(const Json::Value*, cmJSONState*)>;
+using ObjectErrorGenerator =
+  std::function<ErrorGenerator(ObjectError, const Json::Value::Members&)>;
+const auto EXPECTED_TYPE = [](const std::string& type) {
+  return [type](const Json::Value* value, cmJSONState* state) -> void {
+#if !defined(CMAKE_BOOTSTRAP)
+    if (state->key().empty()) {
+      state->AddErrorAtValue(cmStrCat("Expected ", type), value);
+      return;
+    }
+    std::string errMsg = cmStrCat("\"", state->key(), "\" expected ", type);
+    if (value && value->isConvertibleTo(Json::ValueType::stringValue)) {
+      errMsg = cmStrCat(errMsg, ", got: ", value->asString());
+    }
+    state->AddErrorAtValue(errMsg, value);
+#endif
+  };
+};
+const auto INVALID_STRING = [](const Json::Value* value,
+                               cmJSONState* state) -> void {
+  JsonErrors::EXPECTED_TYPE("a string")(value, state);
+};
+const auto INVALID_BOOL = [](const Json::Value* value,
+                             cmJSONState* state) -> void {
+  JsonErrors::EXPECTED_TYPE("a bool")(value, state);
+};
+const auto INVALID_INT = [](const Json::Value* value,
+                            cmJSONState* state) -> void {
+  JsonErrors::EXPECTED_TYPE("an integer")(value, state);
+};
+const auto INVALID_UINT = [](const Json::Value* value,
+                             cmJSONState* state) -> void {
+  JsonErrors::EXPECTED_TYPE("an unsigned integer")(value, state);
+};
+const auto INVALID_NAMED_OBJECT =
+  [](const std::function<std::string(const Json::Value*, cmJSONState*)>&
+       nameGenerator) -> ObjectErrorGenerator {
+  return [nameGenerator](
+           ObjectError errorType,
+           const Json::Value::Members& extraFields) -> ErrorGenerator {
+    return [nameGenerator, errorType, extraFields](
+             const Json::Value* value, cmJSONState* state) -> void {
+#if !defined(CMAKE_BOOTSTRAP)
+      std::string name = nameGenerator(value, state);
+      switch (errorType) {
+        case ObjectError::RequiredMissing:
+          state->AddErrorAtValue(cmStrCat("Invalid Required ", name), value);
+          break;
+        case ObjectError::InvalidObject:
+          state->AddErrorAtValue(cmStrCat("Invalid ", name), value);
+          break;
+        case ObjectError::ExtraField: {
+          for (auto const& member : extraFields) {
+            if (value) {
+              state->AddErrorAtValue(
+                cmStrCat("Invalid extra field \"", member, "\" in ", name),
+                &(*value)[member]);
+            } else {
+              state->AddError(
+                cmStrCat("Invalid extra field \"", member, "\" in ", name));
+            }
+          }
+        } break;
+        case ObjectError::MissingRequired:
+          state->AddErrorAtValue(cmStrCat("Missing required field \"",
+                                          state->key(), "\" in ", name),
+                                 value);
+          break;
+      }
+#endif
+    };
+  };
+};
+const auto INVALID_OBJECT =
+  [](ObjectError errorType,
+     const Json::Value::Members& extraFields) -> ErrorGenerator {
+  return INVALID_NAMED_OBJECT(
+    [](const Json::Value*, cmJSONState*) -> std::string { return "Object"; })(
+    errorType, extraFields);
+};
+const auto INVALID_NAMED_OBJECT_KEY =
+  [](ObjectError errorType,
+     const Json::Value::Members& extraFields) -> ErrorGenerator {
+  return INVALID_NAMED_OBJECT(
+    [](const Json::Value*, cmJSONState* state) -> std::string {
+      for (auto it = state->parseStack.rbegin();
+           it != state->parseStack.rend(); ++it) {
+        if (it->first.rfind("$vector_item_", 0) == 0) {
+          continue;
+        }
+        return cmStrCat("\"", it->first, "\"");
+      }
+      return "root";
+    })(errorType, extraFields);
+};
+}
+
 struct cmJSONHelperBuilder
 {
+
   template <typename T>
   class Object
   {
   public:
-    Object(E&& success, E&& fail, bool allowExtra = true)
-      : Success(std::move(success))
-      , Fail(std::move(fail))
+    Object(JsonErrors::ObjectErrorGenerator error = JsonErrors::INVALID_OBJECT,
+           bool allowExtra = true)
+      : Error(std::move(error))
       , AllowExtra(allowExtra)
     {
     }
@@ -38,8 +150,8 @@
     {
       return this->BindPrivate(
         name,
-        [func, member](T& out, const Json::Value* value, CallState&&... state)
-          -> E { return func(out.*member, value, std::forward(state)...); },
+        [func, member](T& out, const Json::Value* value, cmJSONState* state)
+          -> bool { return func(out.*member, value, state); },
         required);
     }
     template <typename M, typename F>
@@ -49,9 +161,9 @@
       return this->BindPrivate(
         name,
         [func](T& /*out*/, const Json::Value* value,
-               CallState&&... state) -> E {
+               cmJSONState* state) -> bool {
           M dummy;
-          return func(dummy, value, std::forward(state)...);
+          return func(dummy, value, state);
         },
         required);
     }
@@ -61,46 +173,56 @@
       return this->BindPrivate(name, MemberFunction(func), required);
     }
 
-    E operator()(T& out, const Json::Value* value, CallState&&... state) const
+    bool operator()(T& out, const Json::Value* value, cmJSONState* state) const
     {
+      Json::Value::Members extraFields;
+      bool success = true;
       if (!value && this->AnyRequired) {
-        return this->Fail;
+        Error(JsonErrors::ObjectError::RequiredMissing, extraFields)(value,
+                                                                     state);
+        return false;
       }
       if (value && !value->isObject()) {
-        return this->Fail;
+        Error(JsonErrors::ObjectError::InvalidObject, extraFields)(value,
+                                                                   state);
+        return false;
       }
-      Json::Value::Members extraFields;
       if (value) {
         extraFields = value->getMemberNames();
       }
 
       for (auto const& m : this->Members) {
         std::string name(m.Name.data(), m.Name.size());
+        state->push_stack(name, value);
         if (value && value->isMember(name)) {
-          E result = m.Function(out, &(*value)[name], std::forward(state)...);
-          if (result != this->Success) {
-            return result;
+          if (!m.Function(out, &(*value)[name], state)) {
+            success = false;
           }
           extraFields.erase(
             std::find(extraFields.begin(), extraFields.end(), name));
         } else if (!m.Required) {
-          E result = m.Function(out, nullptr, std::forward(state)...);
-          if (result != this->Success) {
-            return result;
+          if (!m.Function(out, nullptr, state)) {
+            success = false;
           }
         } else {
-          return this->Fail;
+          Error(JsonErrors::ObjectError::MissingRequired, extraFields)(value,
+                                                                       state);
+          success = false;
         }
+        state->pop_stack();
       }
 
-      return this->AllowExtra || extraFields.empty() ? this->Success
-                                                     : this->Fail;
+      if (!this->AllowExtra && !extraFields.empty()) {
+        Error(JsonErrors::ObjectError::ExtraField, extraFields)(value, state);
+        success = false;
+      }
+      return success;
     }
 
   private:
     // Not a true cmJSONHelper, it just happens to match the signature
-    using MemberFunction =
-      std::function<E(T& out, const Json::Value* value, CallState&&... state)>;
+    using MemberFunction = std::function<bool(T& out, const Json::Value* value,
+                                              cmJSONState* state)>;
     struct Member
     {
       cm::string_view Name;
@@ -109,8 +231,7 @@
     };
     std::vector<Member> Members;
     bool AnyRequired = false;
-    E Success;
-    E Fail;
+    JsonErrors::ObjectErrorGenerator Error;
     bool AllowExtra;
 
     Object& BindPrivate(const cm::string_view& name, MemberFunction&& func,
@@ -127,175 +248,218 @@
       return *this;
     }
   };
-  static cmJSONHelper<std::string, E, CallState...> String(
-    E success, E fail, const std::string& defval = "")
+
+  static cmJSONHelper<std::string> String(
+    const JsonErrors::ErrorGenerator& error = JsonErrors::INVALID_STRING,
+    const std::string& defval = "")
   {
-    return [success, fail, defval](std::string& out, const Json::Value* value,
-                                   CallState&&... /*state*/) -> E {
+    return [error, defval](std::string& out, const Json::Value* value,
+                           cmJSONState* state) -> bool {
       if (!value) {
         out = defval;
-        return success;
+        return true;
       }
       if (!value->isString()) {
-        return fail;
+        error(value, state);
+        ;
+        return false;
       }
       out = value->asString();
-      return success;
+      return true;
     };
-  }
+  };
 
-  static cmJSONHelper<int, E, CallState...> Int(E success, E fail,
-                                                int defval = 0)
+  static cmJSONHelper<std::string> String(const std::string& defval)
   {
-    return [success, fail, defval](int& out, const Json::Value* value,
-                                   CallState&&... /*state*/) -> E {
+    return String(JsonErrors::INVALID_STRING, defval);
+  };
+
+  static cmJSONHelper<int> Int(
+    const JsonErrors::ErrorGenerator& error = JsonErrors::INVALID_INT,
+    int defval = 0)
+  {
+    return [error, defval](int& out, const Json::Value* value,
+                           cmJSONState* state) -> bool {
       if (!value) {
         out = defval;
-        return success;
+        return true;
       }
       if (!value->isInt()) {
-        return fail;
+        error(value, state);
+        ;
+        return false;
       }
       out = value->asInt();
-      return success;
+      return true;
     };
   }
 
-  static cmJSONHelper<unsigned int, E, CallState...> UInt(
-    E success, E fail, unsigned int defval = 0)
+  static cmJSONHelper<int> Int(int defval)
   {
-    return [success, fail, defval](unsigned int& out, const Json::Value* value,
-                                   CallState&&... /*state*/) -> E {
+    return Int(JsonErrors::INVALID_INT, defval);
+  };
+
+  static cmJSONHelper<unsigned int> UInt(
+    const JsonErrors::ErrorGenerator& error = JsonErrors::INVALID_UINT,
+    unsigned int defval = 0)
+  {
+    return [error, defval](unsigned int& out, const Json::Value* value,
+                           cmJSONState* state) -> bool {
       if (!value) {
         out = defval;
-        return success;
+        return true;
       }
       if (!value->isUInt()) {
-        return fail;
+        error(value, state);
+        ;
+        return false;
       }
       out = value->asUInt();
-      return success;
+      return true;
     };
   }
 
-  static cmJSONHelper<bool, E, CallState...> Bool(E success, E fail,
-                                                  bool defval = false)
+  static cmJSONHelper<unsigned int> UInt(unsigned int defval)
   {
-    return [success, fail, defval](bool& out, const Json::Value* value,
-                                   CallState&&... /*state*/) -> E {
+    return UInt(JsonErrors::INVALID_UINT, defval);
+  }
+
+  static cmJSONHelper<bool> Bool(
+    const JsonErrors::ErrorGenerator& error = JsonErrors::INVALID_BOOL,
+    bool defval = false)
+  {
+    return [error, defval](bool& out, const Json::Value* value,
+                           cmJSONState* state) -> bool {
       if (!value) {
         out = defval;
-        return success;
+        return true;
       }
       if (!value->isBool()) {
-        return fail;
+        error(value, state);
+        ;
+        return false;
       }
       out = value->asBool();
-      return success;
+      return true;
     };
   }
 
+  static cmJSONHelper<bool> Bool(bool defval)
+  {
+    return Bool(JsonErrors::INVALID_BOOL, defval);
+  }
+
   template <typename T, typename F, typename Filter>
-  static cmJSONHelper<std::vector<T>, E, CallState...> VectorFilter(
-    E success, E fail, F func, Filter filter)
+  static cmJSONHelper<std::vector<T>> VectorFilter(
+    const JsonErrors::ErrorGenerator& error, F func, Filter filter)
   {
-    return [success, fail, func, filter](std::vector<T>& out,
-                                         const Json::Value* value,
-                                         CallState&&... state) -> E {
+    return [error, func, filter](std::vector<T>& out, const Json::Value* value,
+                                 cmJSONState* state) -> bool {
+      bool success = true;
       if (!value) {
         out.clear();
-        return success;
+        return true;
       }
       if (!value->isArray()) {
-        return fail;
+        error(value, state);
+        return false;
       }
       out.clear();
+      int index = 0;
       for (auto const& item : *value) {
+        state->push_stack(cmStrCat("$vector_item_", index++), &item);
         T t;
-        E result = func(t, &item, std::forward(state)...);
-        if (result != success) {
-          return result;
+        if (!func(t, &item, state)) {
+          success = false;
         }
         if (!filter(t)) {
+          state->pop_stack();
           continue;
         }
         out.push_back(std::move(t));
+        state->pop_stack();
       }
       return success;
     };
   }
 
   template <typename T, typename F>
-  static cmJSONHelper<std::vector<T>, E, CallState...> Vector(E success,
-                                                              E fail, F func)
+  static cmJSONHelper<std::vector<T>> Vector(JsonErrors::ErrorGenerator error,
+                                             F func)
   {
-    return VectorFilter<T, F>(success, fail, func,
+    return VectorFilter<T, F>(std::move(error), func,
                               [](const T&) { return true; });
   }
 
   template <typename T, typename F, typename Filter>
-  static cmJSONHelper<std::map<std::string, T>, E, CallState...> MapFilter(
-    E success, E fail, F func, Filter filter)
+  static cmJSONHelper<std::map<std::string, T>> MapFilter(
+    const JsonErrors::ErrorGenerator& error, F func, Filter filter)
   {
-    return [success, fail, func, filter](std::map<std::string, T>& out,
-                                         const Json::Value* value,
-                                         CallState&&... state) -> E {
+    return [error, func, filter](std::map<std::string, T>& out,
+                                 const Json::Value* value,
+                                 cmJSONState* state) -> bool {
+      bool success = true;
       if (!value) {
         out.clear();
-        return success;
+        return true;
       }
       if (!value->isObject()) {
-        return fail;
+        error(value, state);
+        ;
+        return false;
       }
       out.clear();
       for (auto const& key : value->getMemberNames()) {
+        state->push_stack(cmStrCat(key, ""), &(*value)[key]);
         if (!filter(key)) {
+          state->pop_stack();
           continue;
         }
         T t;
-        E result = func(t, &(*value)[key], std::forward(state)...);
-        if (result != success) {
-          return result;
+        if (!func(t, &(*value)[key], state)) {
+          success = false;
         }
         out[key] = std::move(t);
+        state->pop_stack();
       }
       return success;
     };
   }
 
   template <typename T, typename F>
-  static cmJSONHelper<std::map<std::string, T>, E, CallState...> Map(E success,
-                                                                     E fail,
-                                                                     F func)
+  static cmJSONHelper<std::map<std::string, T>> Map(
+    const JsonErrors::ErrorGenerator& error, F func)
   {
-    return MapFilter<T, F>(success, fail, func,
+    return MapFilter<T, F>(error, func,
                            [](const std::string&) { return true; });
   }
 
   template <typename T, typename F>
-  static cmJSONHelper<cm::optional<T>, E, CallState...> Optional(E success,
-                                                                 F func)
+  static cmJSONHelper<cm::optional<T>> Optional(F func)
   {
-    return [success, func](cm::optional<T>& out, const Json::Value* value,
-                           CallState&&... state) -> E {
+    return [func](cm::optional<T>& out, const Json::Value* value,
+                  cmJSONState* state) -> bool {
       if (!value) {
         out.reset();
-        return success;
+        return true;
       }
       out.emplace();
-      return func(*out, value, std::forward(state)...);
+      return func(*out, value, state);
     };
   }
 
   template <typename T, typename F>
-  static cmJSONHelper<T, E, CallState...> Required(E fail, F func)
+  static cmJSONHelper<T> Required(const JsonErrors::ErrorGenerator& error,
+                                  F func)
   {
-    return [fail, func](T& out, const Json::Value* value,
-                        CallState&&... state) -> E {
+    return [error, func](T& out, const Json::Value* value,
+                         cmJSONState* state) -> bool {
       if (!value) {
-        return fail;
+        error(value, state);
+        ;
+        return false;
       }
-      return func(out, value, std::forward(state)...);
+      return func(out, value, state);
     };
   }
 };
diff --git a/Source/cmJSONState.cxx b/Source/cmJSONState.cxx
new file mode 100644
index 0000000..92bde77
--- /dev/null
+++ b/Source/cmJSONState.cxx
@@ -0,0 +1,163 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include "cmJSONState.h"
+
+#include <sstream>
+
+#include <cm/memory>
+
+#include <cm3p/json/reader.h>
+#include <cm3p/json/value.h>
+
+#include "cmsys/FStream.hxx"
+
+#include "cmStringAlgorithms.h"
+
+cmJSONState::cmJSONState(const std::string& filename, Json::Value* root)
+{
+  cmsys::ifstream fin(filename.c_str(), std::ios::in | std::ios::binary);
+  if (!fin) {
+    this->AddError(cmStrCat("File not found: ", filename));
+    return;
+  }
+  // If there's a BOM, toss it.
+  cmsys::FStream::ReadBOM(fin);
+
+  // Save the entire document.
+  std::streampos finBegin = fin.tellg();
+  this->doc = std::string(std::istreambuf_iterator<char>(fin),
+                          std::istreambuf_iterator<char>());
+  if (this->doc.empty()) {
+    this->AddError("A JSON document cannot be empty");
+    return;
+  }
+  fin.seekg(finBegin);
+
+  // Parse the document.
+  Json::CharReaderBuilder builder;
+  Json::CharReaderBuilder::strictMode(&builder.settings_);
+  std::string errMsg;
+  if (!Json::parseFromStream(builder, fin, root, &errMsg)) {
+    errMsg = cmStrCat("JSON Parse Error: ", filename, ":\n", errMsg);
+    this->AddError(errMsg);
+  }
+}
+
+void cmJSONState::AddError(std::string const& errMsg)
+{
+  this->errors.push_back(Error(errMsg));
+}
+
+void cmJSONState::AddErrorAtValue(std::string const& errMsg,
+                                  const Json::Value* value)
+{
+  if (value && !value->isNull()) {
+    this->AddErrorAtOffset(errMsg, value->getOffsetStart());
+  } else {
+    this->AddError(errMsg);
+  }
+}
+
+void cmJSONState::AddErrorAtOffset(std::string const& errMsg,
+                                   std::ptrdiff_t offset)
+{
+  if (doc.empty()) {
+    this->AddError(errMsg);
+  } else {
+    Location loc = LocateInDocument(offset);
+    this->errors.push_back(Error(loc, errMsg));
+  }
+}
+
+std::string cmJSONState::GetErrorMessage(bool showContext)
+{
+  std::string message;
+  for (auto const& error : this->errors) {
+    message = cmStrCat(message, error.GetErrorMessage(), "\n");
+    if (showContext) {
+      Location loc = error.GetLocation();
+      if (loc.column > 0) {
+        message = cmStrCat(message, GetJsonContext(loc), "\n");
+      }
+    }
+  }
+  message = cmStrCat("\n", message);
+  message.pop_back();
+  return message;
+}
+
+std::string cmJSONState::key()
+{
+  if (!this->parseStack.empty()) {
+    return this->parseStack.back().first;
+  }
+  return "";
+}
+
+std::string cmJSONState::key_after(std::string const& k)
+{
+  for (auto it = this->parseStack.begin(); it != this->parseStack.end();
+       ++it) {
+    if (it->first == k && (++it) != this->parseStack.end()) {
+      return it->first;
+    }
+  }
+  return "";
+}
+
+const Json::Value* cmJSONState::value_after(std::string const& k)
+{
+  for (auto it = this->parseStack.begin(); it != this->parseStack.end();
+       ++it) {
+    if (it->first == k && (++it) != this->parseStack.end()) {
+      return it->second;
+    }
+  }
+  return nullptr;
+}
+
+void cmJSONState::push_stack(std::string const& k, const Json::Value* value)
+{
+  this->parseStack.push_back(JsonPair(k, value));
+}
+
+void cmJSONState::pop_stack()
+{
+  this->parseStack.pop_back();
+}
+
+std::string cmJSONState::GetJsonContext(Location loc)
+{
+  std::string line;
+  std::stringstream sstream(doc);
+  for (int i = 0; i < loc.line; ++i) {
+    std::getline(sstream, line, '\n');
+  }
+  return cmStrCat(line, '\n', std::string(loc.column - 1, ' '), '^');
+}
+
+cmJSONState::Location cmJSONState::LocateInDocument(ptrdiff_t offset)
+{
+  int line = 1;
+  int col = 1;
+  const char* beginDoc = doc.data();
+  const char* last = beginDoc + offset;
+  for (; beginDoc != last; ++beginDoc) {
+    switch (*beginDoc) {
+      case '\r':
+        if (beginDoc + 1 != last && beginDoc[1] == '\n') {
+          continue; // consume CRLF as a single token.
+        }
+        CM_FALLTHROUGH; // CR without a following LF is same as LF
+      case '\n':
+        col = 1;
+        ++line;
+        break;
+      default:
+        ++col;
+        break;
+    }
+  }
+  return { line, col };
+}
diff --git a/Source/cmJSONState.h b/Source/cmJSONState.h
new file mode 100644
index 0000000..4984c81
--- /dev/null
+++ b/Source/cmJSONState.h
@@ -0,0 +1,73 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <cstddef>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "cmJSONState.h"
+#include "cmStringAlgorithms.h"
+
+namespace Json {
+class Value;
+}
+
+class cmJSONState
+{
+  using Location = struct
+  {
+    int line;
+    int column;
+  };
+
+public:
+  using JsonPair = std::pair<const std::string, const Json::Value*>;
+  cmJSONState() = default;
+  cmJSONState(const std::string& filename, Json::Value* root);
+  void AddError(std::string const& errMsg);
+  void AddErrorAtValue(std::string const& errMsg, const Json::Value* value);
+  void AddErrorAtOffset(std::string const& errMsg, std::ptrdiff_t offset);
+  std::string GetErrorMessage(bool showContext = true);
+  std::string key();
+  std::string key_after(std::string const& key);
+  const Json::Value* value_after(std::string const& key);
+  void push_stack(std::string const& key, const Json::Value* value);
+  void pop_stack();
+
+  class Error
+  {
+  public:
+    Error(Location loc, std::string errMsg)
+      : location(loc)
+      , message(std::move(errMsg)){};
+    Error(std::string errMsg)
+      : location({ -1, -1 })
+      , message(std::move(errMsg)){};
+    std::string GetErrorMessage() const
+    {
+      std::string output = message;
+      if (location.line > 0) {
+        output = cmStrCat("Error: @", location.line, ",", location.column,
+                          ": ", output);
+      }
+      return output;
+    }
+    Location GetLocation() const { return location; }
+
+  private:
+    Location location;
+    std::string message;
+  };
+
+  std::vector<JsonPair> parseStack;
+  std::vector<Error> errors;
+  std::string doc;
+
+private:
+  std::string GetJsonContext(Location loc);
+  Location LocateInDocument(ptrdiff_t offset);
+};
diff --git a/Source/cmLDConfigLDConfigTool.cxx b/Source/cmLDConfigLDConfigTool.cxx
index cce6178..0752b33 100644
--- a/Source/cmLDConfigLDConfigTool.cxx
+++ b/Source/cmLDConfigLDConfigTool.cxx
@@ -9,9 +9,9 @@
 
 #include "cmsys/RegularExpression.hxx"
 
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmRuntimeDependencyArchive.h"
-#include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmUVProcessChain.h"
 
@@ -34,7 +34,7 @@
     }
   }
 
-  std::vector<std::string> ldConfigCommand = cmExpandedList(ldConfigPath);
+  cmList ldConfigCommand{ ldConfigPath };
   ldConfigCommand.emplace_back("-v");
   ldConfigCommand.emplace_back("-N"); // Don't rebuild the cache.
   ldConfigCommand.emplace_back("-X"); // Don't update links.
diff --git a/Source/cmLinkLineComputer.cxx b/Source/cmLinkLineComputer.cxx
index ba0c138..b7ee4fa 100644
--- a/Source/cmLinkLineComputer.cxx
+++ b/Source/cmLinkLineComputer.cxx
@@ -191,16 +191,24 @@
 }
 
 std::string cmLinkLineComputer::ComputeFrameworkPath(
-  cmComputeLinkInformation& cli, std::string const& fwSearchFlag)
+  cmComputeLinkInformation& cli, cmValue fwSearchFlag, cmValue sysFwSearchFlag)
 {
+  if (!fwSearchFlag && !sysFwSearchFlag) {
+    return std::string{};
+  }
+
   std::string frameworkPath;
-  if (!fwSearchFlag.empty()) {
-    std::vector<std::string> const& fwDirs = cli.GetFrameworkPaths();
-    for (std::string const& fd : fwDirs) {
+  auto const& fwDirs = cli.GetFrameworkPaths();
+  for (auto const& fd : fwDirs) {
+    if (sysFwSearchFlag &&
+        cli.GetTarget()->IsSystemIncludeDirectory(fd, cli.GetConfig(),
+                                                  cli.GetLinkLanguage())) {
+      frameworkPath += sysFwSearchFlag;
+    } else {
       frameworkPath += fwSearchFlag;
-      frameworkPath += this->ConvertToOutputFormat(fd);
-      frameworkPath += " ";
     }
+    frameworkPath += this->ConvertToOutputFormat(fd);
+    frameworkPath += " ";
   }
   return frameworkPath;
 }
diff --git a/Source/cmLinkLineComputer.h b/Source/cmLinkLineComputer.h
index 9fb222c..4e285f2 100644
--- a/Source/cmLinkLineComputer.h
+++ b/Source/cmLinkLineComputer.h
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "cmStateDirectory.h"
+#include "cmValue.h"
 
 class cmComputeLinkInformation;
 class cmGeneratorTarget;
@@ -43,7 +44,8 @@
                        std::vector<BT<std::string>>& linkPath);
 
   std::string ComputeFrameworkPath(cmComputeLinkInformation& cli,
-                                   std::string const& fwSearchFlag);
+                                   cmValue fwSearchFlag,
+                                   cmValue sysFwSearchFlag);
 
   std::string ComputeLinkLibraries(cmComputeLinkInformation& cli,
                                    std::string const& stdLibString);
diff --git a/Source/cmList.cxx b/Source/cmList.cxx
new file mode 100644
index 0000000..022fcd2
--- /dev/null
+++ b/Source/cmList.cxx
@@ -0,0 +1,1003 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cmList.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <functional>
+#include <iterator>
+#include <set>
+#include <stdexcept>
+#include <utility>
+
+#include <cm/memory>
+
+#include "cmsys/RegularExpression.hxx"
+
+#include "cmAlgorithms.h"
+#include "cmGeneratorExpression.h"
+#include "cmRange.h"
+#include "cmStringAlgorithms.h"
+#include "cmStringReplaceHelper.h"
+#include "cmSystemTools.h"
+
+cm::string_view cmList::element_separator{ ";" };
+
+cmList cmList::sublist(size_type pos, size_type length) const
+{
+  if (pos >= this->Values.size()) {
+    throw std::out_of_range(cmStrCat(
+      "begin index: ", pos, " is out of range 0 - ", this->Values.size() - 1));
+  }
+
+  size_type count = (length == npos || pos + length > this->size())
+    ? this->size()
+    : pos + length;
+  return this->sublist(this->begin() + pos, this->begin() + count);
+}
+
+cmList::size_type cmList::find(cm::string_view value) const
+{
+  auto res = std::find(this->Values.begin(), this->Values.end(), value);
+  if (res == this->Values.end()) {
+    return npos;
+  }
+
+  return std::distance(this->Values.begin(), res);
+}
+
+cmList& cmList::remove_duplicates()
+{
+  auto newEnd = cmRemoveDuplicates(this->Values);
+  this->Values.erase(newEnd, this->Values.end());
+
+  return *this;
+}
+
+namespace {
+class MatchesRegex
+{
+public:
+  MatchesRegex(cmsys::RegularExpression& regex, cmList::FilterMode mode)
+    : Regex(regex)
+    , IncludeMatches(mode == cmList::FilterMode::INCLUDE)
+  {
+  }
+
+  bool operator()(const std::string& target)
+  {
+    return this->Regex.find(target) ^ this->IncludeMatches;
+  }
+
+private:
+  cmsys::RegularExpression& Regex;
+  const bool IncludeMatches;
+};
+}
+
+cmList& cmList::filter(cm::string_view pattern, FilterMode mode)
+{
+  cmsys::RegularExpression regex(std::string{ pattern });
+  if (!regex.is_valid()) {
+    throw std::invalid_argument(
+      cmStrCat("sub-command FILTER, mode REGEX failed to compile regex \"",
+               pattern, "\"."));
+  }
+
+  auto it = std::remove_if(this->Values.begin(), this->Values.end(),
+                           MatchesRegex{ regex, mode });
+  this->Values.erase(it, this->Values.end());
+
+  return *this;
+}
+
+namespace {
+class StringSorter
+{
+protected:
+  using StringFilter = std::function<std::string(const std::string&)>;
+
+  using OrderMode = cmList::SortConfiguration::OrderMode;
+  using CompareMethod = cmList::SortConfiguration::CompareMethod;
+  using CaseSensitivity = cmList::SortConfiguration::CaseSensitivity;
+
+  StringFilter GetCompareFilter(CompareMethod compare)
+  {
+    return (compare == CompareMethod::FILE_BASENAME)
+      ? cmSystemTools::GetFilenameName
+      : nullptr;
+  }
+
+  StringFilter GetCaseFilter(CaseSensitivity sensitivity)
+  {
+    return (sensitivity == CaseSensitivity::INSENSITIVE)
+      ? cmSystemTools::LowerCase
+      : nullptr;
+  }
+
+  using ComparisonFunction =
+    std::function<bool(const std::string&, const std::string&)>;
+  ComparisonFunction GetComparisonFunction(CompareMethod compare)
+  {
+    if (compare == CompareMethod::NATURAL) {
+      return std::function<bool(const std::string&, const std::string&)>(
+        [](const std::string& x, const std::string& y) {
+          return cmSystemTools::strverscmp(x, y) < 0;
+        });
+    }
+    return std::function<bool(const std::string&, const std::string&)>(
+      [](const std::string& x, const std::string& y) { return x < y; });
+  }
+
+public:
+  StringSorter(cmList::SortConfiguration const& config)
+    : Filters{ this->GetCompareFilter(config.Compare),
+               this->GetCaseFilter(config.Case) }
+    , SortMethod(this->GetComparisonFunction(config.Compare))
+    , Descending(config.Order == OrderMode::DESCENDING)
+  {
+  }
+
+  std::string ApplyFilter(const std::string& argument)
+  {
+    std::string result = argument;
+    for (auto const& filter : this->Filters) {
+      if (filter != nullptr) {
+        result = filter(result);
+      }
+    }
+    return result;
+  }
+
+  bool operator()(const std::string& a, const std::string& b)
+  {
+    std::string af = this->ApplyFilter(a);
+    std::string bf = this->ApplyFilter(b);
+    bool result;
+    if (this->Descending) {
+      result = this->SortMethod(bf, af);
+    } else {
+      result = this->SortMethod(af, bf);
+    }
+    return result;
+  }
+
+private:
+  StringFilter Filters[2] = { nullptr, nullptr };
+  ComparisonFunction SortMethod;
+  bool Descending;
+};
+}
+
+cmList::SortConfiguration::SortConfiguration() = default;
+
+cmList& cmList::sort(const SortConfiguration& cfg)
+{
+  SortConfiguration config{ cfg };
+
+  if (config.Order == SortConfiguration::OrderMode::DEFAULT) {
+    config.Order = SortConfiguration::OrderMode::ASCENDING;
+  }
+  if (config.Compare == SortConfiguration::CompareMethod::DEFAULT) {
+    config.Compare = SortConfiguration::CompareMethod::STRING;
+  }
+  if (config.Case == SortConfiguration::CaseSensitivity::DEFAULT) {
+    config.Case = SortConfiguration::CaseSensitivity::SENSITIVE;
+  }
+
+  if ((config.Compare == SortConfiguration::CompareMethod::STRING) &&
+      (config.Case == SortConfiguration::CaseSensitivity::SENSITIVE) &&
+      (config.Order == SortConfiguration::OrderMode::ASCENDING)) {
+    std::sort(this->Values.begin(), this->Values.end());
+  } else {
+    StringSorter sorter(config);
+    std::sort(this->Values.begin(), this->Values.end(), sorter);
+  }
+
+  return *this;
+}
+
+namespace {
+using transform_type = std::function<std::string(const std::string&)>;
+using transform_error = cmList::transform_error;
+
+class TransformSelector : public cmList::TransformSelector
+{
+public:
+  ~TransformSelector() override = default;
+
+  std::string Tag;
+
+  const std::string& GetTag() override { return this->Tag; }
+
+  virtual bool Validate(std::size_t count = 0) = 0;
+
+  virtual bool InSelection(const std::string&) = 0;
+
+  virtual void Transform(cmList::container_type& list,
+                         const transform_type& transform)
+  {
+    std::transform(list.begin(), list.end(), list.begin(), transform);
+  }
+
+protected:
+  TransformSelector(std::string&& tag)
+    : Tag(std::move(tag))
+  {
+  }
+};
+
+class TransformNoSelector : public TransformSelector
+{
+public:
+  TransformNoSelector()
+    : TransformSelector("NO SELECTOR")
+  {
+  }
+
+  bool Validate(std::size_t) override { return true; }
+
+  bool InSelection(const std::string&) override { return true; }
+};
+class TransformSelectorRegex : public TransformSelector
+{
+public:
+  TransformSelectorRegex(const std::string& regex)
+    : TransformSelector("REGEX")
+    , Regex(regex)
+  {
+  }
+  TransformSelectorRegex(std::string&& regex)
+    : TransformSelector("REGEX")
+    , Regex(regex)
+  {
+  }
+
+  bool Validate(std::size_t) override { return this->Regex.is_valid(); }
+
+  bool InSelection(const std::string& value) override
+  {
+    return this->Regex.find(value);
+  }
+
+  cmsys::RegularExpression Regex;
+};
+class TransformSelectorIndexes : public TransformSelector
+{
+public:
+  std::vector<index_type> Indexes;
+
+  bool InSelection(const std::string&) override { return true; }
+
+  void Transform(std::vector<std::string>& list,
+                 const transform_type& transform) override
+  {
+    this->Validate(list.size());
+
+    for (auto index : this->Indexes) {
+      list[index] = transform(list[index]);
+    }
+  }
+
+protected:
+  TransformSelectorIndexes(std::string&& tag)
+    : TransformSelector(std::move(tag))
+  {
+  }
+  TransformSelectorIndexes(std::string&& tag,
+                           std::vector<index_type> const& indexes)
+    : TransformSelector(std::move(tag))
+    , Indexes(indexes)
+  {
+  }
+  TransformSelectorIndexes(std::string&& tag,
+                           std::vector<index_type>&& indexes)
+    : TransformSelector(std::move(tag))
+    , Indexes(indexes)
+  {
+  }
+
+  index_type NormalizeIndex(index_type index, std::size_t count)
+  {
+    if (index < 0) {
+      index = static_cast<index_type>(count) + index;
+    }
+    if (index < 0 || count <= static_cast<std::size_t>(index)) {
+      throw transform_error(cmStrCat(
+        "sub-command TRANSFORM, selector ", this->Tag, ", index: ", index,
+        " out of range (-", count, ", ", count - 1, ")."));
+    }
+    return index;
+  }
+};
+class TransformSelectorAt : public TransformSelectorIndexes
+{
+public:
+  TransformSelectorAt(std::vector<index_type> const& indexes)
+    : TransformSelectorIndexes("AT", indexes)
+  {
+  }
+  TransformSelectorAt(std::vector<index_type>&& indexes)
+    : TransformSelectorIndexes("AT", std::move(indexes))
+  {
+  }
+
+  bool Validate(std::size_t count) override
+  {
+    decltype(this->Indexes) indexes;
+
+    for (auto index : this->Indexes) {
+      indexes.push_back(this->NormalizeIndex(index, count));
+    }
+    this->Indexes = std::move(indexes);
+
+    return true;
+  }
+};
+class TransformSelectorFor : public TransformSelectorIndexes
+{
+public:
+  TransformSelectorFor(index_type start, index_type stop, index_type step)
+    : TransformSelectorIndexes("FOR")
+    , Start(start)
+    , Stop(stop)
+    , Step(step)
+  {
+  }
+
+  bool Validate(std::size_t count) override
+  {
+    this->Start = this->NormalizeIndex(this->Start, count);
+    this->Stop = this->NormalizeIndex(this->Stop, count);
+
+    // Does stepping move us further from the end?
+    if (this->Start > this->Stop) {
+      throw transform_error(
+        cmStrCat("sub-command TRANSFORM, selector FOR "
+                 "expects <start> to be no greater than <stop> (",
+                 this->Start, " > ", this->Stop, ")"));
+    }
+
+    // compute indexes
+    auto size = (this->Stop - this->Start + 1) / this->Step;
+    if ((this->Stop - this->Start + 1) % this->Step != 0) {
+      size += 1;
+    }
+
+    this->Indexes.resize(size);
+    auto start = this->Start;
+    auto step = this->Step;
+    std::generate(this->Indexes.begin(), this->Indexes.end(),
+                  [&start, step]() -> index_type {
+                    auto r = start;
+                    start += step;
+                    return r;
+                  });
+
+    return true;
+  }
+
+private:
+  index_type Start, Stop, Step;
+};
+
+class TransformAction
+{
+public:
+  virtual ~TransformAction() = default;
+
+  void Initialize(TransformSelector* selector) { this->Selector = selector; }
+  virtual void Initialize(TransformSelector*, const std::string&) {}
+  virtual void Initialize(TransformSelector*, const std::string&,
+                          const std::string&)
+  {
+  }
+  virtual void Initialize(TransformSelector* selector,
+                          const std::vector<std::string>&)
+  {
+    this->Initialize(selector);
+  }
+
+  virtual std::string operator()(const std::string& s) = 0;
+
+protected:
+  TransformSelector* Selector;
+};
+class TransformActionAppend : public TransformAction
+{
+public:
+  using TransformAction::Initialize;
+
+  void Initialize(TransformSelector* selector,
+                  const std::string& append) override
+  {
+    TransformAction::Initialize(selector);
+    this->Append = append;
+  }
+  void Initialize(TransformSelector* selector,
+                  const std::vector<std::string>& append) override
+  {
+    this->Initialize(selector, append.front());
+  }
+
+  std::string operator()(const std::string& s) override
+  {
+    if (this->Selector->InSelection(s)) {
+      return cmStrCat(s, this->Append);
+    }
+
+    return s;
+  }
+
+private:
+  std::string Append;
+};
+class TransformActionPrepend : public TransformAction
+{
+public:
+  using TransformAction::Initialize;
+
+  void Initialize(TransformSelector* selector,
+                  const std::string& prepend) override
+  {
+    TransformAction::Initialize(selector);
+    this->Prepend = prepend;
+  }
+  void Initialize(TransformSelector* selector,
+                  const std::vector<std::string>& prepend) override
+  {
+    this->Initialize(selector, prepend.front());
+  }
+
+  std::string operator()(const std::string& s) override
+  {
+    if (this->Selector->InSelection(s)) {
+      return cmStrCat(this->Prepend, s);
+    }
+
+    return s;
+  }
+
+private:
+  std::string Prepend;
+};
+class TransformActionToUpper : public TransformAction
+{
+public:
+  std::string operator()(const std::string& s) override
+  {
+    if (this->Selector->InSelection(s)) {
+      return cmSystemTools::UpperCase(s);
+    }
+
+    return s;
+  }
+};
+class TransformActionToLower : public TransformAction
+{
+public:
+  std::string operator()(const std::string& s) override
+  {
+    if (this->Selector->InSelection(s)) {
+      return cmSystemTools::LowerCase(s);
+    }
+
+    return s;
+  }
+};
+class TransformActionStrip : public TransformAction
+{
+public:
+  std::string operator()(const std::string& s) override
+  {
+    if (this->Selector->InSelection(s)) {
+      return cmTrimWhitespace(s);
+    }
+
+    return s;
+  }
+};
+class TransformActionGenexStrip : public TransformAction
+{
+public:
+  std::string operator()(const std::string& s) override
+  {
+    if (this->Selector->InSelection(s)) {
+      return cmGeneratorExpression::Preprocess(
+        s, cmGeneratorExpression::StripAllGeneratorExpressions);
+    }
+
+    return s;
+  }
+};
+class TransformActionReplace : public TransformAction
+{
+public:
+  using TransformAction::Initialize;
+
+  void Initialize(TransformSelector* selector, const std::string& regex,
+                  const std::string& replace) override
+  {
+    TransformAction::Initialize(selector);
+    this->ReplaceHelper =
+      cm::make_unique<cmStringReplaceHelper>(regex, replace);
+
+    if (!this->ReplaceHelper->IsRegularExpressionValid()) {
+      throw transform_error(
+        cmStrCat("sub-command TRANSFORM, action REPLACE: Failed to compile "
+                 "regex \"",
+                 regex, "\"."));
+    }
+    if (!this->ReplaceHelper->IsReplaceExpressionValid()) {
+      throw transform_error(cmStrCat("sub-command TRANSFORM, action REPLACE: ",
+                                     this->ReplaceHelper->GetError(), "."));
+    }
+  }
+  void Initialize(TransformSelector* selector,
+                  const std::vector<std::string>& args) override
+  {
+    this->Initialize(selector, args[0], args[1]);
+  }
+
+  std::string operator()(const std::string& s) override
+  {
+    if (this->Selector->InSelection(s)) {
+      // Scan through the input for all matches.
+      std::string output;
+
+      if (!this->ReplaceHelper->Replace(s, output)) {
+        throw transform_error(
+          cmStrCat("sub-command TRANSFORM, action REPLACE: ",
+                   this->ReplaceHelper->GetError(), "."));
+      }
+
+      return output;
+    }
+
+    return s;
+  }
+
+private:
+  std::unique_ptr<cmStringReplaceHelper> ReplaceHelper;
+};
+
+// Descriptor of action
+// Arity: number of arguments required for the action
+// Transform: Object implementing the action
+struct ActionDescriptor
+{
+  ActionDescriptor(cmList::TransformAction action)
+    : Action(action)
+  {
+  }
+  ActionDescriptor(cmList::TransformAction action, std::string name,
+                   std::size_t arity,
+                   std::unique_ptr<TransformAction> transform)
+    : Action(action)
+    , Name(std::move(name))
+    , Arity(arity)
+    , Transform(std::move(transform))
+  {
+  }
+
+  operator cmList::TransformAction() const { return this->Action; }
+
+  cmList::TransformAction Action;
+  std::string Name;
+  std::size_t Arity = 0;
+  std::unique_ptr<TransformAction> Transform;
+};
+
+// Build a set of supported actions.
+using ActionDescriptorSet = std::set<
+  ActionDescriptor,
+  std::function<bool(cmList::TransformAction, cmList::TransformAction)>>;
+
+ActionDescriptorSet Descriptors([](cmList::TransformAction x,
+                                   cmList::TransformAction y) {
+  return x < y;
+});
+
+ActionDescriptorSet::iterator TransformConfigure(
+  cmList::TransformAction action,
+  std::unique_ptr<cmList::TransformSelector>& selector, std::size_t arity)
+{
+  if (Descriptors.empty()) {
+    Descriptors.emplace(cmList::TransformAction::APPEND, "APPEND", 1,
+                        cm::make_unique<TransformActionAppend>());
+    Descriptors.emplace(cmList::TransformAction::PREPEND, "PREPEND", 1,
+                        cm::make_unique<TransformActionPrepend>());
+    Descriptors.emplace(cmList::TransformAction::TOUPPER, "TOUPPER", 0,
+                        cm::make_unique<TransformActionToUpper>());
+    Descriptors.emplace(cmList::TransformAction::TOLOWER, "TOLOWER", 0,
+                        cm::make_unique<TransformActionToLower>());
+    Descriptors.emplace(cmList::TransformAction::STRIP, "STRIP", 0,
+                        cm::make_unique<TransformActionStrip>());
+    Descriptors.emplace(cmList::TransformAction::GENEX_STRIP, "GENEX_STRIP", 0,
+                        cm::make_unique<TransformActionGenexStrip>());
+    Descriptors.emplace(cmList::TransformAction::REPLACE, "REPLACE", 2,
+                        cm::make_unique<TransformActionReplace>());
+  }
+
+  auto descriptor = Descriptors.find(action);
+  if (descriptor == Descriptors.end()) {
+    throw transform_error(cmStrCat(" sub-command TRANSFORM, ",
+                                   std::to_string(static_cast<int>(action)),
+                                   " invalid action."));
+  }
+
+  if (descriptor->Arity != arity) {
+    throw transform_error(cmStrCat("sub-command TRANSFORM, action ",
+                                   descriptor->Name, " expects ",
+                                   descriptor->Arity, " argument(s)."));
+  }
+  if (!selector) {
+    selector = cm::make_unique<TransformNoSelector>();
+  }
+
+  return descriptor;
+}
+}
+
+std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewAT(
+  std::initializer_list<index_type> indexes)
+{
+  return cm::make_unique<TransformSelectorAt>(
+    std::vector<index_type>{ indexes.begin(), indexes.end() });
+  ;
+}
+std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewAT(
+  std::vector<index_type> const& indexes)
+{
+  return cm::make_unique<TransformSelectorAt>(indexes);
+}
+std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewAT(
+  std::vector<index_type>&& indexes)
+{
+  return cm::make_unique<TransformSelectorAt>(std::move(indexes));
+}
+
+std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewFOR(
+  std::initializer_list<index_type> indexes)
+{
+  if (indexes.size() < 2 || indexes.size() > 3) {
+    throw transform_error("sub-command TRANSFORM, selector FOR "
+                          "expects 2 or 3 arguments");
+  }
+  if (indexes.size() == 3 && *(indexes.begin() + 2) < 0) {
+    throw transform_error("sub-command TRANSFORM, selector FOR expects "
+                          "positive numeric value for <step>.");
+  }
+
+  return cm::make_unique<TransformSelectorFor>(
+    *indexes.begin(), *(indexes.begin() + 1),
+    indexes.size() == 3 ? *(indexes.begin() + 2) : 1);
+}
+std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewFOR(
+  std::vector<index_type> const& indexes)
+{
+  if (indexes.size() < 2 || indexes.size() > 3) {
+    throw transform_error("sub-command TRANSFORM, selector FOR "
+                          "expects 2 or 3 arguments");
+  }
+  if (indexes.size() == 3 && indexes[2] < 0) {
+    throw transform_error("sub-command TRANSFORM, selector FOR expects "
+                          "positive numeric value for <step>.");
+  }
+
+  return cm::make_unique<TransformSelectorFor>(
+    indexes[0], indexes[1], indexes.size() == 3 ? indexes[2] : 1);
+}
+std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewFOR(
+  std::vector<index_type>&& indexes)
+{
+  if (indexes.size() < 2 || indexes.size() > 3) {
+    throw transform_error("sub-command TRANSFORM, selector FOR "
+                          "expects 2 or 3 arguments");
+  }
+  if (indexes.size() == 3 && indexes[2] < 0) {
+    throw transform_error("sub-command TRANSFORM, selector FOR expects "
+                          "positive numeric value for <step>.");
+  }
+
+  return cm::make_unique<TransformSelectorFor>(
+    indexes[0], indexes[1], indexes.size() == 3 ? indexes[2] : 1);
+}
+
+std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewREGEX(
+  std::string const& regex)
+{
+  std::unique_ptr<::TransformSelector> selector =
+    cm::make_unique<TransformSelectorRegex>(regex);
+  if (!selector->Validate()) {
+    throw transform_error(
+      cmStrCat("sub-command TRANSFORM, selector REGEX failed to compile "
+               "regex \"",
+               regex, "\"."));
+  }
+  // weird construct to please all compilers
+  return std::unique_ptr<cmList::TransformSelector>(selector.release());
+}
+std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewREGEX(
+  std::string&& regex)
+{
+  std::unique_ptr<::TransformSelector> selector =
+    cm::make_unique<TransformSelectorRegex>(std::move(regex));
+  if (!selector->Validate()) {
+    throw transform_error(
+      cmStrCat("sub-command TRANSFORM, selector REGEX failed to compile "
+               "regex \"",
+               regex, "\"."));
+  }
+  // weird construct to please all compilers
+  return std::unique_ptr<cmList::TransformSelector>(selector.release());
+}
+
+cmList& cmList::transform(TransformAction action,
+                          std::unique_ptr<TransformSelector> selector)
+{
+  auto descriptor = TransformConfigure(action, selector, 0);
+
+  descriptor->Transform->Initialize(
+    static_cast<::TransformSelector*>(selector.get()));
+
+  static_cast<::TransformSelector&>(*selector).Transform(
+    this->Values, [&descriptor](const std::string& s) -> std::string {
+      return (*descriptor->Transform)(s);
+    });
+
+  return *this;
+}
+
+cmList& cmList::transform(TransformAction action, std::string const& arg,
+                          std::unique_ptr<TransformSelector> selector)
+{
+  auto descriptor = TransformConfigure(action, selector, 1);
+
+  descriptor->Transform->Initialize(
+    static_cast<::TransformSelector*>(selector.get()), arg);
+
+  static_cast<::TransformSelector&>(*selector).Transform(
+    this->Values, [&descriptor](const std::string& s) -> std::string {
+      return (*descriptor->Transform)(s);
+    });
+
+  return *this;
+}
+
+cmList& cmList::transform(TransformAction action, std::string const& arg1,
+                          std::string const& arg2,
+                          std::unique_ptr<TransformSelector> selector)
+{
+  auto descriptor = TransformConfigure(action, selector, 2);
+
+  descriptor->Transform->Initialize(
+    static_cast<::TransformSelector*>(selector.get()), arg1, arg2);
+
+  static_cast<::TransformSelector&>(*selector).Transform(
+    this->Values, [&descriptor](const std::string& s) -> std::string {
+      return (*descriptor->Transform)(s);
+    });
+
+  return *this;
+}
+
+cmList& cmList::transform(TransformAction action,
+                          std::vector<std::string> const& args,
+                          std::unique_ptr<TransformSelector> selector)
+{
+  auto descriptor = TransformConfigure(action, selector, args.size());
+
+  descriptor->Transform->Initialize(
+    static_cast<::TransformSelector*>(selector.get()), args);
+
+  static_cast<::TransformSelector&>(*selector).Transform(
+    this->Values, [&descriptor](const std::string& s) -> std::string {
+      return (*descriptor->Transform)(s);
+    });
+
+  return *this;
+}
+
+std::string cmList::join(cm::string_view glue) const
+{
+  return cmJoin(this->Values, glue);
+}
+
+std::string& cmList::append(std::string& list, cm::string_view value)
+{
+  if (list.empty()) {
+    list = std::string(value);
+  } else {
+    list += cmStrCat(cmList::element_separator, value);
+  }
+
+  return list;
+}
+
+std::string& cmList::prepend(std::string& list, cm::string_view value)
+{
+  if (list.empty()) {
+    list = std::string(value);
+  } else {
+    list.insert(0, cmStrCat(value, cmList::element_separator));
+  }
+
+  return list;
+}
+
+cmList::size_type cmList::ComputeIndex(index_type pos, bool boundCheck) const
+{
+  if (boundCheck) {
+    if (this->Values.empty()) {
+      throw std::out_of_range(
+        cmStrCat("index: ", pos, " out of range (0, 0)"));
+    }
+
+    auto index = pos;
+    if (!this->Values.empty()) {
+      auto length = this->Values.size();
+      if (index < 0) {
+        index = static_cast<index_type>(length) + index;
+      }
+      if (index < 0 || length <= static_cast<size_type>(index)) {
+        throw std::out_of_range(cmStrCat("index: ", pos, " out of range (-",
+                                         this->Values.size(), ", ",
+                                         this->Values.size() - 1, ")"));
+      }
+    }
+    return index;
+  }
+
+  return pos < 0 ? this->Values.size() + pos : pos;
+}
+cmList::size_type cmList::ComputeInsertIndex(index_type pos,
+                                             bool boundCheck) const
+{
+  if (boundCheck) {
+    if (this->Values.empty() && pos != 0) {
+      throw std::out_of_range(
+        cmStrCat("index: ", pos, " out of range (0, 0)"));
+    }
+
+    auto index = pos;
+    if (!this->Values.empty()) {
+      auto length = this->Values.size();
+      if (index < 0) {
+        index = static_cast<index_type>(length) + index;
+      }
+      if (index < 0 || length < static_cast<size_type>(index)) {
+        throw std::out_of_range(cmStrCat("index: ", pos, " out of range (-",
+                                         this->Values.size(), ", ",
+                                         this->Values.size(), ")"));
+      }
+    }
+    return index;
+  }
+
+  return pos < 0 ? this->Values.size() + pos : pos;
+}
+
+cmList cmList::GetItems(std::vector<index_type>&& indexes) const
+{
+  cmList listItems;
+
+  for (auto index : indexes) {
+    listItems.emplace_back(this->get_item(index));
+  }
+
+  return listItems;
+}
+
+cmList& cmList::RemoveItems(std::vector<index_type>&& indexes)
+{
+  if (indexes.empty()) {
+    return *this;
+  }
+
+  // compute all indexes
+  std::vector<size_type> idx(indexes.size());
+  std::transform(indexes.cbegin(), indexes.cend(), idx.begin(),
+                 [this](const index_type& index) -> size_type {
+                   return this->ComputeIndex(index);
+                 });
+
+  std::sort(idx.begin(), idx.end(),
+            [](size_type l, size_type r) { return l > r; });
+  auto newEnd = std::unique(idx.begin(), idx.end());
+  idx.erase(newEnd, idx.end());
+
+  for (auto index : idx) {
+    this->erase(this->begin() + index);
+  }
+
+  return *this;
+}
+
+cmList& cmList::RemoveItems(std::vector<std::string>&& items)
+{
+  std::sort(items.begin(), items.end());
+  auto last = std::unique(items.begin(), items.end());
+  auto first = items.begin();
+
+  auto newEnd = cmRemoveMatching(this->Values, cmMakeRange(first, last));
+  this->Values.erase(newEnd, this->Values.end());
+
+  return *this;
+}
+
+cmList::container_type::iterator cmList::Insert(
+  container_type& container, container_type::const_iterator pos,
+  std::string&& value, ExpandElements expandElements,
+  EmptyElements emptyElements)
+{
+  auto delta = std::distance(container.cbegin(), pos);
+  auto insertPos = container.begin() + delta;
+
+  if (expandElements == ExpandElements::Yes) {
+    // If argument is empty, it is an empty list.
+    if (emptyElements == EmptyElements::No && value.empty()) {
+      return insertPos;
+    }
+
+    // if there are no ; in the name then just copy the current string
+    if (value.find(';') == std::string::npos) {
+      return container.insert(insertPos, std::move(value));
+    }
+
+    std::string newValue;
+    // Break the string at non-escaped semicolons not nested in [].
+    int squareNesting = 0;
+    auto last = value.begin();
+    auto const cend = value.end();
+    for (auto c = last; c != cend; ++c) {
+      switch (*c) {
+        case '\\': {
+          // We only want to allow escaping of semicolons.  Other
+          // escapes should not be processed here.
+          auto cnext = c + 1;
+          if ((cnext != cend) && *cnext == ';') {
+            newValue.append(last, c);
+            // Skip over the escape character
+            last = cnext;
+            c = cnext;
+          }
+        } break;
+        case '[': {
+          ++squareNesting;
+        } break;
+        case ']': {
+          --squareNesting;
+        } break;
+        case ';': {
+          // brackets.
+          if (squareNesting == 0) {
+            newValue.append(last, c);
+            // Skip over the semicolon
+            last = c + 1;
+            if (!newValue.empty() || emptyElements == EmptyElements::Yes) {
+              // Add the last argument.
+              insertPos = container.insert(insertPos, newValue);
+              insertPos++;
+              newValue.clear();
+            }
+          }
+        } break;
+        default: {
+          // Just append this character.
+        } break;
+      }
+    }
+    newValue.append(last, cend);
+    if (!newValue.empty() || emptyElements == EmptyElements::Yes) {
+      // Add the last argument.
+      container.insert(insertPos, std::move(newValue));
+    }
+  } else if (!value.empty() || emptyElements == EmptyElements::Yes) {
+    return container.insert(insertPos, std::move(value));
+  }
+  return container.begin() + delta;
+}
diff --git a/Source/cmList.h b/Source/cmList.h
new file mode 100644
index 0000000..d9ce951
--- /dev/null
+++ b/Source/cmList.h
@@ -0,0 +1,1352 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <algorithm>
+#include <cstdint>
+#include <initializer_list>
+#include <iterator>
+#include <memory>
+#include <numeric>
+#include <stdexcept>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <cm/string_view>
+#include <cm/type_traits>
+#include <cmext/iterator>
+
+#include "cmValue.h"
+
+/**
+ * CMake lists management
+ * A CMake list is a string where list elements are separated by the ';'
+ * character.
+ *
+ * For all operations, input arguments (single value like cm::string_view or
+ * multiple values specified through pair of iterators) are, by default,
+ * expanded. The expansion can be controlled by the cmList::ExpandElements
+ * option.
+ *
+ * There ate some exceptions to this rule:
+ *  * When the input argument is a cmList instance, the value is not expanded.
+ *  * The following methods do not expand their argument: cmList::push_back,
+ *    cmList::emplace and cmList::emplace_back.
+ */
+
+class cmList
+{
+public:
+  using container_type = std::vector<std::string>;
+
+  using value_type = container_type::value_type;
+  using allocator_type = container_type::allocator_type;
+  using index_type = std::intptr_t;
+  using size_type = container_type::size_type;
+  using difference_type = container_type::difference_type;
+  using reference = container_type::reference;
+  using const_reference = container_type::const_reference;
+  using iterator = container_type::iterator;
+  using const_iterator = container_type::const_iterator;
+  using reverse_iterator = container_type::reverse_iterator;
+  using const_reverse_iterator = container_type::const_reverse_iterator;
+
+  static const size_type npos = static_cast<size_type>(-1);
+
+  static cm::string_view element_separator;
+
+  enum class EmptyElements
+  {
+    No,
+    Yes,
+  };
+  enum class ExpandElements
+  {
+    No,
+    Yes,
+  };
+
+  cmList() = default;
+  cmList(const cmList&) = default;
+  cmList(cmList&&) = default;
+
+  cmList(cm::string_view value,
+         ExpandElements expandElements = ExpandElements::Yes,
+         EmptyElements emptyElements = EmptyElements::No)
+  {
+    this->assign(value, expandElements, emptyElements);
+  }
+  cmList(cm::string_view value, EmptyElements emptyElements)
+    : cmList(value, ExpandElements::Yes, emptyElements)
+  {
+  }
+  cmList(std::string const& value,
+         ExpandElements expandElements = ExpandElements::Yes,
+         EmptyElements emptyElements = EmptyElements::No)
+  {
+    this->assign(value, expandElements, emptyElements);
+  }
+  cmList(std::string const& value, EmptyElements emptyElements)
+    : cmList(value, ExpandElements::Yes, emptyElements)
+  {
+  }
+  cmList(cmValue list, ExpandElements expandElements = ExpandElements::Yes,
+         EmptyElements emptyElements = EmptyElements::No)
+  {
+    if (list) {
+      this->assign(*list, expandElements, emptyElements);
+    }
+  }
+  cmList(cmValue list, EmptyElements emptyElements)
+    : cmList(list, ExpandElements::Yes, emptyElements)
+  {
+  }
+  template <typename InputIterator>
+  cmList(InputIterator first, InputIterator last,
+         ExpandElements expandElements = ExpandElements::Yes,
+         EmptyElements emptyElements = EmptyElements::No)
+  {
+    this->assign(first, last, expandElements, emptyElements);
+  }
+  template <typename InputIterator>
+  cmList(InputIterator first, InputIterator last, EmptyElements emptyElements)
+    : cmList(first, last, ExpandElements::Yes, emptyElements)
+  {
+  }
+  cmList(const container_type& init,
+         ExpandElements expandElements = ExpandElements::Yes,
+         EmptyElements emptyElements = EmptyElements::No)
+    : cmList(init.begin(), init.end(), expandElements, emptyElements)
+  {
+  }
+  cmList(const container_type& init, EmptyElements emptyElements)
+    : cmList(init, ExpandElements::Yes, emptyElements)
+  {
+  }
+  cmList(container_type&& init,
+         ExpandElements expandElements = ExpandElements::Yes,
+         EmptyElements emptyElements = EmptyElements::No)
+    : cmList(std::make_move_iterator(init.begin()),
+             std::make_move_iterator(init.end()), expandElements,
+             emptyElements)
+  {
+    init.clear();
+  }
+  cmList(container_type&& init, EmptyElements emptyElements)
+    : cmList(std::move(init), ExpandElements::Yes, emptyElements)
+  {
+  }
+  cmList(std::initializer_list<std::string> init) { this->assign(init); }
+
+  ~cmList() = default;
+
+  cmList& operator=(const cmList&) = default;
+  cmList& operator=(cmList&&) = default;
+
+  cmList& operator=(cm::string_view value)
+  {
+    this->assign(value);
+    return *this;
+  }
+  cmList& operator=(std::string const& value)
+  {
+    this->assign(value);
+    return *this;
+  }
+  cmList& operator=(cmValue value)
+  {
+    if (value) {
+      this->operator=(*value);
+    } else {
+      this->clear();
+    }
+
+    return *this;
+  }
+
+  cmList& operator=(const container_type& init)
+  {
+    this->assign(init);
+    return *this;
+  }
+  cmList& operator=(container_type&& init)
+  {
+    this->assign(std::move(init));
+
+    return *this;
+  }
+
+  cmList& operator=(std::initializer_list<std::string> init)
+  {
+    this->assign(init);
+    return *this;
+  }
+
+  void assign(cm::string_view value,
+              ExpandElements expandElements = ExpandElements::Yes,
+              EmptyElements emptyElements = EmptyElements::No)
+  {
+    this->clear();
+    this->append(value, expandElements, emptyElements);
+  }
+  void assign(cm::string_view value, EmptyElements emptyElements)
+  {
+    this->assign(value, ExpandElements::Yes, emptyElements);
+  }
+  void assign(std::string const& value,
+              ExpandElements expandElements = ExpandElements::Yes,
+              EmptyElements emptyElements = EmptyElements::No)
+  {
+    this->clear();
+    this->append(value, expandElements, emptyElements);
+  }
+  void assign(std::string const& value, EmptyElements emptyElements)
+  {
+    this->assign(value, ExpandElements::Yes, emptyElements);
+  }
+  void assign(cmValue value,
+              ExpandElements expandElements = ExpandElements::Yes,
+              EmptyElements emptyElements = EmptyElements::No)
+  {
+    if (value) {
+      this->assign(*value, expandElements, emptyElements);
+    } else {
+      this->clear();
+    }
+  }
+  void assign(cmValue value, EmptyElements emptyElements)
+  {
+    this->assign(value, ExpandElements::Yes, emptyElements);
+  }
+  template <typename InputIterator>
+  void assign(InputIterator first, InputIterator last,
+              ExpandElements expandElements = ExpandElements::Yes,
+              EmptyElements emptyElements = EmptyElements::No)
+  {
+    this->clear();
+    this->append(first, last, expandElements, emptyElements);
+  }
+  template <typename InputIterator>
+  void assign(InputIterator first, InputIterator last,
+              EmptyElements emptyElements)
+  {
+    this->assign(first, last, ExpandElements::Yes, emptyElements);
+  }
+  void assign(const cmList& init,
+              ExpandElements expandElements = ExpandElements::No,
+              EmptyElements emptyElements = EmptyElements::No)
+  {
+    this->assign(init.begin(), init.end(), expandElements, emptyElements);
+  }
+  void assign(const cmList& init, EmptyElements emptyElements)
+  {
+    this->assign(init, ExpandElements::No, emptyElements);
+  }
+  void assign(cmList&& init,
+              ExpandElements expandElements = ExpandElements::No,
+              EmptyElements emptyElements = EmptyElements::No)
+  {
+    this->assign(std::make_move_iterator(init.begin()),
+                 std::make_move_iterator(init.end()), expandElements,
+                 emptyElements);
+    init.clear();
+  }
+  void assign(cmList&& init, EmptyElements emptyElements)
+  {
+    this->assign(std::move(init), ExpandElements::No, emptyElements);
+  }
+  void assign(const container_type& init,
+              ExpandElements expandElements = ExpandElements::Yes,
+              EmptyElements emptyElements = EmptyElements::No)
+  {
+    this->assign(init.begin(), init.end(), expandElements, emptyElements);
+  }
+  void assign(const container_type& init, EmptyElements emptyElements)
+  {
+    this->assign(init, ExpandElements::Yes, emptyElements);
+  }
+  void assign(container_type&& init,
+              ExpandElements expandElements = ExpandElements::Yes,
+              EmptyElements emptyElements = EmptyElements::No)
+  {
+    this->assign(std::make_move_iterator(init.begin()),
+                 std::make_move_iterator(init.end()), expandElements,
+                 emptyElements);
+    init.clear();
+  }
+  void assign(container_type&& init, EmptyElements emptyElements)
+  {
+    this->assign(std::move(init), ExpandElements::Yes, emptyElements);
+  }
+  void assign(std::initializer_list<std::string> init)
+  {
+    this->assign(init.begin(), init.end());
+  }
+
+  // Conversions
+  std::string to_string() const
+  {
+    return this->join(cmList::element_separator);
+  }
+
+  operator container_type&() & noexcept { return this->Values; }
+  operator const container_type&() const& noexcept { return this->Values; }
+  operator container_type&&() && noexcept { return std::move(this->Values); }
+
+  // Element access
+  reference at(size_type pos) { return this->Values.at(pos); }
+  const_reference at(size_type pos) const { return this->Values.at(pos); }
+
+  reference operator[](size_type pos) { return this->Values[pos]; }
+  const_reference operator[](size_type pos) const { return this->Values[pos]; }
+
+  reference get_item(index_type pos)
+  {
+    return this->Values.at(this->ComputeIndex(pos));
+  }
+  const_reference get_item(index_type pos) const
+  {
+    return this->Values.at(this->ComputeIndex(pos));
+  }
+
+  reference front() { return this->Values.front(); }
+  const_reference front() const { return this->Values.front(); }
+
+  reference back() { return this->Values.back(); }
+  const_reference back() const { return this->Values.back(); }
+
+  // extract sublist in range [first, last)
+  cmList sublist(const_iterator first, const_iterator last) const
+  {
+    return cmList{ first, last };
+  }
+  // Extract sublist in range [first, last)
+  // Throw std::out_of_range if pos is invalid
+  cmList sublist(size_type pos = 0, size_type length = npos) const;
+
+  // Returns the list of elements
+  // Throw std::out_of_range if any index is invalid
+  cmList get_items(std::initializer_list<index_type> indexes) const
+  {
+    return this->GetItems(
+      std::vector<index_type>{ indexes.begin(), indexes.end() });
+  }
+  template <typename InputIterator>
+  cmList get_items(InputIterator first, InputIterator last) const
+  {
+    return this->GetItems(std::vector<index_type>{ first, last });
+  }
+
+  size_type find(cm::string_view value) const;
+  size_type find(cmValue value) const
+  {
+    if (value) {
+      return this->find(*value);
+    }
+
+    return npos;
+  }
+
+  container_type& data() noexcept { return this->Values; }
+  const container_type& data() const noexcept { return this->Values; }
+
+  // Iterators
+  iterator begin() noexcept { return this->Values.begin(); }
+  const_iterator begin() const noexcept { return this->Values.begin(); }
+  const_iterator cbegin() const noexcept { return this->Values.cbegin(); }
+
+  iterator end() noexcept { return this->Values.end(); }
+  const_iterator end() const noexcept { return this->Values.end(); }
+  const_iterator cend() const noexcept { return this->Values.cend(); }
+
+  reverse_iterator rbegin() noexcept { return this->Values.rbegin(); }
+  const_reverse_iterator rbegin() const noexcept
+  {
+    return this->Values.rbegin();
+  }
+  const_reverse_iterator crbegin() const noexcept
+  {
+    return this->Values.crbegin();
+  }
+
+  reverse_iterator rend() noexcept { return this->Values.rend(); }
+  const_reverse_iterator rend() const noexcept { return this->Values.rend(); }
+  const_reverse_iterator crend() const noexcept
+  {
+    return this->Values.crend();
+  }
+
+  // Capacity
+  bool empty() const noexcept { return this->Values.empty(); }
+  size_type size() const noexcept { return this->Values.size(); }
+
+  // Modifiers
+  void clear() noexcept { this->Values.clear(); }
+
+  iterator insert(const_iterator pos, cm::string_view value,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    return cmList::Insert(this->Values, pos, std::string(value),
+                          expandElements, emptyElements);
+  }
+  iterator insert(const_iterator pos, cm::string_view value,
+                  EmptyElements emptyElements)
+  {
+    return this->insert(pos, value, ExpandElements::Yes, emptyElements);
+  }
+  iterator insert(const_iterator pos, std::string const& value,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    return cmList::Insert(this->Values, pos, value, expandElements,
+                          emptyElements);
+  }
+  iterator insert(const_iterator pos, std::string const& value,
+                  EmptyElements emptyElements)
+  {
+    return this->insert(pos, value, ExpandElements::Yes, emptyElements);
+  }
+  iterator insert(const_iterator pos, cmValue value,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    if (value) {
+      return this->insert(pos, *value, expandElements, emptyElements);
+    }
+
+    auto delta = std::distance(this->cbegin(), pos);
+    return this->begin() + delta;
+  }
+  iterator insert(const_iterator pos, cmValue value,
+                  EmptyElements emptyElements)
+  {
+    return this->insert(pos, value, ExpandElements::Yes, emptyElements);
+  }
+  template <typename InputIterator>
+  iterator insert(const_iterator pos, InputIterator first, InputIterator last,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    return cmList::Insert(this->Values, pos, first, last, expandElements,
+                          emptyElements);
+  }
+  template <typename InputIterator>
+  iterator insert(const_iterator pos, InputIterator first, InputIterator last,
+                  EmptyElements emptyElements)
+  {
+    return this->insert(pos, first, last, ExpandElements::Yes, emptyElements);
+  }
+  iterator insert(const_iterator pos, const cmList& values,
+                  ExpandElements expandElements = ExpandElements::No,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    return this->insert(pos, values.begin(), values.end(), expandElements,
+                        emptyElements);
+  }
+  iterator insert(const_iterator pos, const cmList& values,
+                  EmptyElements emptyElements)
+  {
+    return this->insert(pos, values, ExpandElements::No, emptyElements);
+  }
+  iterator insert(const_iterator pos, cmList&& values,
+                  ExpandElements expandElements = ExpandElements::No,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    auto result = this->insert(pos, std::make_move_iterator(values.begin()),
+                               std::make_move_iterator(values.end()),
+                               expandElements, emptyElements);
+    values.clear();
+
+    return result;
+  }
+  iterator insert(const_iterator pos, cmList&& values,
+                  EmptyElements emptyElements)
+  {
+    return this->insert(pos, std::move(values), ExpandElements::No,
+                        emptyElements);
+  }
+  iterator insert(const_iterator pos, const container_type& values,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    return this->insert(pos, values.begin(), values.end(), expandElements,
+                        emptyElements);
+  }
+  iterator insert(const_iterator pos, const container_type& values,
+                  EmptyElements emptyElements)
+  {
+    return this->insert(pos, values, ExpandElements::Yes, emptyElements);
+  }
+  iterator insert(const_iterator pos, container_type&& values,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    auto result = this->insert(pos, std::make_move_iterator(values.begin()),
+                               std::make_move_iterator(values.end()),
+                               expandElements, emptyElements);
+    values.clear();
+
+    return result;
+  }
+  iterator insert(const_iterator pos, container_type&& values,
+                  EmptyElements emptyElements)
+  {
+    return this->insert(pos, std::move(values), ExpandElements::Yes,
+                        emptyElements);
+  }
+  iterator insert(const_iterator pos, std::initializer_list<std::string> ilist)
+  {
+    return this->insert(pos, ilist.begin(), ilist.end());
+  }
+
+  iterator append(cm::string_view value,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    return this->insert(this->cend(), value, expandElements, emptyElements);
+  }
+  iterator append(cm::string_view value, EmptyElements emptyElements)
+  {
+    return this->append(value, ExpandElements::Yes, emptyElements);
+  }
+  iterator append(std::string const& value,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    return this->insert(this->cend(), value, expandElements, emptyElements);
+  }
+  iterator append(std::string const& value, EmptyElements emptyElements)
+  {
+    return this->append(value, ExpandElements::Yes, emptyElements);
+  }
+  iterator append(cmValue value,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    if (value) {
+      return this->append(*value, expandElements, emptyElements);
+    }
+
+    return this->end();
+  }
+  iterator append(cmValue value, EmptyElements emptyElements)
+  {
+    return this->append(value, ExpandElements::Yes, emptyElements);
+  }
+  template <typename InputIterator>
+  iterator append(InputIterator first, InputIterator last,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    return this->insert(this->cend(), first, last, expandElements,
+                        emptyElements);
+  }
+  template <typename InputIterator>
+  iterator append(InputIterator first, InputIterator last,
+                  EmptyElements emptyElements)
+  {
+    return this->append(first, last, ExpandElements::Yes, emptyElements);
+  }
+  iterator append(const cmList& values,
+                  ExpandElements expandElements = ExpandElements::No,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    return this->append(values.begin(), values.end(), expandElements,
+                        emptyElements);
+  }
+  iterator append(const cmList& values, EmptyElements emptyElements)
+  {
+    return this->append(values, ExpandElements::No, emptyElements);
+  }
+  iterator append(cmList&& values,
+                  ExpandElements expandElements = ExpandElements::No,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    auto result = this->append(std::make_move_iterator(values.begin()),
+                               std::make_move_iterator(values.end()),
+                               expandElements, emptyElements);
+    values.clear();
+
+    return result;
+  }
+  iterator append(cmList&& values, EmptyElements emptyElements)
+  {
+    return this->append(std::move(values), ExpandElements::No, emptyElements);
+  }
+  iterator append(const container_type& values,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    return this->append(values.begin(), values.end(), expandElements,
+                        emptyElements);
+  }
+  iterator append(const container_type& values, EmptyElements emptyElements)
+  {
+    return this->append(values, ExpandElements::Yes, emptyElements);
+  }
+  iterator append(container_type&& values,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    auto result = this->append(std::make_move_iterator(values.begin()),
+                               std::make_move_iterator(values.end()),
+                               expandElements, emptyElements);
+    values.clear();
+
+    return result;
+  }
+  iterator append(container_type&& values, EmptyElements emptyElements)
+  {
+    return this->append(std::move(values), ExpandElements::Yes, emptyElements);
+  }
+  iterator append(std::initializer_list<std::string> ilist)
+  {
+    return this->insert(this->cend(), ilist);
+  }
+
+  iterator prepend(cm::string_view value,
+                   ExpandElements expandElements = ExpandElements::Yes,
+                   EmptyElements emptyElements = EmptyElements::No)
+  {
+    return this->insert(this->cbegin(), value, expandElements, emptyElements);
+  }
+  iterator prepend(cm::string_view value, EmptyElements emptyElements)
+  {
+    return this->prepend(value, ExpandElements::Yes, emptyElements);
+  }
+  iterator prepend(std::string const& value,
+                   ExpandElements expandElements = ExpandElements::Yes,
+                   EmptyElements emptyElements = EmptyElements::No)
+  {
+    return this->insert(this->cbegin(), value, expandElements, emptyElements);
+  }
+  iterator prepend(std::string const& value, EmptyElements emptyElements)
+  {
+    return this->prepend(value, ExpandElements::Yes, emptyElements);
+  }
+  iterator prepend(cmValue value,
+                   ExpandElements expandElements = ExpandElements::Yes,
+                   EmptyElements emptyElements = EmptyElements::No)
+  {
+    if (value) {
+      return this->prepend(*value, expandElements, emptyElements);
+    }
+
+    return this->begin();
+  }
+  iterator prepend(cmValue value, EmptyElements emptyElements)
+  {
+    return this->prepend(value, ExpandElements::Yes, emptyElements);
+  }
+  template <typename InputIterator>
+  iterator prepend(InputIterator first, InputIterator last,
+                   ExpandElements expandElements = ExpandElements::Yes,
+                   EmptyElements emptyElements = EmptyElements::No)
+  {
+    return this->insert(this->cbegin(), first, last, expandElements,
+                        emptyElements);
+  }
+  template <typename InputIterator>
+  iterator prepend(InputIterator first, InputIterator last,
+                   EmptyElements emptyElements)
+  {
+    return this->prepend(first, last, ExpandElements::Yes, emptyElements);
+  }
+  iterator prepend(const cmList& values,
+                   ExpandElements expandElements = ExpandElements::No,
+                   EmptyElements emptyElements = EmptyElements::No)
+  {
+    return this->prepend(values.begin(), values.end(), expandElements,
+                         emptyElements);
+  }
+  iterator prepend(const cmList& values, EmptyElements emptyElements)
+  {
+    return this->prepend(values, ExpandElements::No, emptyElements);
+  }
+  iterator prepend(cmList&& values,
+                   ExpandElements expandElements = ExpandElements::No,
+                   EmptyElements emptyElements = EmptyElements::No)
+  {
+    auto result = this->prepend(std::make_move_iterator(values.begin()),
+                                std::make_move_iterator(values.end()),
+                                expandElements, emptyElements);
+    values.clear();
+
+    return result;
+  }
+  iterator prepend(cmList&& values, EmptyElements emptyElements)
+  {
+    return this->prepend(std::move(values), ExpandElements::No, emptyElements);
+  }
+  iterator prepend(const container_type& values,
+                   ExpandElements expandElements = ExpandElements::Yes,
+                   EmptyElements emptyElements = EmptyElements::No)
+  {
+    return this->prepend(values.begin(), values.end(), expandElements,
+                         emptyElements);
+  }
+  iterator prepend(const container_type& values, EmptyElements emptyElements)
+  {
+    return this->prepend(values, ExpandElements::Yes, emptyElements);
+  }
+  iterator prepend(container_type&& values,
+                   ExpandElements expandElements = ExpandElements::Yes,
+                   EmptyElements emptyElements = EmptyElements::No)
+  {
+    auto result = this->prepend(std::make_move_iterator(values.begin()),
+                                std::make_move_iterator(values.end()),
+                                expandElements, emptyElements);
+    values.clear();
+
+    return result;
+  }
+  iterator prepend(container_type&& values, EmptyElements emptyElements)
+  {
+    return this->prepend(std::move(values), ExpandElements::Yes,
+                         emptyElements);
+  }
+  iterator prepend(std::initializer_list<std::string> ilist)
+  {
+    return this->insert(this->cbegin(), ilist);
+  }
+
+  void push_back(std::string const& value) { this->Values.push_back(value); }
+  void push_back(cm::string_view value)
+  {
+    this->Values.push_back(std::string{ value });
+  }
+  void push_back(std::string&& value)
+  {
+    this->Values.push_back(std::move(value));
+  }
+
+  template <typename... Args>
+  iterator emplace(const_iterator pos, Args&&... args)
+  {
+    return this->Values.emplace(pos, std::forward<Args>(args)...);
+  }
+
+  template <typename... Args>
+  void emplace_back(Args&&... args)
+  {
+    this->Values.emplace_back(std::forward<Args>(args)...);
+  }
+
+  // Inserts elements in the list
+  // Throw std::out_of_range if index is invalid
+  template <typename InputIterator>
+  cmList& insert_items(index_type index, InputIterator first,
+                       InputIterator last)
+  {
+    this->insert(this->begin() + this->ComputeInsertIndex(index), first, last);
+    return *this;
+  }
+  cmList& insert_items(index_type index,
+                       std::initializer_list<std::string> values)
+  {
+    return this->insert_items(index, values.begin(), values.end());
+  }
+
+  iterator erase(const_iterator pos)
+  {
+    // convert const_iterator in iterator to please non standard c++11
+    // compilers (gcc 4.8 for example)
+    auto pos2 =
+      this->Values.begin() + std::distance(this->Values.cbegin(), pos);
+    return this->Values.erase(pos2);
+  }
+  iterator erase(const_iterator first, const_iterator last)
+  {
+    // convert const_iterator in iterator to please non standard c++11
+    // compilers (gcc 4.8 for example)
+    auto first2 =
+      this->Values.begin() + std::distance(this->Values.cbegin(), first);
+    auto last2 =
+      this->Values.begin() + std::distance(this->Values.cbegin(), last);
+    return this->Values.erase(first2, last2);
+  }
+
+  void pop_back() { this->Values.pop_back(); }
+  void pop_front() { this->Values.erase(this->begin()); }
+
+  // Removes elements from the list
+  // Throw std::out_of_range if any index is invalid
+  cmList& remove_items(std::initializer_list<index_type> indexes)
+  {
+    return this->RemoveItems(
+      std::vector<index_type>{ indexes.begin(), indexes.end() });
+  }
+  cmList& remove_items(std::initializer_list<std::string> values)
+  {
+    return this->RemoveItems(
+      std::vector<std::string>{ values.begin(), values.end() });
+  }
+  template <typename InputIterator>
+  cmList& remove_items(InputIterator first, InputIterator last)
+  {
+    return this->RemoveItems(
+      std::vector<typename InputIterator::value_type>{ first, last });
+  }
+
+  cmList& remove_duplicates();
+
+  void resize(size_type count) { this->Values.resize(count); }
+
+  enum class FilterMode
+  {
+    INCLUDE,
+    EXCLUDE
+  };
+  // Includes or removes items from the list
+  // Throw std::invalid_argument if regular expression is invalid
+  cmList& filter(cm::string_view regex, FilterMode mode);
+
+  cmList& reverse()
+  {
+    std::reverse(this->Values.begin(), this->Values.end());
+    return *this;
+  }
+
+  struct SortConfiguration
+  {
+    enum class OrderMode
+    {
+      DEFAULT,
+      ASCENDING,
+      DESCENDING,
+    } Order = OrderMode::DEFAULT;
+
+    enum class CompareMethod
+    {
+      DEFAULT,
+      STRING,
+      FILE_BASENAME,
+      NATURAL,
+    } Compare = CompareMethod::DEFAULT;
+
+    enum class CaseSensitivity
+    {
+      DEFAULT,
+      SENSITIVE,
+      INSENSITIVE,
+    } Case = CaseSensitivity::DEFAULT;
+
+    // declare the default constructor to work-around clang bug
+    SortConfiguration();
+
+    SortConfiguration(OrderMode order, CompareMethod compare,
+                      CaseSensitivity caseSensitivity)
+      : Order(order)
+      , Compare(compare)
+      , Case(caseSensitivity)
+    {
+    }
+  };
+  cmList& sort(const SortConfiguration& config = SortConfiguration{});
+
+  // exception raised on error during transform operations
+  class transform_error : public std::runtime_error
+  {
+  public:
+    transform_error(const std::string& error)
+      : std::runtime_error(error)
+    {
+    }
+  };
+
+  class TransformSelector
+  {
+  public:
+    using index_type = cmList::index_type;
+
+    // define some structs used as template selector
+    struct AT;
+    struct FOR;
+    struct REGEX;
+
+    virtual ~TransformSelector() = default;
+
+    virtual const std::string& GetTag() = 0;
+
+    // method NEW is used to allocate a selector of the needed type.
+    // For example:
+    // cmList::TransformSelector::New<AT>({1, 2, 5, 6});
+    //  or
+    // cmList::TransformSelector::New<REGEX>("^XX.*");
+    template <typename Type>
+    static std::unique_ptr<TransformSelector> New(
+      std::initializer_list<index_type>);
+    template <typename Type>
+    static std::unique_ptr<TransformSelector> New(
+      std::vector<index_type> const&);
+    template <typename Type>
+    static std::unique_ptr<TransformSelector> New(std::vector<index_type>&&);
+
+    template <typename Type>
+    static std::unique_ptr<TransformSelector> New(std::string const&);
+    template <typename Type>
+    static std::unique_ptr<TransformSelector> New(std::string&&);
+
+  private:
+    static std::unique_ptr<TransformSelector> NewAT(
+      std::initializer_list<index_type> init);
+    static std::unique_ptr<TransformSelector> NewAT(
+      std::vector<index_type> const& init);
+    static std::unique_ptr<TransformSelector> NewAT(
+      std::vector<index_type>&& init);
+
+    static std::unique_ptr<TransformSelector> NewFOR(
+      std::initializer_list<index_type> init);
+    static std::unique_ptr<TransformSelector> NewFOR(
+      std::vector<index_type> const& init);
+    static std::unique_ptr<TransformSelector> NewFOR(
+      std::vector<index_type>&& init);
+
+    static std::unique_ptr<TransformSelector> NewREGEX(
+      std::string const& init);
+    static std::unique_ptr<TransformSelector> NewREGEX(std::string&& init);
+  };
+
+  enum class TransformAction
+  {
+    APPEND,
+    PREPEND,
+    TOLOWER,
+    TOUPPER,
+    STRIP,
+    GENEX_STRIP,
+    REPLACE
+  };
+
+  // Transforms the list by applying an action
+  // Throw std::transform_error is any of arguments specified are invalid
+  cmList& transform(TransformAction action,
+                    std::unique_ptr<TransformSelector> = {});
+  cmList& transform(TransformAction action, std::string const& arg,
+                    std::unique_ptr<TransformSelector> = {});
+  cmList& transform(TransformAction action, std::string const& arg1,
+                    std::string const& arg2,
+                    std::unique_ptr<TransformSelector> = {});
+  cmList& transform(TransformAction action,
+                    std::vector<std::string> const& args,
+                    std::unique_ptr<TransformSelector> = {});
+
+  std::string join(cm::string_view glue) const;
+
+  void swap(cmList& other) noexcept { this->Values.swap(other.Values); }
+
+  // static members
+  // ==============
+  // these methods can be used to store CMake list expansion directly in a
+  // std::vector.
+  static void assign(std::vector<std::string>& container,
+                     cm::string_view value,
+                     EmptyElements emptyElements = EmptyElements::No)
+  {
+    container.clear();
+    cmList::append(container, value, emptyElements);
+  }
+  static void assign(std::vector<std::string>& container,
+                     std::string const& value,
+                     EmptyElements emptyElements = EmptyElements::No)
+  {
+    container.clear();
+    cmList::append(container, value, emptyElements);
+  }
+  static void assign(std::vector<std::string>& container, cmValue value,
+                     EmptyElements emptyElements = EmptyElements::No)
+  {
+    if (value) {
+      cmList::assign(container, *value, emptyElements);
+    } else {
+      container.clear();
+    }
+  }
+  template <typename InputIterator>
+  static void assign(std::vector<std::string>& container, InputIterator first,
+                     InputIterator last,
+                     EmptyElements emptyElements = EmptyElements::No)
+  {
+    container.clear();
+    cmList::append(container, first, last, emptyElements);
+  }
+
+  static std::vector<std::string>::iterator insert(
+    std::vector<std::string>& container,
+    std::vector<std::string>::const_iterator pos, cm::string_view value,
+    EmptyElements emptyElements = EmptyElements::No)
+  {
+    return cmList::Insert(container, pos, std::string(value),
+                          ExpandElements::Yes, emptyElements);
+  }
+  static std::vector<std::string>::iterator insert(
+    std::vector<std::string>& container,
+    std::vector<std::string>::const_iterator pos, std::string const& value,
+    EmptyElements emptyElements = EmptyElements::No)
+  {
+    return cmList::Insert(container, pos, value, ExpandElements::Yes,
+                          emptyElements);
+  }
+  static std::vector<std::string>::iterator insert(
+    std::vector<std::string>& container,
+    std::vector<std::string>::const_iterator pos, cmValue value,
+    EmptyElements emptyElements = EmptyElements::No)
+  {
+    if (value) {
+      return cmList::insert(container, pos, *value, emptyElements);
+    }
+
+    auto delta = std::distance(container.cbegin(), pos);
+    return container.begin() + delta;
+  }
+  template <typename InputIterator>
+  static std::vector<std::string>::iterator insert(
+    std::vector<std::string>& container,
+    std::vector<std::string>::const_iterator pos, InputIterator first,
+    InputIterator last, EmptyElements emptyElements = EmptyElements::No)
+  {
+    return cmList::Insert(container, pos, first, last, ExpandElements::Yes,
+                          emptyElements);
+  }
+
+  static std::vector<std::string>::iterator append(
+    std::vector<std::string>& container, cm::string_view value,
+    EmptyElements emptyElements = EmptyElements::No)
+  {
+    return cmList::insert(container, container.cend(), value, emptyElements);
+  }
+  static std::vector<std::string>::iterator append(
+    std::vector<std::string>& container, std::string const& value,
+    EmptyElements emptyElements = EmptyElements::No)
+  {
+    return cmList::insert(container, container.cend(), value, emptyElements);
+  }
+  static std::vector<std::string>::iterator append(
+    std::vector<std::string>& container, cmValue value,
+    EmptyElements emptyElements = EmptyElements::No)
+  {
+    if (value) {
+      return cmList::append(container, *value, emptyElements);
+    }
+
+    return container.end();
+  }
+  template <typename InputIterator>
+  static std::vector<std::string>::iterator append(
+    std::vector<std::string>& container, InputIterator first,
+    InputIterator last, EmptyElements emptyElements = EmptyElements::No)
+  {
+    return cmList::insert(container, container.cend(), first, last,
+                          emptyElements);
+  }
+
+  static std::vector<std::string>::iterator prepend(
+    std::vector<std::string>& container, cm::string_view value,
+    EmptyElements emptyElements = EmptyElements::No)
+  {
+    return cmList::insert(container, container.cbegin(), value, emptyElements);
+  }
+  static std::vector<std::string>::iterator prepend(
+    std::vector<std::string>& container, std::string const& value,
+    EmptyElements emptyElements = EmptyElements::No)
+  {
+    return cmList::insert(container, container.cbegin(), value, emptyElements);
+  }
+  static std::vector<std::string>::iterator prepend(
+    std::vector<std::string>& container, cmValue value,
+    EmptyElements emptyElements = EmptyElements::No)
+  {
+    if (value) {
+      return cmList::prepend(container, *value, emptyElements);
+    }
+
+    return container.begin();
+  }
+  template <typename InputIterator>
+  static std::vector<std::string>::iterator prepend(
+    std::vector<std::string>& container, InputIterator first,
+    InputIterator last, EmptyElements emptyElements = EmptyElements::No)
+  {
+    return cmList::insert(container, container.cbegin(), first, last,
+                          emptyElements);
+  }
+
+  // The following methods offer the possibility to extend a CMake list
+  // but without any intermediate expansion. So the operation is simply a
+  // string concatenation with special handling for the CMake list item
+  // separator
+  static std::string& append(std::string& list, cm::string_view value);
+  template <typename InputIterator>
+  static std::string& append(std::string& list, InputIterator first,
+                             InputIterator last)
+  {
+    if (first == last) {
+      return list;
+    }
+
+    return cmList::append(list,
+                          cm::string_view{ std::accumulate(
+                            std::next(first), last, *first,
+                            [](std::string a, const std::string& b) {
+                              return std::move(a) +
+                                std::string(cmList::element_separator) + b;
+                            }) });
+  }
+
+  static std::string& prepend(std::string& list, cm::string_view value);
+  template <typename InputIterator>
+  static std::string& prepend(std::string& list, InputIterator first,
+                              InputIterator last)
+  {
+    if (first == last) {
+      return list;
+    }
+
+    return cmList::prepend(list,
+                           cm::string_view{ std::accumulate(
+                             std::next(first), last, *first,
+                             [](std::string a, const std::string& b) {
+                               return std::move(a) +
+                                 std::string(cmList::element_separator) + b;
+                             }) });
+  }
+
+  // Non-members
+  // ===========
+  friend inline bool operator==(const cmList& lhs, const cmList& rhs) noexcept
+  {
+    return lhs.Values == rhs.Values;
+  }
+  friend inline bool operator!=(const cmList& lhs, const cmList& rhs) noexcept
+  {
+    return lhs.Values != rhs.Values;
+  }
+
+private:
+  size_type ComputeIndex(index_type pos, bool boundCheck = true) const;
+  size_type ComputeInsertIndex(index_type pos, bool boundCheck = true) const;
+
+  cmList GetItems(std::vector<index_type>&& indexes) const;
+
+  cmList& RemoveItems(std::vector<index_type>&& indexes);
+  cmList& RemoveItems(std::vector<std::string>&& items);
+
+  static container_type::iterator Insert(container_type& container,
+                                         container_type::const_iterator pos,
+                                         std::string&& value,
+                                         ExpandElements expandElements,
+                                         EmptyElements emptyElements);
+  static container_type::iterator Insert(container_type& container,
+                                         container_type::const_iterator pos,
+                                         const std::string& value,
+                                         ExpandElements expandElements,
+                                         EmptyElements emptyElements)
+  {
+    auto tmp = value;
+    return cmList::Insert(container, pos, std::move(tmp), expandElements,
+                          emptyElements);
+  }
+  template <typename InputIterator>
+  static container_type::iterator Insert(container_type& container,
+                                         container_type::const_iterator pos,
+                                         InputIterator first,
+                                         InputIterator last,
+                                         ExpandElements expandElements,
+                                         EmptyElements emptyElements)
+  {
+    auto delta = std::distance(container.cbegin(), pos);
+
+    if (first == last) {
+      return container.begin() + delta;
+    }
+
+    auto insertPos = container.begin() + delta;
+    if (expandElements == ExpandElements::Yes) {
+      for (; first != last; ++first) {
+        auto size = container.size();
+        insertPos = cmList::Insert(container, insertPos, *first,
+                                   expandElements, emptyElements);
+        insertPos += container.size() - size;
+      }
+    } else {
+      for (; first != last; ++first) {
+        if (!first->empty() || emptyElements == EmptyElements::Yes) {
+          insertPos = container.insert(insertPos, *first);
+          insertPos++;
+        }
+      }
+    }
+
+    return container.begin() + delta;
+  }
+
+  container_type Values;
+};
+
+// specializations for cmList::TransformSelector allocators
+// ========================================================
+template <>
+inline std::unique_ptr<cmList::TransformSelector>
+cmList::TransformSelector::New<cmList::TransformSelector::AT>(
+  std::initializer_list<index_type> init)
+{
+  return cmList::TransformSelector::NewAT(init);
+}
+template <>
+inline std::unique_ptr<cmList::TransformSelector>
+cmList::TransformSelector::New<cmList::TransformSelector::AT>(
+  std::vector<index_type> const& init)
+{
+  return cmList::TransformSelector::NewAT(init);
+}
+template <>
+inline std::unique_ptr<cmList::TransformSelector>
+cmList::TransformSelector::New<cmList::TransformSelector::AT>(
+  std::vector<index_type>&& init)
+{
+  return cmList::TransformSelector::NewAT(std::move(init));
+}
+
+template <>
+inline std::unique_ptr<cmList::TransformSelector>
+cmList::TransformSelector::New<cmList::TransformSelector::FOR>(
+  std::initializer_list<index_type> init)
+{
+  return cmList::TransformSelector::NewFOR(init);
+}
+template <>
+inline std::unique_ptr<cmList::TransformSelector>
+cmList::TransformSelector::New<cmList::TransformSelector::FOR>(
+  std::vector<index_type> const& init)
+{
+  return cmList::TransformSelector::NewFOR(init);
+}
+template <>
+inline std::unique_ptr<cmList::TransformSelector>
+cmList::TransformSelector::New<cmList::TransformSelector::FOR>(
+  std::vector<index_type>&& init)
+{
+  return cmList::TransformSelector::NewFOR(std::move(init));
+}
+
+template <>
+inline std::unique_ptr<cmList::TransformSelector>
+cmList::TransformSelector::New<cmList::TransformSelector::REGEX>(
+  std::string const& init)
+{
+  return cmList::TransformSelector::NewREGEX(init);
+}
+template <>
+inline std::unique_ptr<cmList::TransformSelector>
+cmList::TransformSelector::New<cmList::TransformSelector::REGEX>(
+  std::string&& init)
+{
+  return cmList::TransformSelector::NewREGEX(std::move(init));
+}
+
+// Non-member functions
+// ====================
+inline std::vector<std::string>& operator+=(std::vector<std::string>& l,
+                                            const cmList& r)
+{
+  l.insert(l.end(), r.begin(), r.end());
+  return l;
+}
+inline std::vector<std::string>& operator+=(std::vector<std::string>& l,
+                                            cmList&& r)
+{
+  std::move(r.begin(), r.end(), std::back_inserter(l));
+  r.clear();
+
+  return l;
+}
+
+namespace std {
+inline void swap(cmList& lhs, cmList& rhs) noexcept
+{
+  lhs.swap(rhs);
+}
+} // namespace std
+
+namespace cm {
+inline void erase(cmList& list, const std::string& value)
+{
+  list.erase(std::remove(list.begin(), list.end(), value), list.end());
+}
+
+template <typename Predicate>
+inline void erase_if(cmList& list, Predicate pred)
+{
+  list.erase(std::remove_if(list.begin(), list.end(), pred), list.end());
+}
+
+//
+// Provide a special implementation of cm::append because, in this case,
+// expansion must not be applied to the inserted elements
+//
+#if defined(__SUNPRO_CC) && defined(__sparc)
+// Oracle DeveloperStudio C++ compiler on Solaris/Sparc fails to compile
+// templates with constraints.
+// So, on this platform, use only simple templates.
+template <typename InputIt,
+          cm::enable_if_t<cm::is_input_iterator<InputIt>::value, int> = 0>
+void append(cmList& v, InputIt first, InputIt last)
+{
+  v.append(first, last, cmList::ExpandElements::No);
+}
+
+template <typename Range,
+          cm::enable_if_t<cm::is_input_range<Range>::value, int> = 0>
+void append(cmList& v, Range const& r)
+{
+  v.append(r.begin(), r.end(), cmList::ExpandElements::No);
+}
+
+#else
+
+template <
+  typename InputIt,
+  cm::enable_if_t<
+    cm::is_input_iterator<InputIt>::value &&
+      std::is_convertible<typename std::iterator_traits<InputIt>::value_type,
+                          cmList::value_type>::value,
+    int> = 0>
+void append(cmList& v, InputIt first, InputIt last)
+{
+  v.append(first, last, cmList::ExpandElements::No);
+}
+
+template <typename Range,
+          cm::enable_if_t<cm::is_input_range<Range>::value &&
+                            std::is_convertible<typename Range::value_type,
+                                                cmList::value_type>::value,
+                          int> = 0>
+void append(cmList& v, Range const& r)
+{
+  v.append(r.begin(), r.end(), cmList::ExpandElements::No);
+}
+#endif
+
+} // namespace cm
+
+/**
+ * Helper functions for legacy support. Use preferably cmList class directly
+ * or the static methods of the class.
+ */
+inline void cmExpandList(
+  cm::string_view arg, std::vector<std::string>& argsOut,
+  cmList::EmptyElements emptyElements = cmList::EmptyElements::No)
+{
+  cmList::append(argsOut, arg, emptyElements);
+}
+inline void cmExpandList(
+  cmValue arg, std::vector<std::string>& argsOut,
+  cmList::EmptyElements emptyElements = cmList::EmptyElements::No)
+{
+  cmList::append(argsOut, arg, emptyElements);
+}
diff --git a/Source/cmListCommand.cxx b/Source/cmListCommand.cxx
index d412534..acffa2e 100644
--- a/Source/cmListCommand.cxx
+++ b/Source/cmListCommand.cxx
@@ -2,11 +2,9 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmListCommand.h"
 
-#include <algorithm>
 #include <cassert>
 #include <cstdio>
 #include <functional>
-#include <iterator>
 #include <set>
 #include <sstream>
 #include <stdexcept>
@@ -14,22 +12,18 @@
 #include <vector>
 
 #include <cm/memory>
+#include <cm/optional>
 #include <cmext/algorithm>
 #include <cmext/string_view>
 
-#include "cmsys/RegularExpression.hxx"
-
-#include "cmAlgorithms.h"
 #include "cmExecutionStatus.h"
-#include "cmGeneratorExpression.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
 #include "cmPolicies.h"
 #include "cmRange.h"
 #include "cmStringAlgorithms.h"
-#include "cmStringReplaceHelper.h"
 #include "cmSubcommandTable.h"
-#include "cmSystemTools.h"
 #include "cmValue.h"
 
 namespace {
@@ -70,11 +64,6 @@
   return true;
 }
 
-bool FilterRegex(std::vector<std::string> const& args, bool includeMatches,
-                 std::string const& listName,
-                 std::vector<std::string>& varArgsExpanded,
-                 cmExecutionStatus& status);
-
 bool GetListString(std::string& listString, const std::string& var,
                    const cmMakefile& makefile)
 {
@@ -87,22 +76,25 @@
   return true;
 }
 
-bool GetList(std::vector<std::string>& list, const std::string& var,
-             const cmMakefile& makefile)
+cm::optional<cmList> GetList(const std::string& var,
+                             const cmMakefile& makefile)
 {
+  cm::optional<cmList> list;
+
   std::string listString;
   if (!GetListString(listString, var, makefile)) {
-    return false;
+    return list;
   }
   // if the size of the list
   if (listString.empty()) {
-    return true;
+    list.emplace();
+    return list;
   }
   // expand the variable into a list
-  cmExpandList(listString, list, true);
+  list.emplace(listString, cmList::EmptyElements::Yes);
   // if no empty elements then just return
-  if (!cm::contains(list, std::string())) {
-    return true;
+  if (!cm::contains(*list, std::string())) {
+    return list;
   }
   // if we have empty elements we need to check policy CMP0007
   switch (makefile.GetPolicyStatus(cmPolicies::CMP0007)) {
@@ -111,31 +103,29 @@
       // OLD behavior is to allow compatibility, so recall
       // ExpandListArgument without the true which will remove
       // empty values
-      list.clear();
-      cmExpandList(listString, list);
+      list->assign(listString);
       std::string warn =
         cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0007),
                  " List has value = [", listString, "].");
       makefile.IssueMessage(MessageType::AUTHOR_WARNING, warn);
-      return true;
+      return list;
     }
     case cmPolicies::OLD:
       // OLD behavior is to allow compatibility, so recall
       // ExpandListArgument without the true which will remove
       // empty values
-      list.clear();
-      cmExpandList(listString, list);
-      return true;
+      list->assign(listString);
+      return list;
     case cmPolicies::NEW:
-      return true;
+      return list;
     case cmPolicies::REQUIRED_IF_USED:
     case cmPolicies::REQUIRED_ALWAYS:
       makefile.IssueMessage(
         MessageType::FATAL_ERROR,
         cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0007));
-      return false;
+      return {};
   }
-  return true;
+  return list;
 }
 
 bool HandleLengthCommand(std::vector<std::string> const& args,
@@ -148,16 +138,11 @@
 
   const std::string& listName = args[1];
   const std::string& variableName = args.back();
-  std::vector<std::string> varArgsExpanded;
-  // do not check the return value here
-  // if the list var is not found varArgsExpanded will have size 0
-  // and we will return 0
-  GetList(varArgsExpanded, listName, status.GetMakefile());
-  size_t length = varArgsExpanded.size();
-  char buffer[1024];
-  snprintf(buffer, sizeof(buffer), "%d", static_cast<int>(length));
 
-  status.GetMakefile().AddDefinition(variableName, buffer);
+  auto list = GetList(listName, status.GetMakefile());
+  status.GetMakefile().AddDefinition(variableName,
+                                     std::to_string(list ? list->size() : 0));
+
   return true;
 }
 
@@ -172,42 +157,35 @@
   const std::string& listName = args[1];
   const std::string& variableName = args.back();
   // expand the variable
-  std::vector<std::string> varArgsExpanded;
-  if (!GetList(varArgsExpanded, listName, status.GetMakefile())) {
+  auto list = GetList(listName, status.GetMakefile());
+  if (!list) {
     status.GetMakefile().AddDefinition(variableName, "NOTFOUND");
     return true;
   }
   // FIXME: Add policy to make non-existing lists an error like empty lists.
-  if (varArgsExpanded.empty()) {
+  if (list->empty()) {
     status.SetError("GET given empty list");
     return false;
   }
 
-  std::string value;
-  size_t cc;
-  const char* sep = "";
-  size_t nitem = varArgsExpanded.size();
-  for (cc = 2; cc < args.size() - 1; cc++) {
-    int item;
-    if (!GetIndexArg(args[cc], &item, status.GetMakefile())) {
+  std::vector<int> indexes;
+  for (std::size_t cc = 2; cc < args.size() - 1; cc++) {
+    int index;
+    if (!GetIndexArg(args[cc], &index, status.GetMakefile())) {
       status.SetError(cmStrCat("index: ", args[cc], " is not a valid index"));
       return false;
     }
-    value += sep;
-    sep = ";";
-    if (item < 0) {
-      item = static_cast<int>(nitem) + item;
-    }
-    if (item < 0 || nitem <= static_cast<size_t>(item)) {
-      status.SetError(cmStrCat("index: ", item, " out of range (-", nitem,
-                               ", ", nitem - 1, ")"));
-      return false;
-    }
-    value += varArgsExpanded[item];
+    indexes.push_back(index);
   }
 
-  status.GetMakefile().AddDefinition(variableName, value);
-  return true;
+  try {
+    auto values = list->get_items(indexes.begin(), indexes.end());
+    status.GetMakefile().AddDefinition(variableName, values.to_string());
+    return true;
+  } catch (std::out_of_range& e) {
+    status.SetError(e.what());
+    return false;
+  }
 }
 
 bool HandleAppendCommand(std::vector<std::string> const& args,
@@ -226,13 +204,8 @@
   std::string listString;
   GetListString(listString, listName, makefile);
 
-  // If `listString` or `args` is empty, no need to append `;`,
-  // then index is going to be `1` and points to the end-of-string ";"
-  auto const offset =
-    static_cast<std::string::size_type>(listString.empty() || args.empty());
-  listString += &";"[offset] + cmJoin(cmMakeRange(args).advance(2), ";");
-
-  makefile.AddDefinition(listName, listString);
+  makefile.AddDefinition(
+    listName, cmList::append(listString, args.begin() + 2, args.end()));
   return true;
 }
 
@@ -252,14 +225,8 @@
   std::string listString;
   GetListString(listString, listName, makefile);
 
-  // If `listString` or `args` is empty, no need to append `;`,
-  // then `offset` is going to be `1` and points to the end-of-string ";"
-  auto const offset =
-    static_cast<std::string::size_type>(listString.empty() || args.empty());
-  listString.insert(0,
-                    cmJoin(cmMakeRange(args).advance(2), ";") + &";"[offset]);
-
-  makefile.AddDefinition(listName, listString);
+  makefile.AddDefinition(
+    listName, cmList::prepend(listString, args.begin() + 2, args.end()));
   return true;
 }
 
@@ -272,8 +239,9 @@
   auto ai = args.cbegin();
   ++ai; // Skip subcommand name
   std::string const& listName = *ai++;
-  std::vector<std::string> varArgsExpanded;
-  if (!GetList(varArgsExpanded, listName, makefile)) {
+  auto list = GetList(listName, makefile);
+
+  if (!list) {
     // Can't get the list definition... undefine any vars given after.
     for (; ai != args.cend(); ++ai) {
       makefile.RemoveDefinition(*ai);
@@ -281,16 +249,16 @@
     return true;
   }
 
-  if (!varArgsExpanded.empty()) {
+  if (!list->empty()) {
     if (ai == args.cend()) {
       // No variables are given... Just remove one element.
-      varArgsExpanded.pop_back();
+      list->pop_back();
     } else {
       // Ok, assign elements to be removed to the given variables
-      for (; !varArgsExpanded.empty() && ai != args.cend(); ++ai) {
+      for (; !list->empty() && ai != args.cend(); ++ai) {
         assert(!ai->empty());
-        makefile.AddDefinition(*ai, varArgsExpanded.back());
-        varArgsExpanded.pop_back();
+        makefile.AddDefinition(*ai, list->back());
+        list->pop_back();
       }
       // Undefine the rest variables if the list gets empty earlier...
       for (; ai != args.cend(); ++ai) {
@@ -298,7 +266,7 @@
       }
     }
 
-    makefile.AddDefinition(listName, cmJoin(varArgsExpanded, ";"));
+    makefile.AddDefinition(listName, list->to_string());
 
   } else if (ai !=
              args.cend()) { // The list is empty, but some args were given
@@ -320,8 +288,9 @@
   auto ai = args.cbegin();
   ++ai; // Skip subcommand name
   std::string const& listName = *ai++;
-  std::vector<std::string> varArgsExpanded;
-  if (!GetList(varArgsExpanded, listName, makefile)) {
+  auto list = GetList(listName, makefile);
+
+  if (!list) {
     // Can't get the list definition... undefine any vars given after.
     for (; ai != args.cend(); ++ai) {
       makefile.RemoveDefinition(*ai);
@@ -329,25 +298,25 @@
     return true;
   }
 
-  if (!varArgsExpanded.empty()) {
+  if (!list->empty()) {
     if (ai == args.cend()) {
       // No variables are given... Just remove one element.
-      varArgsExpanded.erase(varArgsExpanded.begin());
+      list->pop_front();
     } else {
       // Ok, assign elements to be removed to the given variables
-      auto vi = varArgsExpanded.begin();
-      for (; vi != varArgsExpanded.end() && ai != args.cend(); ++ai, ++vi) {
+      auto vi = list->begin();
+      for (; vi != list->end() && ai != args.cend(); ++ai, ++vi) {
         assert(!ai->empty());
         makefile.AddDefinition(*ai, *vi);
       }
-      varArgsExpanded.erase(varArgsExpanded.begin(), vi);
+      list->erase(list->begin(), vi);
       // Undefine the rest variables if the list gets empty earlier...
       for (; ai != args.cend(); ++ai) {
         makefile.RemoveDefinition(*ai);
       }
     }
 
-    makefile.AddDefinition(listName, cmJoin(varArgsExpanded, ";"));
+    makefile.AddDefinition(listName, list->to_string());
 
   } else if (ai !=
              args.cend()) { // The list is empty, but some args were given
@@ -371,21 +340,16 @@
   const std::string& listName = args[1];
   const std::string& variableName = args.back();
   // expand the variable
-  std::vector<std::string> varArgsExpanded;
-  if (!GetList(varArgsExpanded, listName, status.GetMakefile())) {
+  auto list = GetList(listName, status.GetMakefile());
+
+  if (!list) {
     status.GetMakefile().AddDefinition(variableName, "-1");
     return true;
   }
 
-  auto it = std::find(varArgsExpanded.begin(), varArgsExpanded.end(), args[2]);
-  if (it != varArgsExpanded.end()) {
-    status.GetMakefile().AddDefinition(
-      variableName,
-      std::to_string(std::distance(varArgsExpanded.begin(), it)));
-    return true;
-  }
-
-  status.GetMakefile().AddDefinition(variableName, "-1");
+  auto index = list->find(args[2]);
+  status.GetMakefile().AddDefinition(
+    variableName, index == cmList::npos ? "-1" : std::to_string(index));
   return true;
 }
 
@@ -400,38 +364,24 @@
   const std::string& listName = args[1];
 
   // expand the variable
-  int item;
-  if (!GetIndexArg(args[2], &item, status.GetMakefile())) {
+  int index;
+  if (!GetIndexArg(args[2], &index, status.GetMakefile())) {
     status.SetError(cmStrCat("index: ", args[2], " is not a valid index"));
     return false;
   }
-  std::vector<std::string> varArgsExpanded;
-  if ((!GetList(varArgsExpanded, listName, status.GetMakefile()) ||
-       varArgsExpanded.empty()) &&
-      item != 0) {
-    status.SetError(cmStrCat("index: ", item, " out of range (0, 0)"));
+  auto list = GetList(listName, status.GetMakefile());
+  if (!list) {
+    list = cmList{};
+  }
+
+  try {
+    list->insert_items(index, args.begin() + 3, args.end());
+    status.GetMakefile().AddDefinition(listName, list->to_string());
+    return true;
+  } catch (std::out_of_range& e) {
+    status.SetError(e.what());
     return false;
   }
-
-  if (!varArgsExpanded.empty()) {
-    size_t nitem = varArgsExpanded.size();
-    if (item < 0) {
-      item = static_cast<int>(nitem) + item;
-    }
-    if (item < 0 || nitem < static_cast<size_t>(item)) {
-      status.SetError(cmStrCat("index: ", item, " out of range (-",
-                               varArgsExpanded.size(), ", ",
-                               varArgsExpanded.size(), ")"));
-      return false;
-    }
-  }
-
-  varArgsExpanded.insert(varArgsExpanded.begin() + item, args.begin() + 3,
-                         args.end());
-
-  std::string value = cmJoin(varArgsExpanded, ";");
-  status.GetMakefile().AddDefinition(listName, value);
-  return true;
 }
 
 bool HandleJoinCommand(std::vector<std::string> const& args,
@@ -448,16 +398,14 @@
   const std::string& variableName = args[3];
 
   // expand the variable
-  std::vector<std::string> varArgsExpanded;
-  if (!GetList(varArgsExpanded, listName, status.GetMakefile())) {
+  auto list = GetList(listName, status.GetMakefile());
+
+  if (!list) {
     status.GetMakefile().AddDefinition(variableName, "");
     return true;
   }
 
-  std::string value =
-    cmJoin(cmMakeRange(varArgsExpanded.begin(), varArgsExpanded.end()), glue);
-
-  status.GetMakefile().AddDefinition(variableName, value);
+  status.GetMakefile().AddDefinition(variableName, list->join(glue));
   return true;
 }
 
@@ -472,21 +420,14 @@
 
   const std::string& listName = args[1];
   // expand the variable
-  std::vector<std::string> varArgsExpanded;
-  if (!GetList(varArgsExpanded, listName, status.GetMakefile())) {
+  auto list = GetList(listName, status.GetMakefile());
+
+  if (!list) {
     return true;
   }
 
-  std::vector<std::string> remove(args.begin() + 2, args.end());
-  std::sort(remove.begin(), remove.end());
-  auto remEnd = std::unique(remove.begin(), remove.end());
-  auto remBegin = remove.begin();
-
-  auto argsEnd =
-    cmRemoveMatching(varArgsExpanded, cmMakeRange(remBegin, remEnd));
-  auto argsBegin = varArgsExpanded.cbegin();
-  std::string value = cmJoin(cmMakeRange(argsBegin, argsEnd), ";");
-  status.GetMakefile().AddDefinition(listName, value);
+  status.GetMakefile().AddDefinition(
+    listName, list->remove_items(args.begin() + 2, args.end()).to_string());
   return true;
 }
 
@@ -501,14 +442,13 @@
 
   const std::string& listName = args[1];
   // expand the variable
-  std::vector<std::string> varArgsExpanded;
-  if (!GetList(varArgsExpanded, listName, status.GetMakefile())) {
+  auto list = GetList(listName, status.GetMakefile());
+
+  if (!list) {
     return true;
   }
 
-  std::string value = cmJoin(cmReverseRange(varArgsExpanded), ";");
-
-  status.GetMakefile().AddDefinition(listName, value);
+  status.GetMakefile().AddDefinition(listName, list->reverse().to_string());
   return true;
 }
 
@@ -523,237 +463,17 @@
 
   const std::string& listName = args[1];
   // expand the variable
-  std::vector<std::string> varArgsExpanded;
-  if (!GetList(varArgsExpanded, listName, status.GetMakefile())) {
+  auto list = GetList(listName, status.GetMakefile());
+
+  if (!list) {
     return true;
   }
 
-  auto argsEnd = cmRemoveDuplicates(varArgsExpanded);
-  auto argsBegin = varArgsExpanded.cbegin();
-  std::string value = cmJoin(cmMakeRange(argsBegin, argsEnd), ";");
-
-  status.GetMakefile().AddDefinition(listName, value);
+  status.GetMakefile().AddDefinition(listName,
+                                     list->remove_duplicates().to_string());
   return true;
 }
 
-// Helpers for list(TRANSFORM <list> ...)
-using transform_type = std::function<std::string(const std::string&)>;
-
-class transform_error : public std::runtime_error
-{
-public:
-  transform_error(const std::string& error)
-    : std::runtime_error(error)
-  {
-  }
-};
-
-class TransformSelector
-{
-public:
-  virtual ~TransformSelector() = default;
-
-  std::string Tag;
-
-  virtual bool Validate(std::size_t count = 0) = 0;
-
-  virtual bool InSelection(const std::string&) = 0;
-
-  virtual void Transform(std::vector<std::string>& list,
-                         const transform_type& transform)
-  {
-    std::transform(list.begin(), list.end(), list.begin(), transform);
-  }
-
-protected:
-  TransformSelector(std::string&& tag)
-    : Tag(std::move(tag))
-  {
-  }
-};
-class TransformNoSelector : public TransformSelector
-{
-public:
-  TransformNoSelector()
-    : TransformSelector("NO SELECTOR")
-  {
-  }
-
-  bool Validate(std::size_t) override { return true; }
-
-  bool InSelection(const std::string&) override { return true; }
-};
-class TransformSelectorRegex : public TransformSelector
-{
-public:
-  TransformSelectorRegex(const std::string& regex)
-    : TransformSelector("REGEX")
-    , Regex(regex)
-  {
-  }
-
-  bool Validate(std::size_t) override { return this->Regex.is_valid(); }
-
-  bool InSelection(const std::string& value) override
-  {
-    return this->Regex.find(value);
-  }
-
-  cmsys::RegularExpression Regex;
-};
-class TransformSelectorIndexes : public TransformSelector
-{
-public:
-  std::vector<int> Indexes;
-
-  bool InSelection(const std::string&) override { return true; }
-
-  void Transform(std::vector<std::string>& list,
-                 const transform_type& transform) override
-  {
-    this->Validate(list.size());
-
-    for (auto index : this->Indexes) {
-      list[index] = transform(list[index]);
-    }
-  }
-
-protected:
-  TransformSelectorIndexes(std::string&& tag)
-    : TransformSelector(std::move(tag))
-  {
-  }
-  TransformSelectorIndexes(std::string&& tag, std::vector<int>&& indexes)
-    : TransformSelector(std::move(tag))
-    , Indexes(indexes)
-  {
-  }
-
-  int NormalizeIndex(int index, std::size_t count)
-  {
-    if (index < 0) {
-      index = static_cast<int>(count) + index;
-    }
-    if (index < 0 || count <= static_cast<std::size_t>(index)) {
-      throw transform_error(cmStrCat(
-        "sub-command TRANSFORM, selector ", this->Tag, ", index: ", index,
-        " out of range (-", count, ", ", count - 1, ")."));
-    }
-    return index;
-  }
-};
-class TransformSelectorAt : public TransformSelectorIndexes
-{
-public:
-  TransformSelectorAt(std::vector<int>&& indexes)
-    : TransformSelectorIndexes("AT", std::move(indexes))
-  {
-  }
-
-  bool Validate(std::size_t count) override
-  {
-    decltype(this->Indexes) indexes;
-
-    for (auto index : this->Indexes) {
-      indexes.push_back(this->NormalizeIndex(index, count));
-    }
-    this->Indexes = std::move(indexes);
-
-    return true;
-  }
-};
-class TransformSelectorFor : public TransformSelectorIndexes
-{
-public:
-  TransformSelectorFor(int start, int stop, int step)
-    : TransformSelectorIndexes("FOR")
-    , Start(start)
-    , Stop(stop)
-    , Step(step)
-  {
-  }
-
-  bool Validate(std::size_t count) override
-  {
-    this->Start = this->NormalizeIndex(this->Start, count);
-    this->Stop = this->NormalizeIndex(this->Stop, count);
-
-    // Does stepping move us further from the end?
-    if (this->Start > this->Stop) {
-      throw transform_error(
-        cmStrCat("sub-command TRANSFORM, selector FOR "
-                 "expects <start> to be no greater than <stop> (",
-                 this->Start, " > ", this->Stop, ")"));
-    }
-
-    // compute indexes
-    auto size = (this->Stop - this->Start + 1) / this->Step;
-    if ((this->Stop - this->Start + 1) % this->Step != 0) {
-      size += 1;
-    }
-
-    this->Indexes.resize(size);
-    auto start = this->Start;
-    auto step = this->Step;
-    std::generate(this->Indexes.begin(), this->Indexes.end(),
-                  [&start, step]() -> int {
-                    auto r = start;
-                    start += step;
-                    return r;
-                  });
-
-    return true;
-  }
-
-private:
-  int Start, Stop, Step;
-};
-
-class TransformAction
-{
-public:
-  virtual ~TransformAction() = default;
-
-  virtual std::string Transform(const std::string& input) = 0;
-};
-class TransformReplace : public TransformAction
-{
-public:
-  TransformReplace(const std::vector<std::string>& arguments,
-                   cmMakefile* makefile)
-    : ReplaceHelper(arguments[0], arguments[1], makefile)
-  {
-    makefile->ClearMatches();
-
-    if (!this->ReplaceHelper.IsRegularExpressionValid()) {
-      throw transform_error(
-        cmStrCat("sub-command TRANSFORM, action REPLACE: Failed to compile "
-                 "regex \"",
-                 arguments[0], "\"."));
-    }
-    if (!this->ReplaceHelper.IsReplaceExpressionValid()) {
-      throw transform_error(cmStrCat("sub-command TRANSFORM, action REPLACE: ",
-                                     this->ReplaceHelper.GetError(), "."));
-    }
-  }
-
-  std::string Transform(const std::string& input) override
-  {
-    // Scan through the input for all matches.
-    std::string output;
-
-    if (!this->ReplaceHelper.Replace(input, output)) {
-      throw transform_error(cmStrCat("sub-command TRANSFORM, action REPLACE: ",
-                                     this->ReplaceHelper.GetError(), "."));
-    }
-
-    return output;
-  }
-
-private:
-  cmStringReplaceHelper ReplaceHelper;
-};
-
 bool HandleTransformCommand(std::vector<std::string> const& args,
                             cmExecutionStatus& status)
 {
@@ -763,119 +483,50 @@
     return false;
   }
 
-  // Structure collecting all elements of the command
-  struct Command
-  {
-    Command(const std::string& listName)
-      : ListName(listName)
-      , OutputName(listName)
-    {
-    }
-
-    std::string Name;
-    std::string ListName;
-    std::vector<std::string> Arguments;
-    std::unique_ptr<TransformAction> Action;
-    std::unique_ptr<TransformSelector> Selector;
-    std::string OutputName;
-  } command(args[1]);
-
   // Descriptor of action
+  // Action: enum value identifying action
   // Arity: number of arguments required for the action
-  // Transform: lambda function implementing the action
   struct ActionDescriptor
   {
     ActionDescriptor(std::string name)
       : Name(std::move(name))
     {
     }
-    ActionDescriptor(std::string name, int arity, transform_type transform)
+    ActionDescriptor(std::string name, cmList::TransformAction action,
+                     int arity)
       : Name(std::move(name))
+      , Action(action)
       , Arity(arity)
-#if defined(__GNUC__) && __GNUC__ == 6 && defined(__aarch64__)
-      // std::function move constructor miscompiles on this architecture
-      , Transform(transform)
-#else
-      , Transform(std::move(transform))
-#endif
     {
     }
 
     operator const std::string&() const { return this->Name; }
 
     std::string Name;
+    cmList::TransformAction Action;
     int Arity = 0;
-    transform_type Transform;
   };
 
   // Build a set of supported actions.
   std::set<ActionDescriptor,
            std::function<bool(const std::string&, const std::string&)>>
-    descriptors(
-      [](const std::string& x, const std::string& y) { return x < y; });
-  descriptors = { { "APPEND", 1,
-                    [&command](const std::string& s) -> std::string {
-                      if (command.Selector->InSelection(s)) {
-                        return s + command.Arguments[0];
-                      }
+    descriptors{ { { "APPEND", cmList::TransformAction::APPEND, 1 },
+                   { "PREPEND", cmList::TransformAction::PREPEND, 1 },
+                   { "TOUPPER", cmList::TransformAction::TOUPPER, 0 },
+                   { "TOLOWER", cmList::TransformAction::TOLOWER, 0 },
+                   { "STRIP", cmList::TransformAction::STRIP, 0 },
+                   { "GENEX_STRIP", cmList::TransformAction::GENEX_STRIP, 0 },
+                   { "REPLACE", cmList::TransformAction::REPLACE, 2 } },
+                 [](const std::string& x, const std::string& y) {
+                   return x < y;
+                 } };
 
-                      return s;
-                    } },
-                  { "PREPEND", 1,
-                    [&command](const std::string& s) -> std::string {
-                      if (command.Selector->InSelection(s)) {
-                        return command.Arguments[0] + s;
-                      }
+  const std::string& listName = args[1];
 
-                      return s;
-                    } },
-                  { "TOUPPER", 0,
-                    [&command](const std::string& s) -> std::string {
-                      if (command.Selector->InSelection(s)) {
-                        return cmSystemTools::UpperCase(s);
-                      }
-
-                      return s;
-                    } },
-                  { "TOLOWER", 0,
-                    [&command](const std::string& s) -> std::string {
-                      if (command.Selector->InSelection(s)) {
-                        return cmSystemTools::LowerCase(s);
-                      }
-
-                      return s;
-                    } },
-                  { "STRIP", 0,
-                    [&command](const std::string& s) -> std::string {
-                      if (command.Selector->InSelection(s)) {
-                        return cmTrimWhitespace(s);
-                      }
-
-                      return s;
-                    } },
-                  { "GENEX_STRIP", 0,
-                    [&command](const std::string& s) -> std::string {
-                      if (command.Selector->InSelection(s)) {
-                        return cmGeneratorExpression::Preprocess(
-                          s,
-                          cmGeneratorExpression::StripAllGeneratorExpressions);
-                      }
-
-                      return s;
-                    } },
-                  { "REPLACE", 2,
-                    [&command](const std::string& s) -> std::string {
-                      if (command.Selector->InSelection(s)) {
-                        return command.Action->Transform(s);
-                      }
-
-                      return s;
-                    } } };
-
+  // Parse all possible function parameters
   using size_type = std::vector<std::string>::size_type;
   size_type index = 2;
 
-  // Parse all possible function parameters
   auto descriptor = descriptors.find(args[index]);
 
   if (descriptor == descriptors.end()) {
@@ -893,295 +544,184 @@
     return false;
   }
 
-  command.Name = descriptor->Name;
+  std::vector<std::string> arguments;
   index += descriptor->Arity;
   if (descriptor->Arity > 0) {
-    command.Arguments =
+    arguments =
       std::vector<std::string>(args.begin() + 3, args.begin() + index);
   }
 
-  if (command.Name == "REPLACE") {
-    try {
-      command.Action = cm::make_unique<TransformReplace>(
-        command.Arguments, &status.GetMakefile());
-    } catch (const transform_error& e) {
-      status.SetError(e.what());
-      return false;
-    }
-  }
-
   const std::string REGEX{ "REGEX" };
   const std::string AT{ "AT" };
   const std::string FOR{ "FOR" };
   const std::string OUTPUT_VARIABLE{ "OUTPUT_VARIABLE" };
+  std::unique_ptr<cmList::TransformSelector> selector;
+  std::string outputName = listName;
 
-  // handle optional arguments
-  while (args.size() > index) {
-    if ((args[index] == REGEX || args[index] == AT || args[index] == FOR) &&
-        command.Selector) {
-      status.SetError(
-        cmStrCat("sub-command TRANSFORM, selector already specified (",
-                 command.Selector->Tag, ")."));
-
-      return false;
-    }
-
-    // REGEX selector
-    if (args[index] == REGEX) {
-      if (args.size() == ++index) {
-        status.SetError("sub-command TRANSFORM, selector REGEX expects "
-                        "'regular expression' argument.");
-        return false;
-      }
-
-      command.Selector = cm::make_unique<TransformSelectorRegex>(args[index]);
-      if (!command.Selector->Validate()) {
+  try {
+    // handle optional arguments
+    while (args.size() > index) {
+      if ((args[index] == REGEX || args[index] == AT || args[index] == FOR) &&
+          selector) {
         status.SetError(
-          cmStrCat("sub-command TRANSFORM, selector REGEX failed to compile "
-                   "regex \"",
-                   args[index], "\"."));
+          cmStrCat("sub-command TRANSFORM, selector already specified (",
+                   selector->GetTag(), ")."));
+
         return false;
       }
 
-      index += 1;
-      continue;
-    }
+      // REGEX selector
+      if (args[index] == REGEX) {
+        if (args.size() == ++index) {
+          status.SetError("sub-command TRANSFORM, selector REGEX expects "
+                          "'regular expression' argument.");
+          return false;
+        }
 
-    // AT selector
-    if (args[index] == AT) {
-      // get all specified indexes
-      std::vector<int> indexes;
-      while (args.size() > ++index) {
-        std::size_t pos;
-        int value;
+        selector =
+          cmList::TransformSelector::New<cmList::TransformSelector::REGEX>(
+            args[index]);
 
-        try {
-          value = std::stoi(args[index], &pos);
-          if (pos != args[index].length()) {
+        index += 1;
+        continue;
+      }
+
+      // AT selector
+      if (args[index] == AT) {
+        // get all specified indexes
+        std::vector<cmList::index_type> indexes;
+        while (args.size() > ++index) {
+          std::size_t pos;
+          int value;
+
+          try {
+            value = std::stoi(args[index], &pos);
+            if (pos != args[index].length()) {
+              // this is not a number, stop processing
+              break;
+            }
+            indexes.push_back(value);
+          } catch (const std::invalid_argument&) {
             // this is not a number, stop processing
             break;
           }
-          indexes.push_back(value);
-        } catch (const std::invalid_argument&) {
-          // this is not a number, stop processing
-          break;
         }
-      }
 
-      if (indexes.empty()) {
-        status.SetError(
-          "sub-command TRANSFORM, selector AT expects at least one "
-          "numeric value.");
-        return false;
-      }
-
-      command.Selector =
-        cm::make_unique<TransformSelectorAt>(std::move(indexes));
-
-      continue;
-    }
-
-    // FOR selector
-    if (args[index] == FOR) {
-      if (args.size() <= ++index + 1) {
-        status.SetError(
-          "sub-command TRANSFORM, selector FOR expects, at least,"
-          " two arguments.");
-        return false;
-      }
-
-      int start = 0;
-      int stop = 0;
-      int step = 1;
-      bool valid = true;
-      try {
-        std::size_t pos;
-
-        start = std::stoi(args[index], &pos);
-        if (pos != args[index].length()) {
-          // this is not a number
-          valid = false;
-        } else {
-          stop = std::stoi(args[++index], &pos);
-          if (pos != args[index].length()) {
-            // this is not a number
-            valid = false;
-          }
+        if (indexes.empty()) {
+          status.SetError(
+            "sub-command TRANSFORM, selector AT expects at least one "
+            "numeric value.");
+          return false;
         }
-      } catch (const std::invalid_argument&) {
-        // this is not numbers
-        valid = false;
+
+        selector =
+          cmList::TransformSelector::New<cmList::TransformSelector::AT>(
+            std::move(indexes));
+
+        continue;
       }
-      if (!valid) {
-        status.SetError("sub-command TRANSFORM, selector FOR expects, "
-                        "at least, two numeric values.");
-        return false;
-      }
-      // try to read a third numeric value for step
-      if (args.size() > ++index) {
+
+      // FOR selector
+      if (args[index] == FOR) {
+        if (args.size() <= ++index + 1) {
+          status.SetError(
+            "sub-command TRANSFORM, selector FOR expects, at least,"
+            " two arguments.");
+          return false;
+        }
+
+        cmList::index_type start = 0;
+        cmList::index_type stop = 0;
+        cmList::index_type step = 1;
+        bool valid = true;
         try {
           std::size_t pos;
 
-          step = std::stoi(args[index], &pos);
+          start = std::stoi(args[index], &pos);
           if (pos != args[index].length()) {
             // this is not a number
-            step = 1;
+            valid = false;
           } else {
-            index += 1;
+            stop = std::stoi(args[++index], &pos);
+            if (pos != args[index].length()) {
+              // this is not a number
+              valid = false;
+            }
           }
         } catch (const std::invalid_argument&) {
-          // this is not number, ignore exception
+          // this is not numbers
+          valid = false;
         }
+        if (!valid) {
+          status.SetError("sub-command TRANSFORM, selector FOR expects, "
+                          "at least, two numeric values.");
+          return false;
+        }
+        // try to read a third numeric value for step
+        if (args.size() > ++index) {
+          try {
+            std::size_t pos;
+
+            step = std::stoi(args[index], &pos);
+            if (pos != args[index].length()) {
+              // this is not a number
+              step = 1;
+            } else {
+              index += 1;
+            }
+          } catch (const std::invalid_argument&) {
+            // this is not number, ignore exception
+          }
+        }
+
+        if (step <= 0) {
+          status.SetError("sub-command TRANSFORM, selector FOR expects "
+                          "positive numeric value for <step>.");
+          return false;
+        }
+
+        selector =
+          cmList::TransformSelector::New<cmList::TransformSelector::FOR>(
+            { start, stop, step });
+
+        continue;
       }
 
-      if (step <= 0) {
-        status.SetError("sub-command TRANSFORM, selector FOR expects "
-                        "positive numeric value for <step>.");
-        return false;
+      // output variable
+      if (args[index] == OUTPUT_VARIABLE) {
+        if (args.size() == ++index) {
+          status.SetError("sub-command TRANSFORM, OUTPUT_VARIABLE "
+                          "expects variable name argument.");
+          return false;
+        }
+
+        outputName = args[index++];
+        continue;
       }
 
-      command.Selector =
-        cm::make_unique<TransformSelectorFor>(start, stop, step);
-
-      continue;
+      status.SetError(cmStrCat("sub-command TRANSFORM, '",
+                               cmJoin(cmMakeRange(args).advance(index), " "),
+                               "': unexpected argument(s)."));
+      return false;
     }
 
-    // output variable
-    if (args[index] == OUTPUT_VARIABLE) {
-      if (args.size() == ++index) {
-        status.SetError("sub-command TRANSFORM, OUTPUT_VARIABLE "
-                        "expects variable name argument.");
-        return false;
-      }
+    // expand the list variable
+    auto list = GetList(listName, status.GetMakefile());
 
-      command.OutputName = args[index++];
-      continue;
+    if (!list) {
+      status.GetMakefile().AddDefinition(outputName, "");
+      return true;
     }
 
-    status.SetError(cmStrCat("sub-command TRANSFORM, '",
-                             cmJoin(cmMakeRange(args).advance(index), " "),
-                             "': unexpected argument(s)."));
-    return false;
-  }
-
-  // expand the list variable
-  std::vector<std::string> varArgsExpanded;
-  if (!GetList(varArgsExpanded, command.ListName, status.GetMakefile())) {
-    status.GetMakefile().AddDefinition(command.OutputName, "");
+    list->transform(descriptor->Action, arguments, std::move(selector));
+    status.GetMakefile().AddDefinition(outputName, list->to_string());
     return true;
-  }
-
-  if (!command.Selector) {
-    // no selector specified, apply transformation to all elements
-    command.Selector = cm::make_unique<TransformNoSelector>();
-  }
-
-  try {
-    command.Selector->Transform(varArgsExpanded, descriptor->Transform);
-  } catch (const transform_error& e) {
+  } catch (cmList::transform_error& e) {
     status.SetError(e.what());
     return false;
   }
-
-  status.GetMakefile().AddDefinition(command.OutputName,
-                                     cmJoin(varArgsExpanded, ";"));
-
-  return true;
 }
 
-class cmStringSorter
-{
-public:
-  enum class Order
-  {
-    UNINITIALIZED,
-    ASCENDING,
-    DESCENDING,
-  };
-
-  enum class Compare
-  {
-    UNINITIALIZED,
-    STRING,
-    FILE_BASENAME,
-    NATURAL,
-  };
-  enum class CaseSensitivity
-  {
-    UNINITIALIZED,
-    SENSITIVE,
-    INSENSITIVE,
-  };
-
-protected:
-  using StringFilter = std::string (*)(const std::string&);
-  StringFilter GetCompareFilter(Compare compare)
-  {
-    return (compare == Compare::FILE_BASENAME) ? cmSystemTools::GetFilenameName
-                                               : nullptr;
-  }
-
-  StringFilter GetCaseFilter(CaseSensitivity sensitivity)
-  {
-    return (sensitivity == CaseSensitivity::INSENSITIVE)
-      ? cmSystemTools::LowerCase
-      : nullptr;
-  }
-
-  using ComparisonFunction =
-    std::function<bool(const std::string&, const std::string&)>;
-  ComparisonFunction GetComparisonFunction(Compare compare)
-  {
-    if (compare == Compare::NATURAL) {
-      return std::function<bool(const std::string&, const std::string&)>(
-        [](const std::string& x, const std::string& y) {
-          return cmSystemTools::strverscmp(x, y) < 0;
-        });
-    }
-    return std::function<bool(const std::string&, const std::string&)>(
-      [](const std::string& x, const std::string& y) { return x < y; });
-  }
-
-public:
-  cmStringSorter(Compare compare, CaseSensitivity caseSensitivity,
-                 Order desc = Order::ASCENDING)
-    : filters{ this->GetCompareFilter(compare),
-               this->GetCaseFilter(caseSensitivity) }
-    , sortMethod(this->GetComparisonFunction(compare))
-    , descending(desc == Order::DESCENDING)
-  {
-  }
-
-  std::string ApplyFilter(const std::string& argument)
-  {
-    std::string result = argument;
-    for (auto filter : this->filters) {
-      if (filter != nullptr) {
-        result = filter(result);
-      }
-    }
-    return result;
-  }
-
-  bool operator()(const std::string& a, const std::string& b)
-  {
-    std::string af = this->ApplyFilter(a);
-    std::string bf = this->ApplyFilter(b);
-    bool result;
-    if (this->descending) {
-      result = this->sortMethod(bf, af);
-    } else {
-      result = this->sortMethod(af, bf);
-    }
-    return result;
-  }
-
-protected:
-  StringFilter filters[2] = { nullptr, nullptr };
-  ComparisonFunction sortMethod;
-  bool descending;
-};
-
 bool HandleSortCommand(std::vector<std::string> const& args,
                        cmExecutionStatus& status)
 {
@@ -1191,9 +731,8 @@
     return false;
   }
 
-  auto sortCompare = cmStringSorter::Compare::UNINITIALIZED;
-  auto sortCaseSensitivity = cmStringSorter::CaseSensitivity::UNINITIALIZED;
-  auto sortOrder = cmStringSorter::Order::UNINITIALIZED;
+  using SortConfig = cmList::SortConfiguration;
+  SortConfig sortConfig;
 
   size_t argumentIndex = 2;
   const std::string messageHint = "sub-command SORT ";
@@ -1201,7 +740,7 @@
   while (argumentIndex < args.size()) {
     std::string const& option = args[argumentIndex++];
     if (option == "COMPARE") {
-      if (sortCompare != cmStringSorter::Compare::UNINITIALIZED) {
+      if (sortConfig.Compare != SortConfig::CompareMethod::DEFAULT) {
         std::string error = cmStrCat(messageHint, "option \"", option,
                                      "\" has been specified multiple times.");
         status.SetError(error);
@@ -1210,11 +749,11 @@
       if (argumentIndex < args.size()) {
         std::string const& argument = args[argumentIndex++];
         if (argument == "STRING") {
-          sortCompare = cmStringSorter::Compare::STRING;
+          sortConfig.Compare = SortConfig::CompareMethod::STRING;
         } else if (argument == "FILE_BASENAME") {
-          sortCompare = cmStringSorter::Compare::FILE_BASENAME;
+          sortConfig.Compare = SortConfig::CompareMethod::FILE_BASENAME;
         } else if (argument == "NATURAL") {
-          sortCompare = cmStringSorter::Compare::NATURAL;
+          sortConfig.Compare = SortConfig::CompareMethod::NATURAL;
         } else {
           std::string error =
             cmStrCat(messageHint, "value \"", argument, "\" for option \"",
@@ -1228,8 +767,7 @@
         return false;
       }
     } else if (option == "CASE") {
-      if (sortCaseSensitivity !=
-          cmStringSorter::CaseSensitivity::UNINITIALIZED) {
+      if (sortConfig.Case != SortConfig::CaseSensitivity::DEFAULT) {
         status.SetError(cmStrCat(messageHint, "option \"", option,
                                  "\" has been specified multiple times."));
         return false;
@@ -1237,9 +775,9 @@
       if (argumentIndex < args.size()) {
         std::string const& argument = args[argumentIndex++];
         if (argument == "SENSITIVE") {
-          sortCaseSensitivity = cmStringSorter::CaseSensitivity::SENSITIVE;
+          sortConfig.Case = SortConfig::CaseSensitivity::SENSITIVE;
         } else if (argument == "INSENSITIVE") {
-          sortCaseSensitivity = cmStringSorter::CaseSensitivity::INSENSITIVE;
+          sortConfig.Case = SortConfig::CaseSensitivity::INSENSITIVE;
         } else {
           status.SetError(cmStrCat(messageHint, "value \"", argument,
                                    "\" for option \"", option,
@@ -1253,7 +791,7 @@
       }
     } else if (option == "ORDER") {
 
-      if (sortOrder != cmStringSorter::Order::UNINITIALIZED) {
+      if (sortConfig.Order != SortConfig::OrderMode::DEFAULT) {
         status.SetError(cmStrCat(messageHint, "option \"", option,
                                  "\" has been specified multiple times."));
         return false;
@@ -1261,9 +799,9 @@
       if (argumentIndex < args.size()) {
         std::string const& argument = args[argumentIndex++];
         if (argument == "ASCENDING") {
-          sortOrder = cmStringSorter::Order::ASCENDING;
+          sortConfig.Order = SortConfig::OrderMode::ASCENDING;
         } else if (argument == "DESCENDING") {
-          sortOrder = cmStringSorter::Order::DESCENDING;
+          sortConfig.Order = SortConfig::OrderMode::DESCENDING;
         } else {
           status.SetError(cmStrCat(messageHint, "value \"", argument,
                                    "\" for option \"", option,
@@ -1281,35 +819,17 @@
       return false;
     }
   }
-  // set Default Values if Option is not given
-  if (sortCompare == cmStringSorter::Compare::UNINITIALIZED) {
-    sortCompare = cmStringSorter::Compare::STRING;
-  }
-  if (sortCaseSensitivity == cmStringSorter::CaseSensitivity::UNINITIALIZED) {
-    sortCaseSensitivity = cmStringSorter::CaseSensitivity::SENSITIVE;
-  }
-  if (sortOrder == cmStringSorter::Order::UNINITIALIZED) {
-    sortOrder = cmStringSorter::Order::ASCENDING;
-  }
 
   const std::string& listName = args[1];
   // expand the variable
-  std::vector<std::string> varArgsExpanded;
-  if (!GetList(varArgsExpanded, listName, status.GetMakefile())) {
+  auto list = GetList(listName, status.GetMakefile());
+
+  if (!list) {
     return true;
   }
 
-  if ((sortCompare == cmStringSorter::Compare::STRING) &&
-      (sortCaseSensitivity == cmStringSorter::CaseSensitivity::SENSITIVE) &&
-      (sortOrder == cmStringSorter::Order::ASCENDING)) {
-    std::sort(varArgsExpanded.begin(), varArgsExpanded.end());
-  } else {
-    cmStringSorter sorter(sortCompare, sortCaseSensitivity, sortOrder);
-    std::sort(varArgsExpanded.begin(), varArgsExpanded.end(), sorter);
-  }
-
-  std::string value = cmJoin(varArgsExpanded, ";");
-  status.GetMakefile().AddDefinition(listName, value);
+  status.GetMakefile().AddDefinition(listName,
+                                     list->sort(sortConfig).to_string());
   return true;
 }
 
@@ -1326,9 +846,9 @@
   const std::string& variableName = args.back();
 
   // expand the variable
-  std::vector<std::string> varArgsExpanded;
-  if (!GetList(varArgsExpanded, listName, status.GetMakefile()) ||
-      varArgsExpanded.empty()) {
+  auto list = GetList(listName, status.GetMakefile());
+
+  if (!list || list->empty()) {
     status.GetMakefile().AddDefinition(variableName, "");
     return true;
   }
@@ -1344,11 +864,9 @@
     return false;
   }
 
-  using size_type = decltype(varArgsExpanded)::size_type;
-
-  if (start < 0 || static_cast<size_type>(start) >= varArgsExpanded.size()) {
+  if (start < 0) {
     status.SetError(cmStrCat("begin index: ", start, " is out of range 0 - ",
-                             varArgsExpanded.size() - 1));
+                             list->size() - 1));
     return false;
   }
   if (length < -1) {
@@ -1356,15 +874,17 @@
     return false;
   }
 
-  const size_type end =
-    (length == -1 ||
-     static_cast<size_type>(start + length) > varArgsExpanded.size())
-    ? varArgsExpanded.size()
-    : static_cast<size_type>(start + length);
-  std::vector<std::string> sublist(varArgsExpanded.begin() + start,
-                                   varArgsExpanded.begin() + end);
-  status.GetMakefile().AddDefinition(variableName, cmJoin(sublist, ";"));
-  return true;
+  using size_type = cmList::size_type;
+
+  try {
+    auto sublist = list->sublist(static_cast<size_type>(start),
+                                 static_cast<size_type>(length));
+    status.GetMakefile().AddDefinition(variableName, sublist.to_string());
+    return true;
+  } catch (std::out_of_range& e) {
+    status.SetError(e.what());
+    return false;
+  }
 }
 
 bool HandleRemoveAtCommand(std::vector<std::string> const& args,
@@ -1378,9 +898,9 @@
 
   const std::string& listName = args[1];
   // expand the variable
-  std::vector<std::string> varArgsExpanded;
-  if (!GetList(varArgsExpanded, listName, status.GetMakefile()) ||
-      varArgsExpanded.empty()) {
+  auto list = GetList(listName, status.GetMakefile());
+
+  if (!list || list->empty()) {
     std::ostringstream str;
     str << "index: ";
     for (size_t i = 1; i < args.size(); ++i) {
@@ -1395,36 +915,25 @@
   }
 
   size_t cc;
-  std::vector<size_t> removed;
-  size_t nitem = varArgsExpanded.size();
+  std::vector<cmList::index_type> removed;
   for (cc = 2; cc < args.size(); ++cc) {
-    int item;
-    if (!GetIndexArg(args[cc], &item, status.GetMakefile())) {
+    int index;
+    if (!GetIndexArg(args[cc], &index, status.GetMakefile())) {
       status.SetError(cmStrCat("index: ", args[cc], " is not a valid index"));
       return false;
     }
-    if (item < 0) {
-      item = static_cast<int>(nitem) + item;
-    }
-    if (item < 0 || nitem <= static_cast<size_t>(item)) {
-      status.SetError(cmStrCat("index: ", item, " out of range (-", nitem,
-                               ", ", nitem - 1, ")"));
-      return false;
-    }
-    removed.push_back(static_cast<size_t>(item));
+    removed.push_back(index);
   }
 
-  std::sort(removed.begin(), removed.end());
-  auto remEnd = std::unique(removed.begin(), removed.end());
-  auto remBegin = removed.begin();
-
-  auto argsEnd =
-    cmRemoveIndices(varArgsExpanded, cmMakeRange(remBegin, remEnd));
-  auto argsBegin = varArgsExpanded.cbegin();
-  std::string value = cmJoin(cmMakeRange(argsBegin, argsEnd), ";");
-
-  status.GetMakefile().AddDefinition(listName, value);
-  return true;
+  try {
+    status.GetMakefile().AddDefinition(
+      listName,
+      list->remove_items(removed.begin(), removed.end()).to_string());
+    return true;
+  } catch (std::out_of_range& e) {
+    status.SetError(e.what());
+    return false;
+  }
 }
 
 bool HandleFilterCommand(std::vector<std::string> const& args,
@@ -1447,11 +956,11 @@
   }
 
   const std::string& op = args[2];
-  bool includeMatches;
+  cmList::FilterMode filterMode;
   if (op == "INCLUDE") {
-    includeMatches = true;
+    filterMode = cmList::FilterMode::INCLUDE;
   } else if (op == "EXCLUDE") {
-    includeMatches = false;
+    filterMode = cmList::FilterMode::EXCLUDE;
   } else {
     status.SetError("sub-command FILTER does not recognize operator " + op);
     return false;
@@ -1459,70 +968,33 @@
 
   const std::string& listName = args[1];
   // expand the variable
-  std::vector<std::string> varArgsExpanded;
-  if (!GetList(varArgsExpanded, listName, status.GetMakefile())) {
+  auto list = GetList(listName, status.GetMakefile());
+
+  if (!list) {
     return true;
   }
 
   const std::string& mode = args[3];
-  if (mode == "REGEX") {
-    if (args.size() != 5) {
-      status.SetError("sub-command FILTER, mode REGEX "
-                      "requires five arguments.");
-      return false;
-    }
-    return FilterRegex(args, includeMatches, listName, varArgsExpanded,
-                       status);
-  }
-
-  status.SetError("sub-command FILTER does not recognize mode " + mode);
-  return false;
-}
-
-class MatchesRegex
-{
-public:
-  MatchesRegex(cmsys::RegularExpression& in_regex, bool in_includeMatches)
-    : regex(in_regex)
-    , includeMatches(in_includeMatches)
-  {
-  }
-
-  bool operator()(const std::string& target)
-  {
-    return this->regex.find(target) ^ this->includeMatches;
-  }
-
-private:
-  cmsys::RegularExpression& regex;
-  const bool includeMatches;
-};
-
-bool FilterRegex(std::vector<std::string> const& args, bool includeMatches,
-                 std::string const& listName,
-                 std::vector<std::string>& varArgsExpanded,
-                 cmExecutionStatus& status)
-{
-  const std::string& pattern = args[4];
-  cmsys::RegularExpression regex(pattern);
-  if (!regex.is_valid()) {
-    std::string error =
-      cmStrCat("sub-command FILTER, mode REGEX failed to compile regex \"",
-               pattern, "\".");
-    status.SetError(error);
+  if (mode != "REGEX") {
+    status.SetError("sub-command FILTER does not recognize mode " + mode);
     return false;
   }
+  if (args.size() != 5) {
+    status.SetError("sub-command FILTER, mode REGEX "
+                    "requires five arguments.");
+    return false;
+  }
+  const std::string& pattern = args[4];
 
-  auto argsBegin = varArgsExpanded.begin();
-  auto argsEnd = varArgsExpanded.end();
-  auto newArgsEnd =
-    std::remove_if(argsBegin, argsEnd, MatchesRegex(regex, includeMatches));
-
-  std::string value = cmJoin(cmMakeRange(argsBegin, newArgsEnd), ";");
-  status.GetMakefile().AddDefinition(listName, value);
-  return true;
+  try {
+    status.GetMakefile().AddDefinition(
+      listName, list->filter(pattern, filterMode).to_string());
+    return true;
+  } catch (std::invalid_argument& e) {
+    status.SetError(e.what());
+    return false;
+  }
 }
-
 } // namespace
 
 bool cmListCommand(std::vector<std::string> const& args,
diff --git a/Source/cmListFileCache.cxx b/Source/cmListFileCache.cxx
index 6270c82..97f5de9 100644
--- a/Source/cmListFileCache.cxx
+++ b/Source/cmListFileCache.cxx
@@ -11,10 +11,10 @@
 #  include <cmsys/Encoding.hxx>
 #endif
 
+#include "cmList.h"
 #include "cmListFileLexer.h"
 #include "cmMessageType.h"
 #include "cmMessenger.h"
-#include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 
 struct cmListFileParser
@@ -496,10 +496,11 @@
 }
 
 std::vector<BT<std::string>> cmExpandListWithBacktrace(
-  std::string const& list, cmListFileBacktrace const& bt, bool emptyArgs)
+  std::string const& list, cmListFileBacktrace const& bt,
+  cmList::EmptyElements emptyArgs)
 {
   std::vector<BT<std::string>> result;
-  std::vector<std::string> tmp = cmExpandedList(list, emptyArgs);
+  cmList tmp{ list, emptyArgs };
   result.reserve(tmp.size());
   for (std::string& i : tmp) {
     result.emplace_back(std::move(i), bt);
diff --git a/Source/cmListFileCache.h b/Source/cmListFileCache.h
index 0553989..e4e6eb3 100644
--- a/Source/cmListFileCache.h
+++ b/Source/cmListFileCache.h
@@ -13,6 +13,7 @@
 #include <cm/optional>
 
 #include "cmConstStack.h"
+#include "cmList.h"
 #include "cmSystemTools.h"
 
 /** \class cmListFileCache
@@ -232,7 +233,7 @@
 std::vector<BT<std::string>> cmExpandListWithBacktrace(
   std::string const& list,
   cmListFileBacktrace const& bt = cmListFileBacktrace(),
-  bool emptyArgs = false);
+  cmList::EmptyElements emptyArgs = cmList::EmptyElements::No);
 
 struct cmListFile
 {
diff --git a/Source/cmLocalCommonGenerator.cxx b/Source/cmLocalCommonGenerator.cxx
index eca7a9e..aa953f4 100644
--- a/Source/cmLocalCommonGenerator.cxx
+++ b/Source/cmLocalCommonGenerator.cxx
@@ -8,7 +8,6 @@
 #include "cmGeneratorTarget.h"
 #include "cmMakefile.h"
 #include "cmOutputConverter.h"
-#include "cmState.h"
 #include "cmStateDirectory.h"
 #include "cmStateSnapshot.h"
 #include "cmStringAlgorithms.h"
@@ -17,9 +16,8 @@
 class cmGlobalGenerator;
 
 cmLocalCommonGenerator::cmLocalCommonGenerator(cmGlobalGenerator* gg,
-                                               cmMakefile* mf, WorkDir wd)
+                                               cmMakefile* mf)
   : cmLocalGenerator(gg, mf)
-  , WorkingDirectory(wd)
 {
   this->ConfigNames =
     this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
@@ -29,21 +27,9 @@
 
 std::string const& cmLocalCommonGenerator::GetWorkingDirectory() const
 {
-  if (this->WorkingDirectory == WorkDir::TopBin) {
-    return this->GetState()->GetBinaryDirectory();
-  }
   return this->StateSnapshot.GetDirectory().GetCurrentBinary();
 }
 
-std::string cmLocalCommonGenerator::MaybeRelativeToWorkDir(
-  std::string const& path) const
-{
-  if (this->WorkingDirectory == WorkDir::TopBin) {
-    return this->MaybeRelativeToTopBinDir(path);
-  }
-  return this->MaybeRelativeToCurBinDir(path);
-}
-
 std::string cmLocalCommonGenerator::GetTargetFortranFlags(
   cmGeneratorTarget const* target, std::string const& config)
 {
diff --git a/Source/cmLocalCommonGenerator.h b/Source/cmLocalCommonGenerator.h
index 0505c13..52f7a9e 100644
--- a/Source/cmLocalCommonGenerator.h
+++ b/Source/cmLocalCommonGenerator.h
@@ -20,15 +20,8 @@
  */
 class cmLocalCommonGenerator : public cmLocalGenerator
 {
-protected:
-  enum class WorkDir
-  {
-    TopBin,
-    CurBin,
-  };
-
 public:
-  cmLocalCommonGenerator(cmGlobalGenerator* gg, cmMakefile* mf, WorkDir wd);
+  cmLocalCommonGenerator(cmGlobalGenerator* gg, cmMakefile* mf);
   ~cmLocalCommonGenerator() override;
 
   std::vector<std::string> const& GetConfigNames() const
@@ -36,9 +29,7 @@
     return this->ConfigNames;
   }
 
-  std::string const& GetWorkingDirectory() const;
-
-  std::string MaybeRelativeToWorkDir(std::string const& path) const;
+  virtual std::string const& GetWorkingDirectory() const;
 
   std::string GetTargetFortranFlags(cmGeneratorTarget const* target,
                                     std::string const& config) override;
@@ -48,8 +39,6 @@
     cmGeneratorTarget const* gt = nullptr) override;
 
 protected:
-  WorkDir WorkingDirectory;
-
   std::vector<std::string> ConfigNames;
 
   friend class cmCommonTargetGenerator;
diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx
index 75ec694..63b2043 100644
--- a/Source/cmLocalGenerator.cxx
+++ b/Source/cmLocalGenerator.cxx
@@ -38,6 +38,7 @@
 #include "cmInstallTargetGenerator.h"
 #include "cmLinkLineComputer.h"
 #include "cmLinkLineDeviceComputer.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmRange.h"
 #include "cmRulePlaceholderExpander.h"
@@ -85,6 +86,7 @@
                                 "CMAKE_RANLIB",
                                 "CMAKE_LINKER",
                                 "CMAKE_MT",
+                                "CMAKE_TAPI",
                                 "CMAKE_CUDA_HOST_COMPILER",
                                 "CMAKE_CUDA_HOST_LINK_LAUNCHER",
                                 "CMAKE_CL_SHOWINCLUDES_PREFIX" };
@@ -134,16 +136,21 @@
     this->LinkerSysroot = this->Makefile->GetSafeDefinition("CMAKE_SYSROOT");
   }
 
+  // OSX SYSROOT can be required by some tools, like tapi
+  {
+    cmValue osxSysroot = this->Makefile->GetDefinition("CMAKE_OSX_SYSROOT");
+    this->VariableMappings["CMAKE_OSX_SYSROOT"] =
+      osxSysroot.IsEmpty() ? "/" : this->EscapeForShell(*osxSysroot, true);
+  }
+
   if (cmValue appleArchSysroots =
         this->Makefile->GetDefinition("CMAKE_APPLE_ARCH_SYSROOTS")) {
     std::string const& appleArchs =
       this->Makefile->GetSafeDefinition("CMAKE_OSX_ARCHITECTURES");
-    std::vector<std::string> archs;
-    std::vector<std::string> sysroots;
-    cmExpandList(appleArchs, archs);
-    cmExpandList(*appleArchSysroots, sysroots, true);
+    cmList archs(appleArchs);
+    cmList sysroots{ appleArchSysroots, cmList::EmptyElements::Yes };
     if (archs.size() == sysroots.size()) {
-      for (size_t i = 0; i < archs.size(); ++i) {
+      for (cmList::size_type i = 0; i < archs.size(); ++i) {
         this->AppleArchSysroots[archs[i]] = sysroots[i];
       }
     } else {
@@ -200,12 +207,12 @@
   }
 }
 
-cmRulePlaceholderExpander* cmLocalGenerator::CreateRulePlaceholderExpander()
-  const
+std::unique_ptr<cmRulePlaceholderExpander>
+cmLocalGenerator::CreateRulePlaceholderExpander() const
 {
-  return new cmRulePlaceholderExpander(this->Compilers, this->VariableMappings,
-                                       this->CompilerSysroot,
-                                       this->LinkerSysroot);
+  return cm::make_unique<cmRulePlaceholderExpander>(
+    this->Compilers, this->VariableMappings, this->CompilerSysroot,
+    this->LinkerSysroot);
 }
 
 cmLocalGenerator::~cmLocalGenerator() = default;
@@ -339,7 +346,7 @@
 
   cmValue testIncludeFiles = this->Makefile->GetProperty("TEST_INCLUDE_FILES");
   if (testIncludeFiles) {
-    std::vector<std::string> includesList = cmExpandedList(*testIncludeFiles);
+    cmList includesList{ *testIncludeFiles };
     for (std::string const& i : includesList) {
       fout << "include(\"" << i << "\")\n";
     }
@@ -827,13 +834,18 @@
   return this->Makefile->GetStateSnapshot();
 }
 
-cmValue cmLocalGenerator::GetRuleLauncher(cmGeneratorTarget* target,
-                                          const std::string& prop)
+std::string cmLocalGenerator::GetRuleLauncher(cmGeneratorTarget* target,
+                                              const std::string& prop,
+                                              const std::string& config)
 {
+  cmValue value = this->Makefile->GetProperty(prop);
   if (target) {
-    return target->GetProperty(prop);
+    value = target->GetProperty(prop);
   }
-  return this->Makefile->GetProperty(prop);
+  if (value) {
+    return cmGeneratorExpression::Evaluate(*value, this, config, target);
+  }
+  return "";
 }
 
 std::string cmLocalGenerator::ConvertToIncludeReference(
@@ -902,7 +914,7 @@
         cmSystemTools::CollapseFullPath(cmStrCat(i, "/../"));
       if (emitted.insert(frameworkDir).second) {
         if (sysFwSearchFlag && target &&
-            target->IsSystemIncludeDirectory(i, config, lang)) {
+            target->IsSystemIncludeDirectory(frameworkDir, config, lang)) {
           includeFlags << *sysFwSearchFlag;
         } else {
           includeFlags << *fwSearchFlag;
@@ -1051,9 +1063,9 @@
         std::string isJMCEnabled =
           cmGeneratorExpression::Evaluate(*jmcExprGen, this, config);
         if (cmIsOn(isJMCEnabled)) {
-          std::vector<std::string> optVec = cmExpandedList(*jmc);
+          cmList optList{ *jmc };
           std::string jmcFlags;
-          this->AppendCompileOptions(jmcFlags, optVec);
+          this->AppendCompileOptions(jmcFlags, optList);
           if (!jmcFlags.empty()) {
             flags.emplace_back(std::move(jmcFlags));
           }
@@ -1155,11 +1167,11 @@
 
   // Standard include directories to be added unconditionally at the end.
   // These are intended to simulate additional implicit include directories.
-  std::vector<std::string> userStandardDirs;
+  cmList userStandardDirs;
   {
     std::string const value = this->Makefile->GetSafeDefinition(
       cmStrCat("CMAKE_", lang, "_STANDARD_INCLUDE_DIRECTORIES"));
-    cmExpandList(value, userStandardDirs);
+    userStandardDirs.assign(value);
     for (std::string& usd : userStandardDirs) {
       cmSystemTools::ConvertToUnixSlashes(usd);
     }
@@ -1182,13 +1194,12 @@
     //   directories for modules ('.mod' files).
     if (lang != "Fortran") {
       size_t const impDirVecOldSize = impDirVec.size();
-      if (this->Makefile->GetDefExpandList(
-            cmStrCat("CMAKE_", lang, "_IMPLICIT_INCLUDE_DIRECTORIES"),
-            impDirVec)) {
-        // FIXME: Use cmRange with 'advance()' when it supports non-const.
-        for (size_t i = impDirVecOldSize; i < impDirVec.size(); ++i) {
-          cmSystemTools::ConvertToUnixSlashes(impDirVec[i]);
-        }
+      cmList::append(impDirVec,
+                     this->Makefile->GetDefinition(cmStrCat(
+                       "CMAKE_", lang, "_IMPLICIT_INCLUDE_DIRECTORIES")));
+      // FIXME: Use cmRange with 'advance()' when it supports non-const.
+      for (size_t i = impDirVecOldSize; i < impDirVec.size(); ++i) {
+        cmSystemTools::ConvertToUnixSlashes(impDirVec[i]);
       }
     }
 
@@ -1575,6 +1586,8 @@
   this->AppendPositionIndependentLinkerFlags(extraLinkFlags, target, config,
                                              linkLanguage);
   this->AppendIPOLinkerFlags(extraLinkFlags, target, config, linkLanguage);
+  this->AppendDependencyInfoLinkerFlags(extraLinkFlags, target, config,
+                                        linkLanguage);
   this->AppendModuleDefinitionFlag(extraLinkFlags, target, linkLineComputer,
                                    config);
 
@@ -1632,21 +1645,24 @@
   return flags;
 }
 
-static std::string GetFrameworkFlags(const std::string& lang,
-                                     const std::string& config,
-                                     cmGeneratorTarget* target)
+std::string cmLocalGenerator::GetFrameworkFlags(std::string const& lang,
+                                                std::string const& config,
+                                                cmGeneratorTarget* target)
 {
   cmLocalGenerator* lg = target->GetLocalGenerator();
   cmMakefile* mf = lg->GetMakefile();
 
-  if (!mf->IsOn("APPLE")) {
+  if (!target->IsApple()) {
     return std::string();
   }
 
-  std::string fwSearchFlagVar = "CMAKE_" + lang + "_FRAMEWORK_SEARCH_FLAG";
-  cmValue fwSearchFlag = mf->GetDefinition(fwSearchFlagVar);
-  if (!cmNonempty(fwSearchFlag)) {
-    return std::string();
+  cmValue fwSearchFlag =
+    mf->GetDefinition(cmStrCat("CMAKE_", lang, "_FRAMEWORK_SEARCH_FLAG"));
+  cmValue sysFwSearchFlag = mf->GetDefinition(
+    cmStrCat("CMAKE_", lang, "_SYSTEM_FRAMEWORK_SEARCH_FLAG"));
+
+  if (!fwSearchFlag && !sysFwSearchFlag) {
+    return std::string{};
   }
 
   std::set<std::string> emitted;
@@ -1671,7 +1687,12 @@
     std::vector<std::string> const& frameworks = cli->GetFrameworkPaths();
     for (std::string const& framework : frameworks) {
       if (emitted.insert(framework).second) {
-        flags += *fwSearchFlag;
+        if (sysFwSearchFlag &&
+            target->IsSystemIncludeDirectory(framework, config, lang)) {
+          flags += *sysFwSearchFlag;
+        } else {
+          flags += *fwSearchFlag;
+        }
         flags +=
           lg->ConvertToOutputFormat(framework, cmOutputConverter::SHELL);
         flags += " ";
@@ -1681,13 +1702,6 @@
   return flags;
 }
 
-std::string cmLocalGenerator::GetFrameworkFlags(std::string const& l,
-                                                std::string const& config,
-                                                cmGeneratorTarget* target)
-{
-  return ::GetFrameworkFlags(l, config, target);
-}
-
 void cmLocalGenerator::GetTargetDefines(cmGeneratorTarget const* target,
                                         std::string const& config,
                                         std::string const& lang,
@@ -1775,10 +1789,13 @@
     cmStrCat("CMAKE_", cli.GetLinkLanguage(), "_STANDARD_LIBRARIES"));
 
   // Append the framework search path flags.
-  std::string fwSearchFlag = this->Makefile->GetSafeDefinition(
+  cmValue fwSearchFlag = this->Makefile->GetDefinition(
     cmStrCat("CMAKE_", linkLanguage, "_FRAMEWORK_SEARCH_FLAG"));
+  cmValue sysFwSearchFlag = this->Makefile->GetDefinition(
+    cmStrCat("CMAKE_", linkLanguage, "_SYSTEM_FRAMEWORK_SEARCH_FLAG"));
 
-  frameworkPath = linkLineComputer->ComputeFrameworkPath(cli, fwSearchFlag);
+  frameworkPath =
+    linkLineComputer->ComputeFrameworkPath(cli, fwSearchFlag, sysFwSearchFlag);
   linkLineComputer->ComputeLinkPath(cli, libPathFlag, libPathTerminator,
                                     linkPath);
   linkLineComputer->ComputeLinkLibraries(cli, stdLibString, linkLibraries);
@@ -1813,7 +1830,7 @@
         // OLD behavior is to always add the flags, except on AIX where
         // we compute symbol exports if ENABLE_EXPORTS is on.
         add_shlib_flags =
-          !(tgt.Target->IsAIX() && tgt.GetPropertyAsBool("ENABLE_EXPORTS"));
+          !(tgt.IsAIX() && tgt.GetPropertyAsBool("ENABLE_EXPORTS"));
         break;
       case cmPolicies::REQUIRED_IF_USED:
       case cmPolicies::REQUIRED_ALWAYS:
@@ -1825,7 +1842,7 @@
         // NEW behavior is to only add the flags if ENABLE_EXPORTS is on,
         // except on AIX where we compute symbol exports.
         add_shlib_flags =
-          !tgt.Target->IsAIX() && tgt.GetPropertyAsBool("ENABLE_EXPORTS");
+          !tgt.IsAIX() && tgt.GetPropertyAsBool("ENABLE_EXPORTS");
         break;
     }
 
@@ -1859,9 +1876,8 @@
                                             const std::string& filterArch)
 {
   // Only add Apple specific flags on Apple platforms
-  if (this->Makefile->IsOn("APPLE") && this->EmitUniversalBinaryFlags) {
-    std::vector<std::string> archs;
-    target->GetAppleArchs(config, archs);
+  if (target->IsApple() && this->EmitUniversalBinaryFlags) {
+    std::vector<std::string> archs = target->GetAppleArchs(config, lang);
     if (!archs.empty() &&
         (lang == "C" || lang == "CXX" || lang == "OBJC" || lang == "OBJCXX" ||
          lang == "ASM")) {
@@ -1942,8 +1958,8 @@
       cmValue opt =
         target->Target->GetMakefile()->GetDefinition(optionFlagDef);
       if (opt) {
-        std::vector<std::string> optVec = cmExpandedList(*opt);
-        for (std::string const& i : optVec) {
+        cmList optList{ *opt };
+        for (std::string const& i : optList) {
           this->AppendFlagEscape(flags, i);
         }
       }
@@ -2414,7 +2430,7 @@
       cmStrCat("CMAKE_", lang, "_COMPILE_OPTIONS_PIC"));
   }
   if (!picFlags.empty()) {
-    std::vector<std::string> options = cmExpandedList(picFlags);
+    cmList options{ picFlags };
     for (std::string const& o : options) {
       this->AppendFlagEscape(flags, o);
     }
@@ -2435,10 +2451,9 @@
         cmStrCat("CMAKE_", lang, "_COMPILE_OPTIONS_COLOR_DIAGNOSTICS_OFF");
     }
 
-    std::vector<std::string> options;
-    this->Makefile->GetDefExpandList(colorFlagName, options);
+    cmList options{ this->Makefile->GetDefinition(colorFlagName) };
 
-    for (std::string const& option : options) {
+    for (auto const& option : options) {
       this->AppendFlagEscape(flags, option);
     }
   }
@@ -2581,7 +2596,7 @@
 
       std::vector<std::string> architectures;
       if (!this->GetGlobalGenerator()->IsXcode()) {
-        target->GetAppleArchs(config, architectures);
+        architectures = target->GetAppleArchs(config, lang);
       }
       if (architectures.empty()) {
         architectures.emplace_back();
@@ -2841,7 +2856,6 @@
   auto cc = cm::make_unique<cmCustomCommand>();
   cc->SetCommandLines(commandLines);
   cc->SetComment(no_message);
-  cc->SetCMP0116Status(cmPolicies::NEW);
   cc->SetStdPipesUTF8(true);
 
   if (this->GetGlobalGenerator()->IsVisualStudio()) {
@@ -2962,6 +2976,7 @@
     unity_file << *beforeInclude << "\n";
   }
 
+  unity_file << "// NOLINTNEXTLINE(bugprone-suspicious-include)\n";
   unity_file << "#include \"" << sf_full_path << "\"\n";
 
   if (afterInclude) {
@@ -3152,7 +3167,7 @@
     return;
   }
 
-  std::vector<std::string> flagsList = cmExpandedList(*rawFlagsList);
+  cmList flagsList{ *rawFlagsList };
   for (std::string const& o : flagsList) {
     this->AppendFlagEscape(flags, o);
   }
@@ -3187,12 +3202,47 @@
     return;
   }
 
-  std::vector<std::string> flagsList = cmExpandedList(pieFlags);
+  cmList flagsList{ pieFlags };
   for (const auto& flag : flagsList) {
     this->AppendFlagEscape(flags, flag);
   }
 }
 
+void cmLocalGenerator::AppendDependencyInfoLinkerFlags(
+  std::string& flags, cmGeneratorTarget* target, const std::string& config,
+  const std::string& linkLanguage)
+{
+  if (!this->GetGlobalGenerator()->SupportsLinkerDependencyFile() ||
+      !target->HasLinkDependencyFile(config)) {
+    return;
+  }
+
+  auto depFlag = *this->Makefile->GetDefinition(
+    cmStrCat("CMAKE_", linkLanguage, "_LINKER_DEPFILE_FLAGS"));
+  if (depFlag.empty()) {
+    return;
+  }
+
+  auto depFile = this->ConvertToOutputFormat(
+    this->MaybeRelativeToWorkDir(this->GetLinkDependencyFile(target, config)),
+    cmOutputConverter::SHELL);
+  auto rulePlaceholderExpander = this->CreateRulePlaceholderExpander();
+  cmRulePlaceholderExpander::RuleVariables linkDepsVariables;
+  linkDepsVariables.DependencyFile = depFile.c_str();
+  rulePlaceholderExpander->ExpandRuleVariables(this, depFlag,
+                                               linkDepsVariables);
+  auto depFlags = cmExpandListWithBacktrace(depFlag);
+  target->ResolveLinkerWrapper(depFlags, linkLanguage);
+
+  this->AppendFlags(flags, depFlags);
+}
+
+std::string cmLocalGenerator::GetLinkDependencyFile(
+  cmGeneratorTarget* /*target*/, const std::string& /*config*/) const
+{
+  return "link.d";
+}
+
 void cmLocalGenerator::AppendModuleDefinitionFlag(
   std::string& flags, cmGeneratorTarget const* target,
   cmLinkLineComputer* linkLineComputer, std::string const& config)
@@ -3253,7 +3303,7 @@
   }
 
   // Expand the list of options.
-  std::vector<std::string> options_vec = cmExpandedList(options_list);
+  cmList options_vec{ options_list };
   this->AppendCompileOptions(options, options_vec, regex);
 }
 
@@ -3311,7 +3361,7 @@
   }
 
   // Expand the list of includes.
-  std::vector<std::string> includes_vec = cmExpandedList(includes_list);
+  cmList includes_vec{ includes_list };
   this->AppendIncludeDirectories(includes, includes_vec, sourceFile);
 }
 
@@ -3443,7 +3493,7 @@
   cmValue optionList = this->Makefile->GetDefinition(
     cmStrCat("CMAKE_", lang, "_COMPILE_OPTIONS_", feature));
   if (optionList) {
-    std::vector<std::string> options = cmExpandedList(*optionList);
+    cmList options{ *optionList };
     for (std::string const& o : options) {
       this->AppendFlagEscape(flags, o);
     }
@@ -4374,12 +4424,11 @@
 
 std::vector<std::string> ComputeISPCObjectSuffixes(cmGeneratorTarget* target)
 {
-  const std::string& targetProperty =
-    target->GetSafeProperty("ISPC_INSTRUCTION_SETS");
-  std::vector<std::string> ispcTargets;
+  const cmValue targetProperty = target->GetProperty("ISPC_INSTRUCTION_SETS");
+  cmList ispcTargets;
 
-  if (!cmIsOff(targetProperty)) {
-    cmExpandList(targetProperty, ispcTargets);
+  if (!targetProperty.IsOff()) {
+    ispcTargets.assign(targetProperty);
     for (auto& ispcTarget : ispcTargets) {
       // transform targets into the suffixes
       auto pos = ispcTarget.find('-');
@@ -4391,7 +4440,7 @@
       ispcTarget = target_suffix;
     }
   }
-  return ispcTargets;
+  return std::move(ispcTargets.data());
 }
 
 std::vector<std::string> ComputeISPCExtraObjects(
@@ -4489,11 +4538,11 @@
 std::vector<std::string> cmLocalGenerator::ExpandCustomCommandOutputPaths(
   cmCompiledGeneratorExpression const& cge, std::string const& config)
 {
-  std::vector<std::string> paths = cmExpandedList(cge.Evaluate(this, config));
+  cmList paths{ cge.Evaluate(this, config) };
   for (std::string& p : paths) {
     p = cmSystemTools::CollapseFullPath(p, this->GetCurrentBinaryDirectory());
   }
-  return paths;
+  return std::move(paths.data());
 }
 
 std::vector<std::string> cmLocalGenerator::ExpandCustomCommandOutputGenex(
diff --git a/Source/cmLocalGenerator.h b/Source/cmLocalGenerator.h
index 20f23de..c811408 100644
--- a/Source/cmLocalGenerator.h
+++ b/Source/cmLocalGenerator.h
@@ -89,7 +89,7 @@
 {
 public:
   cmLocalGenerator(cmGlobalGenerator* gg, cmMakefile* makefile);
-  virtual ~cmLocalGenerator();
+  ~cmLocalGenerator() override;
 
   /**
    * Generate the makefile for this directory.
@@ -137,7 +137,8 @@
     return this->GlobalGenerator;
   }
 
-  virtual cmRulePlaceholderExpander* CreateRulePlaceholderExpander() const;
+  virtual std::unique_ptr<cmRulePlaceholderExpander>
+  CreateRulePlaceholderExpander() const;
 
   std::string GetLinkLibsCMP0065(std::string const& linkLanguage,
                                  cmGeneratorTarget& tgt) const;
@@ -183,6 +184,12 @@
                                             cmGeneratorTarget* target,
                                             const std::string& config,
                                             const std::string& lang);
+  void AppendDependencyInfoLinkerFlags(std::string& flags,
+                                       cmGeneratorTarget* target,
+                                       const std::string& config,
+                                       const std::string& lang);
+  virtual std::string GetLinkDependencyFile(cmGeneratorTarget* target,
+                                            const std::string& config) const;
   void AppendModuleDefinitionFlag(std::string& flags,
                                   cmGeneratorTarget const* target,
                                   cmLinkLineComputer* linkLineComputer,
@@ -532,7 +539,9 @@
   void CreateEvaluationFileOutputs(const std::string& config);
   void ProcessEvaluationFiles(std::vector<std::string>& generatedFiles);
 
-  cmValue GetRuleLauncher(cmGeneratorTarget* target, const std::string& prop);
+  std::string GetRuleLauncher(cmGeneratorTarget* target,
+                              const std::string& prop,
+                              const std::string& config);
 
 protected:
   // The default implementation converts to a Windows shortpath to
diff --git a/Source/cmLocalNinjaGenerator.cxx b/Source/cmLocalNinjaGenerator.cxx
index 3443cd3..4b0604c 100644
--- a/Source/cmLocalNinjaGenerator.cxx
+++ b/Source/cmLocalNinjaGenerator.cxx
@@ -5,11 +5,11 @@
 #include <algorithm>
 #include <cassert>
 #include <cstdio>
-#include <iterator>
 #include <memory>
 #include <sstream>
 #include <utility>
 
+#include <cm/unordered_set>
 #include <cmext/string_view>
 
 #include "cmsys/FStream.hxx"
@@ -22,10 +22,12 @@
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
 #include "cmGlobalNinjaGenerator.h"
+#include "cmList.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
 #include "cmNinjaTargetGenerator.h"
+#include "cmNinjaTypes.h"
 #include "cmPolicies.h"
 #include "cmRulePlaceholderExpander.h"
 #include "cmSourceFile.h"
@@ -39,19 +41,18 @@
 
 cmLocalNinjaGenerator::cmLocalNinjaGenerator(cmGlobalGenerator* gg,
                                              cmMakefile* mf)
-  : cmLocalCommonGenerator(gg, mf, WorkDir::TopBin)
+  : cmLocalCommonGenerator(gg, mf)
 {
 }
 
 // Virtual public methods.
 
-cmRulePlaceholderExpander*
+std::unique_ptr<cmRulePlaceholderExpander>
 cmLocalNinjaGenerator::CreateRulePlaceholderExpander() const
 {
-  cmRulePlaceholderExpander* ret =
-    this->cmLocalGenerator::CreateRulePlaceholderExpander();
+  auto ret = this->cmLocalGenerator::CreateRulePlaceholderExpander();
   ret->SetTargetImpLib("$TARGET_IMPLIB");
-  return ret;
+  return std::unique_ptr<cmRulePlaceholderExpander>(std::move(ret));
 }
 
 cmLocalNinjaGenerator::~cmLocalNinjaGenerator() = default;
@@ -186,6 +187,26 @@
   return static_cast<cmGlobalNinjaGenerator*>(this->GetGlobalGenerator());
 }
 
+std::string const& cmLocalNinjaGenerator::GetWorkingDirectory() const
+{
+  return this->GetState()->GetBinaryDirectory();
+}
+
+std::string cmLocalNinjaGenerator::MaybeRelativeToWorkDir(
+  std::string const& path) const
+{
+  return this->GetGlobalNinjaGenerator()->NinjaOutputPath(
+    this->MaybeRelativeToTopBinDir(path));
+}
+
+std::string cmLocalNinjaGenerator::GetLinkDependencyFile(
+  cmGeneratorTarget* target, std::string const& config) const
+{
+  return cmStrCat(target->GetSupportDirectory(),
+                  this->GetGlobalNinjaGenerator()->ConfigDirectory(config),
+                  "/link.d");
+}
+
 // Virtual protected methods.
 
 std::string cmLocalNinjaGenerator::ConvertToIncludeReference(
@@ -300,7 +321,7 @@
   if (jobpools) {
     cmGlobalNinjaGenerator::WriteComment(
       os, "Pools defined by global property JOB_POOLS");
-    std::vector<std::string> pools = cmExpandedList(*jobpools);
+    cmList pools{ *jobpools };
     for (std::string const& pool : pools) {
       const std::string::size_type eq = pool.find('=');
       unsigned int jobs;
@@ -601,34 +622,32 @@
       continue;
     }
 
-    cmNinjaDeps orderOnlyDeps;
+    std::unordered_set<std::string> orderOnlyDeps;
 
-    // A custom command may appear on multiple targets.  However, some build
-    // systems exist where the target dependencies on some of the targets are
-    // overspecified, leading to a dependency cycle.  If we assume all target
-    // dependencies are a superset of the true target dependencies for this
-    // custom command, we can take the set intersection of all target
-    // dependencies to obtain a correct dependency list.
-    //
-    // FIXME: This won't work in certain obscure scenarios involving indirect
-    // dependencies.
-    auto j = targets.begin();
-    assert(j != targets.end());
-    this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure(
-      *j, orderOnlyDeps, ccg.GetOutputConfig(), fileConfig, ccgs.size() > 1);
-    std::sort(orderOnlyDeps.begin(), orderOnlyDeps.end());
-    ++j;
-
-    for (; j != targets.end(); ++j) {
-      std::vector<std::string> jDeps;
-      std::vector<std::string> depsIntersection;
+    if (!cc->GetDependsExplicitOnly()) {
+      // A custom command may appear on multiple targets.  However, some build
+      // systems exist where the target dependencies on some of the targets are
+      // overspecified, leading to a dependency cycle.  If we assume all target
+      // dependencies are a superset of the true target dependencies for this
+      // custom command, we can take the set intersection of all target
+      // dependencies to obtain a correct dependency list.
+      //
+      // FIXME: This won't work in certain obscure scenarios involving indirect
+      // dependencies.
+      auto j = targets.begin();
+      assert(j != targets.end());
       this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure(
-        *j, jDeps, ccg.GetOutputConfig(), fileConfig, ccgs.size() > 1);
-      std::sort(jDeps.begin(), jDeps.end());
-      std::set_intersection(orderOnlyDeps.begin(), orderOnlyDeps.end(),
-                            jDeps.begin(), jDeps.end(),
-                            std::back_inserter(depsIntersection));
-      orderOnlyDeps = depsIntersection;
+        *j, orderOnlyDeps, ccg.GetOutputConfig(), fileConfig, ccgs.size() > 1);
+      ++j;
+
+      for (; j != targets.end(); ++j) {
+        std::unordered_set<std::string> jDeps;
+        this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure(
+          *j, jDeps, ccg.GetOutputConfig(), fileConfig, ccgs.size() > 1);
+        cm::erase_if(orderOnlyDeps, [&jDeps](std::string const& dep) {
+          return jDeps.find(dep) == jDeps.end();
+        });
+      }
     }
 
     const std::vector<std::string>& outputs = ccg.GetOutputs();
@@ -656,13 +675,17 @@
     std::vector<std::string> cmdLines;
     this->AppendCustomCommandLines(ccg, cmdLines);
 
+    cmNinjaDeps sortedOrderOnlyDeps(orderOnlyDeps.begin(),
+                                    orderOnlyDeps.end());
+    std::sort(sortedOrderOnlyDeps.begin(), sortedOrderOnlyDeps.end());
+
     if (cmdLines.empty()) {
       cmNinjaBuild build("phony");
       build.Comment = cmStrCat("Phony custom command for ", mainOutput);
       build.Outputs = std::move(ccOutputs.ExplicitOuts);
       build.WorkDirOuts = std::move(ccOutputs.WorkDirOuts);
       build.ExplicitDeps = std::move(ninjaDeps);
-      build.OrderOnlyDeps = orderOnlyDeps;
+      build.OrderOnlyDeps = std::move(sortedOrderOnlyDeps);
       gg->WriteBuild(this->GetImplFileStream(fileConfig), build);
     } else {
       std::string customStep = cmSystemTools::GetFilenameName(mainOutput);
@@ -708,7 +731,8 @@
         this->ConstructComment(ccg), comment, depfile, cc->GetJobPool(),
         cc->GetUsesTerminal(),
         /*restat*/ !symbolic || !byproducts.empty(), fileConfig,
-        std::move(ccOutputs), std::move(ninjaDeps), std::move(orderOnlyDeps));
+        std::move(ccOutputs), std::move(ninjaDeps),
+        std::move(sortedOrderOnlyDeps));
     }
   }
 }
@@ -888,8 +912,7 @@
   }
   vars.Output = output.c_str();
 
-  std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander(
-    this->CreateRulePlaceholderExpander());
+  auto rulePlaceholderExpander = this->CreateRulePlaceholderExpander();
 
   std::string launcher = *property_value;
   rulePlaceholderExpander->ExpandRuleVariables(this, launcher, vars);
@@ -904,14 +927,11 @@
 {
   if (cmValue prop_value =
         this->Makefile->GetProperty("ADDITIONAL_CLEAN_FILES")) {
-    std::vector<std::string> cleanFiles;
-    {
-      cmExpandList(cmGeneratorExpression::Evaluate(*prop_value, this, config),
-                   cleanFiles);
-    }
+    cmList cleanFiles{ cmGeneratorExpression::Evaluate(*prop_value, this,
+                                                       config) };
     std::string const& binaryDir = this->GetCurrentBinaryDirectory();
     cmGlobalNinjaGenerator* gg = this->GetGlobalNinjaGenerator();
-    for (std::string const& cleanFile : cleanFiles) {
+    for (auto const& cleanFile : cleanFiles) {
       // Support relative paths
       gg->AddAdditionalCleanFile(
         cmSystemTools::CollapseFullPath(cleanFile, binaryDir), config);
diff --git a/Source/cmLocalNinjaGenerator.h b/Source/cmLocalNinjaGenerator.h
index 4d393d9..74b8b3b 100644
--- a/Source/cmLocalNinjaGenerator.h
+++ b/Source/cmLocalNinjaGenerator.h
@@ -6,6 +6,7 @@
 
 #include <iosfwd>
 #include <map>
+#include <memory>
 #include <set>
 #include <string>
 #include <vector>
@@ -41,7 +42,8 @@
 
   void Generate() override;
 
-  cmRulePlaceholderExpander* CreateRulePlaceholderExpander() const override;
+  std::unique_ptr<cmRulePlaceholderExpander> CreateRulePlaceholderExpander()
+    const override;
 
   std::string GetTargetDirectory(
     cmGeneratorTarget const* target) const override;
@@ -52,6 +54,10 @@
   const cmake* GetCMakeInstance() const;
   cmake* GetCMakeInstance();
 
+  std::string const& GetWorkingDirectory() const override;
+
+  std::string MaybeRelativeToWorkDir(std::string const& path) const override;
+
   /// @returns the relative path between the HomeOutputDirectory and this
   /// local generators StartOutputDirectory.
   std::string GetHomeRelativeOutputPath() const
@@ -90,6 +96,9 @@
   bool HasUniqueByproducts(std::vector<std::string> const& byproducts,
                            cmListFileBacktrace const& bt);
 
+  std::string GetLinkDependencyFile(cmGeneratorTarget* target,
+                                    std::string const& config) const override;
+
 protected:
   std::string ConvertToIncludeReference(
     std::string const& path, cmOutputConverter::OutputFormat format) override;
diff --git a/Source/cmLocalUnixMakefileGenerator3.cxx b/Source/cmLocalUnixMakefileGenerator3.cxx
index 7172d34..cfe4eb6 100644
--- a/Source/cmLocalUnixMakefileGenerator3.cxx
+++ b/Source/cmLocalUnixMakefileGenerator3.cxx
@@ -29,6 +29,7 @@
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
 #include "cmGlobalUnixMakefileGenerator3.h"
+#include "cmList.h"
 #include "cmListFileCache.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
@@ -110,7 +111,7 @@
 
 cmLocalUnixMakefileGenerator3::cmLocalUnixMakefileGenerator3(
   cmGlobalGenerator* gg, cmMakefile* mf)
-  : cmLocalCommonGenerator(gg, mf, WorkDir::CurBin)
+  : cmLocalCommonGenerator(gg, mf)
 {
   this->MakefileVariableSize = 0;
   this->ColorMakefile = false;
@@ -238,6 +239,12 @@
   }
 }
 
+std::string cmLocalUnixMakefileGenerator3::GetLinkDependencyFile(
+  cmGeneratorTarget* target, std::string const& /*config*/) const
+{
+  return cmStrCat(target->GetSupportDirectory(), "/link.d");
+}
+
 void cmLocalUnixMakefileGenerator3::WriteLocalMakefile()
 {
   // generate the includes
@@ -962,8 +969,7 @@
     *content << dir;
   }
 
-  std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander(
-    this->CreateRulePlaceholderExpander());
+  auto rulePlaceholderExpander = this->CreateRulePlaceholderExpander();
 
   // Add each command line to the set of commands.
   std::vector<std::string> commands1;
@@ -1003,7 +1009,9 @@
 
       std::string launcher;
       // Short-circuit if there is no launcher.
-      cmValue val = this->GetRuleLauncher(target, "RULE_LAUNCH_CUSTOM");
+      std::string val = this->GetRuleLauncher(
+        target, "RULE_LAUNCH_CUSTOM",
+        this->Makefile->GetSafeDefinition("CMAKE_BUILD_TYPE"));
       if (cmNonempty(val)) {
         // Expand rule variables referenced in the given launcher command.
         cmRulePlaceholderExpander::RuleVariables vars;
@@ -1022,7 +1030,7 @@
         }
         vars.Output = output.c_str();
 
-        launcher = *val;
+        launcher = val;
         rulePlaceholderExpander->ExpandRuleVariables(this, launcher, vars);
         if (!launcher.empty()) {
           launcher += " ";
@@ -1127,14 +1135,13 @@
 void cmLocalUnixMakefileGenerator3::AppendDirectoryCleanCommand(
   std::vector<std::string>& commands)
 {
-  std::vector<std::string> cleanFiles;
+  cmList cleanFiles;
   // Look for additional files registered for cleaning in this directory.
   if (cmValue prop_value =
         this->Makefile->GetProperty("ADDITIONAL_CLEAN_FILES")) {
-    cmExpandList(cmGeneratorExpression::Evaluate(
-                   *prop_value, this,
-                   this->Makefile->GetSafeDefinition("CMAKE_BUILD_TYPE")),
-                 cleanFiles);
+    cleanFiles.assign(cmGeneratorExpression::Evaluate(
+      *prop_value, this,
+      this->Makefile->GetSafeDefinition("CMAKE_BUILD_TYPE")));
   }
   if (cleanFiles.empty()) {
     return;
@@ -1432,7 +1439,7 @@
     this->Makefile->GetSafeDefinition("CMAKE_DEPENDS_DEPENDENCY_FILES");
   if (!depends.empty()) {
     // dependencies are managed by compiler
-    auto depFiles = cmExpandedList(depends, true);
+    cmList depFiles{ depends, cmList::EmptyElements::Yes };
     std::string const internalDepFile =
       targetDir + "/compiler_depend.internal";
     std::string const depFile = targetDir + "/compiler_depend.make";
@@ -1554,8 +1561,7 @@
   this->WriteDisclaimer(internalRuleFileStream);
 
   // for each language we need to scan, scan it
-  std::vector<std::string> langs =
-    cmExpandedList(mf->GetSafeDefinition("CMAKE_DEPENDS_LANGUAGES"));
+  cmList langs{ mf->GetSafeDefinition("CMAKE_DEPENDS_LANGUAGES") };
   for (std::string const& lang : langs) {
     // construct the checker
     // Create the scanner for this language
@@ -1600,7 +1606,7 @@
   }
 
   // Convert the string to a list and preserve empty entries.
-  std::vector<std::string> pairs = cmExpandedList(*pairs_string, true);
+  cmList pairs{ *pairs_string, cmList::EmptyElements::Yes };
   for (auto i = pairs.begin(); i != pairs.end() && (i + 1) != pairs.end();) {
     const std::string& depender = *i++;
     const std::string& dependee = *i++;
@@ -1820,7 +1826,7 @@
   if (!infoDef) {
     return;
   }
-  std::vector<std::string> files = cmExpandedList(*infoDef);
+  cmList files{ *infoDef };
 
   // Each depend information file corresponds to a target.  Clear the
   // dependencies for that target.
@@ -1861,7 +1867,7 @@
       cmSystemTools::Touch(DepTimestamp.GenericString(), true);
 
       // clear the dependencies files generated by the compiler
-      std::vector<std::string> dependencies = cmExpandedList(depsFiles, true);
+      cmList dependencies{ depsFiles, cmList::EmptyElements::Yes };
       cmDependsCompiler depsManager;
       depsManager.SetVerbose(verbose);
       depsManager.ClearDependencies(dependencies);
@@ -1971,14 +1977,14 @@
 
     // Store include transform rule properties.  Write the directory
     // rules first because they may be overridden by later target rules.
-    std::vector<std::string> transformRules;
+    cmList transformRules;
     if (cmValue xform =
           this->Makefile->GetProperty("IMPLICIT_DEPENDS_INCLUDE_TRANSFORM")) {
-      cmExpandList(*xform, transformRules);
+      transformRules.assign(*xform);
     }
     if (cmValue xform =
           target->GetProperty("IMPLICIT_DEPENDS_INCLUDE_TRANSFORM")) {
-      cmExpandList(*xform, transformRules);
+      transformRules.append(*xform);
     }
     if (!transformRules.empty()) {
       cmakefileStream << "\nset(CMAKE_INCLUDE_TRANSFORMS\n";
@@ -2007,6 +2013,18 @@
                           << this->MaybeRelativeToTopBinDir(src) << "\"\n";
         }
       }
+    } else if (compilerLang.first == "LINK"_s) {
+      auto depFormat = this->Makefile->GetDefinition(
+        cmStrCat("CMAKE_", target->GetLinkerLanguage(this->GetConfigName()),
+                 "_LINKER_DEPFILE_FORMAT"));
+      for (auto const& compilerPair : compilerPairs) {
+        for (auto const& src : compilerPair.second) {
+          cmakefileStream << R"(  "" ")"
+                          << this->MaybeRelativeToTopBinDir(compilerPair.first)
+                          << "\" \"" << depFormat << "\" \""
+                          << this->MaybeRelativeToTopBinDir(src) << "\"\n";
+        }
+      }
     } else {
       auto depFormat = this->Makefile->GetSafeDefinition(
         cmStrCat("CMAKE_", compilerLang.first, "_DEPFILE_FORMAT"));
diff --git a/Source/cmLocalUnixMakefileGenerator3.h b/Source/cmLocalUnixMakefileGenerator3.h
index 78aa7f9..7d5a922 100644
--- a/Source/cmLocalUnixMakefileGenerator3.h
+++ b/Source/cmLocalUnixMakefileGenerator3.h
@@ -191,6 +191,9 @@
   // Eclipse generator.
   void GetIndividualFileTargets(std::vector<std::string>& targets);
 
+  std::string GetLinkDependencyFile(cmGeneratorTarget* target,
+                                    std::string const& config) const override;
+
 protected:
   void WriteLocalMakefile();
 
diff --git a/Source/cmLocalVisualStudio7Generator.cxx b/Source/cmLocalVisualStudio7Generator.cxx
index ded1647..a7ea0df 100644
--- a/Source/cmLocalVisualStudio7Generator.cxx
+++ b/Source/cmLocalVisualStudio7Generator.cxx
@@ -29,6 +29,7 @@
 #include "cmGlobalGenerator.h"
 #include "cmGlobalVisualStudio7Generator.h"
 #include "cmGlobalVisualStudioGenerator.h"
+#include "cmList.h"
 #include "cmListFileCache.h"
 #include "cmMakefile.h"
 #include "cmOutputConverter.h"
@@ -141,7 +142,6 @@
       cc->SetOutputs(force);
       cc->SetCommandLines(force_commands);
       cc->SetComment(" ");
-      cc->SetCMP0116Status(cmPolicies::NEW);
       if (cmSourceFile* file =
             this->AddCustomCommandToOutput(std::move(cc), true)) {
         l->AddSource(file->ResolveFullPath());
@@ -269,9 +269,9 @@
   cc->SetDepends(listFiles);
   cc->SetCommandLines(commandLines);
   cc->SetComment(comment.c_str());
-  cc->SetCMP0116Status(cmPolicies::NEW);
   cc->SetEscapeOldStyle(false);
   cc->SetStdPipesUTF8(true);
+  cc->SetUsesTerminal(true);
   this->AddCustomCommandToOutput(std::move(cc), true);
   if (cmSourceFile* file = this->Makefile->GetSource(makefileIn)) {
     // Finalize the source file path now since we're adding this after
@@ -1577,12 +1577,12 @@
 
     // Check for extra object-file dependencies.
     if (cmValue deps = sf.GetProperty("OBJECT_DEPENDS")) {
-      std::vector<std::string> depends = cmExpandedList(*deps);
-      const char* sep = "";
-      for (const std::string& d : depends) {
-        fc.AdditionalDeps += sep;
-        fc.AdditionalDeps += lg->ConvertToXMLOutputPath(d);
-        sep = ";";
+      cmList depends{ *deps };
+      if (!depends.empty()) {
+        for (std::string& d : depends) {
+          d = lg->ConvertToXMLOutputPath(d);
+        }
+        fc.AdditionalDeps += depends.to_string();
         needfc = true;
       }
     }
diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx
index d26f383..0af0ed0 100644
--- a/Source/cmMakefile.cxx
+++ b/Source/cmMakefile.cxx
@@ -43,6 +43,7 @@
 #include "cmGlobalGenerator.h"
 #include "cmInstallGenerator.h" // IWYU pragma: keep
 #include "cmInstallSubdirectoryGenerator.h"
+#include "cmList.h"
 #include "cmListFileCache.h"
 #include "cmLocalGenerator.h"
 #include "cmMessageType.h"
@@ -67,6 +68,28 @@
 #  include "cmVariableWatch.h"
 #endif
 
+#ifdef CMake_ENABLE_DEBUGGER
+#  include "cmDebuggerAdapter.h"
+#endif
+
+#ifndef __has_feature
+#  define __has_feature(x) 0
+#endif
+
+// Select a recursion limit that fits within the stack size.
+// See stack size flags in '../CompileFlags.cmake'.
+#ifndef CMake_DEFAULT_RECURSION_LIMIT
+#  if __has_feature(address_sanitizer)
+#    define CMake_DEFAULT_RECURSION_LIMIT 400
+#  elif defined(_MSC_VER) && defined(_DEBUG)
+#    define CMake_DEFAULT_RECURSION_LIMIT 600
+#  elif defined(__ibmxl__) && defined(__linux)
+#    define CMake_DEFAULT_RECURSION_LIMIT 600
+#  else
+#    define CMake_DEFAULT_RECURSION_LIMIT 1000
+#  endif
+#endif
+
 class cmMessenger;
 
 cmDirectoryId::cmDirectoryId(std::string s)
@@ -99,7 +122,6 @@
   this->StateSnapshot =
     this->StateSnapshot.GetState()->CreatePolicyScopeSnapshot(
       this->StateSnapshot);
-  this->RecursionDepth = 0;
 
   // Enter a policy level for this directory.
   this->PushPolicy();
@@ -208,32 +230,47 @@
   return true;
 }
 
-void cmMakefile::MaybeWarnCMP0074(std::string const& pkg)
+void cmMakefile::MaybeWarnCMP0074(std::string const& rootVar, cmValue rootDef,
+                                  cm::optional<std::string> const& rootEnv)
 {
-  // Warn if a <pkg>_ROOT variable we may use is set.
-  std::string const varName = pkg + "_ROOT";
-  cmValue var = this->GetDefinition(varName);
-  std::string env;
-  cmSystemTools::GetEnv(varName, env);
-
-  bool const haveVar = cmNonempty(var);
-  bool const haveEnv = !env.empty();
-  if ((haveVar || haveEnv) && this->WarnedCMP0074.insert(varName).second) {
+  // Warn if a <PackageName>_ROOT variable we may use is set.
+  if ((rootDef || rootEnv) && this->WarnedCMP0074.insert(rootVar).second) {
     std::ostringstream w;
     w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0074) << "\n";
-    if (haveVar) {
-      w << "CMake variable " << varName << " is set to:\n"
-        << "  " << *var << "\n";
+    if (rootDef) {
+      w << "CMake variable " << rootVar << " is set to:\n"
+        << "  " << *rootDef << "\n";
     }
-    if (haveEnv) {
-      w << "Environment variable " << varName << " is set to:\n"
-        << "  " << env << "\n";
+    if (rootEnv) {
+      w << "Environment variable " << rootVar << " is set to:\n"
+        << "  " << *rootEnv << "\n";
     }
     w << "For compatibility, CMake is ignoring the variable.";
     this->IssueMessage(MessageType::AUTHOR_WARNING, w.str());
   }
 }
 
+void cmMakefile::MaybeWarnCMP0144(std::string const& rootVAR, cmValue rootDEF,
+                                  cm::optional<std::string> const& rootENV)
+{
+  // Warn if a <PACKAGENAME>_ROOT variable we may use is set.
+  if ((rootDEF || rootENV) && this->WarnedCMP0144.insert(rootVAR).second) {
+    std::ostringstream w;
+    w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0144) << "\n";
+    if (rootDEF) {
+      w << "CMake variable " << rootVAR << " is set to:\n"
+        << "  " << *rootDEF << "\n";
+    }
+    if (rootENV) {
+      w << "Environment variable " << rootVAR << " is set to:\n"
+        << "  " << *rootENV << "\n";
+    }
+    w << "For compatibility, find_package is ignoring the variable, but "
+         "code in a .cmake module might still use it.";
+    this->IssueMessage(MessageType::AUTHOR_WARNING, w.str());
+  }
+}
+
 cmBTStringRange cmMakefile::GetIncludeDirectoriesEntries() const
 {
   return this->StateSnapshot.GetDirectory().GetIncludeDirectoriesEntries();
@@ -308,7 +345,7 @@
   cm::optional<std::string> const& deferId = bt.Top().DeferId;
 
   switch (this->GetCMakeInstance()->GetTraceFormat()) {
-    case cmake::TraceFormat::TRACE_JSON_V1: {
+    case cmake::TraceFormat::JSONv1: {
 #ifndef CMAKE_BOOTSTRAP
       Json::Value val;
       Json::StreamWriterBuilder builder;
@@ -335,7 +372,7 @@
 #endif
       break;
     }
-    case cmake::TraceFormat::TRACE_HUMAN:
+    case cmake::TraceFormat::Human:
       msg << full_path << "(" << lff.Line() << "):";
       if (deferId) {
         msg << "DEFERRED:" << *deferId << ":";
@@ -347,8 +384,8 @@
       }
       msg << ")";
       break;
-    case cmake::TraceFormat::TRACE_UNDEFINED:
-      msg << "INTERNAL ERROR: Trace format is TRACE_UNDEFINED";
+    case cmake::TraceFormat::Undefined:
+      msg << "INTERNAL ERROR: Trace format is Undefined";
       break;
   }
 
@@ -391,6 +428,13 @@
           return argsValue;
         });
 #endif
+#ifdef CMake_ENABLE_DEBUGGER
+    if (this->Makefile->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+      this->Makefile->GetCMakeInstance()
+        ->GetDebugAdapter()
+        ->OnBeginFunctionCall(mf, lfc.FilePath, lff);
+    }
+#endif
   }
 
   ~cmMakefileCall()
@@ -401,6 +445,13 @@
     this->Makefile->ExecutionStatusStack.pop_back();
     --this->Makefile->RecursionDepth;
     this->Makefile->Backtrace = this->Makefile->Backtrace.Pop();
+#ifdef CMake_ENABLE_DEBUGGER
+    if (this->Makefile->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+      this->Makefile->GetCMakeInstance()
+        ->GetDebugAdapter()
+        ->OnEndFunctionCall();
+    }
+#endif
   }
 
   cmMakefileCall(const cmMakefileCall&) = delete;
@@ -439,18 +490,10 @@
   static_cast<void>(stack_manager);
 
   // Check for maximum recursion depth.
-  int depth = CMake_DEFAULT_RECURSION_LIMIT;
-  cmValue depthStr = this->GetDefinition("CMAKE_MAXIMUM_RECURSION_DEPTH");
-  if (depthStr) {
-    std::istringstream s(*depthStr);
-    int d;
-    if (s >> d) {
-      depth = d;
-    }
-  }
-  if (this->RecursionDepth > depth) {
+  size_t depthLimit = this->GetRecursionDepthLimit();
+  if (this->RecursionDepth > depthLimit) {
     std::ostringstream e;
-    e << "Maximum recursion depth of " << depth << " exceeded";
+    e << "Maximum recursion depth of " << depthLimit << " exceeded";
     this->IssueMessage(MessageType::FATAL_ERROR, e.str());
     cmSystemTools::SetFatalErrorOccurred();
     return false;
@@ -638,12 +681,33 @@
 
   IncludeScope incScope(this, filenametoread, noPolicyScope);
 
+#ifdef CMake_ENABLE_DEBUGGER
+  if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+    this->GetCMakeInstance()->GetDebugAdapter()->OnBeginFileParse(
+      this, filenametoread);
+  }
+#endif
+
   cmListFile listFile;
   if (!listFile.ParseFile(filenametoread.c_str(), this->GetMessenger(),
                           this->Backtrace)) {
+#ifdef CMake_ENABLE_DEBUGGER
+    if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+      this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse();
+    }
+#endif
+
     return false;
   }
 
+#ifdef CMake_ENABLE_DEBUGGER
+  if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+    this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse();
+    this->GetCMakeInstance()->GetDebugAdapter()->OnFileParsedSuccessfully(
+      filenametoread, listFile.Functions);
+  }
+#endif
+
   this->RunListFile(listFile, filenametoread);
   if (cmSystemTools::GetFatalErrorOccurred()) {
     incScope.Quiet();
@@ -739,12 +803,33 @@
 
   ListFileScope scope(this, filenametoread);
 
+#ifdef CMake_ENABLE_DEBUGGER
+  if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+    this->GetCMakeInstance()->GetDebugAdapter()->OnBeginFileParse(
+      this, filenametoread);
+  }
+#endif
+
   cmListFile listFile;
   if (!listFile.ParseFile(filenametoread.c_str(), this->GetMessenger(),
                           this->Backtrace)) {
+#ifdef CMake_ENABLE_DEBUGGER
+    if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+      this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse();
+    }
+#endif
+
     return false;
   }
 
+#ifdef CMake_ENABLE_DEBUGGER
+  if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+    this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse();
+    this->GetCMakeInstance()->GetDebugAdapter()->OnFileParsedSuccessfully(
+      filenametoread, listFile.Functions);
+  }
+#endif
+
   this->RunListFile(listFile, filenametoread);
   if (cmSystemTools::GetFatalErrorOccurred()) {
     scope.Quiet();
@@ -766,6 +851,13 @@
     return false;
   }
 
+#ifdef CMake_ENABLE_DEBUGGER
+  if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+    this->GetCMakeInstance()->GetDebugAdapter()->OnFileParsedSuccessfully(
+      filenametoread, listFile.Functions);
+  }
+#endif
+
   this->RunListFile(listFile, filenametoread);
   if (cmSystemTools::GetFatalErrorOccurred()) {
     scope.Quiet();
@@ -1117,7 +1209,7 @@
   // Always create the byproduct sources and mark them generated.
   this->CreateGeneratedOutputs(byproducts);
 
-  cc->SetCMP0116Status(this->GetPolicyStatus(cmPolicies::CMP0116));
+  cc->RecordPolicyValues(this->GetStateSnapshot());
 
   // Dispatch command creation to allow generator expressions in outputs.
   this->AddGeneratorAction(
@@ -1156,7 +1248,7 @@
   this->CreateGeneratedOutputs(outputs);
   this->CreateGeneratedOutputs(byproducts);
 
-  cc->SetCMP0116Status(this->GetPolicyStatus(cmPolicies::CMP0116));
+  cc->RecordPolicyValues(this->GetStateSnapshot());
 
   // Dispatch command creation to allow generator expressions in outputs.
   this->AddGeneratorAction(
@@ -1214,7 +1306,7 @@
 
   // Each output must get its own copy of this rule.
   cmsys::RegularExpression sourceFiles(
-    "\\.(C|M|c|c\\+\\+|cc|cpp|cxx|mpp|ixx|cppm|cu|m|mm|"
+    "\\.(C|M|c|c\\+\\+|cc|cpp|cxx|mpp|ixx|cppm|ccm|cxxm|c\\+\\+m|cu|m|mm|"
     "rc|def|r|odl|idl|hpj|bat|h|h\\+\\+|"
     "hm|hpp|hxx|in|txx|inl)$");
 
@@ -1274,7 +1366,7 @@
   // Always create the byproduct sources and mark them generated.
   this->CreateGeneratedOutputs(byproducts);
 
-  cc->SetCMP0116Status(this->GetPolicyStatus(cmPolicies::CMP0116));
+  cc->RecordPolicyValues(this->GetStateSnapshot());
 
   // Dispatch command creation to allow generator expressions in outputs.
   this->AddGeneratorAction(
@@ -1424,15 +1516,13 @@
   if (remove) {
     if (cmValue cdefs = this->GetProperty("COMPILE_DEFINITIONS")) {
       // Expand the list.
-      std::vector<std::string> defs = cmExpandedList(*cdefs);
+      cmList defs{ *cdefs };
 
       // Recompose the list without the definition.
-      auto defEnd = std::remove(defs.begin(), defs.end(), define);
-      auto defBegin = defs.begin();
-      std::string ndefs = cmJoin(cmMakeRange(defBegin, defEnd), ";");
+      defs.remove_items({ define });
 
       // Store the new list.
-      this->SetProperty("COMPILE_DEFINITIONS", ndefs);
+      this->SetProperty("COMPILE_DEFINITIONS", defs.to_string());
     }
   } else {
     // Append the definition to the directory property.
@@ -1635,11 +1725,33 @@
   assert(cmSystemTools::FileExists(currentStart, true));
   this->AddDefinition("CMAKE_PARENT_LIST_FILE", currentStart);
 
+#ifdef CMake_ENABLE_DEBUGGER
+  if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+    this->GetCMakeInstance()->GetDebugAdapter()->OnBeginFileParse(
+      this, currentStart);
+  }
+#endif
+
   cmListFile listFile;
   if (!listFile.ParseFile(currentStart.c_str(), this->GetMessenger(),
                           this->Backtrace)) {
+#ifdef CMake_ENABLE_DEBUGGER
+    if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+      this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse();
+    }
+#endif
+
     return;
   }
+
+#ifdef CMake_ENABLE_DEBUGGER
+  if (this->GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+    this->GetCMakeInstance()->GetDebugAdapter()->OnEndFileParse();
+    this->GetCMakeInstance()->GetDebugAdapter()->OnFileParsedSuccessfully(
+      currentStart, listFile.Functions);
+  }
+#endif
+
   if (this->IsRootMakefile()) {
     bool hasVersion = false;
     // search for the right policy command
@@ -1916,8 +2028,8 @@
   this->AddDefinition(name, value ? "ON" : "OFF");
 }
 
-void cmMakefile::AddCacheDefinition(const std::string& name, const char* value,
-                                    const char* doc,
+void cmMakefile::AddCacheDefinition(const std::string& name, cmValue value,
+                                    cmValue doc,
                                     cmStateEnums::CacheEntryType type,
                                     bool force)
 {
@@ -1931,28 +2043,20 @@
     // if this is not a force, then use the value from the cache
     // if it is a force, then use the value being passed in
     if (!force) {
-      value = existingValue->c_str();
+      value = existingValue;
     }
     if (type == cmStateEnums::PATH || type == cmStateEnums::FILEPATH) {
-      std::vector<std::string>::size_type cc;
-      std::vector<std::string> files;
-      nvalue = value ? value : "";
-
-      cmExpandList(nvalue, files);
-      nvalue.clear();
-      for (cc = 0; cc < files.size(); cc++) {
-        if (!cmIsOff(files[cc])) {
-          files[cc] = cmSystemTools::CollapseFullPath(files[cc]);
+      cmList files(value);
+      for (auto& file : files) {
+        if (!cmIsOff(file)) {
+          file = cmSystemTools::CollapseFullPath(file);
         }
-        if (cc > 0) {
-          nvalue += ";";
-        }
-        nvalue += files[cc];
       }
+      nvalue = files.to_string();
+      value = cmValue{ nvalue };
 
-      this->GetCMakeInstance()->AddCacheEntry(name, nvalue, doc, type);
-      nvalue = *this->GetState()->GetInitializedCacheValue(name);
-      value = nvalue.c_str();
+      this->GetCMakeInstance()->AddCacheEntry(name, value, doc, type);
+      value = this->GetState()->GetInitializedCacheValue(name);
     }
   }
   this->GetCMakeInstance()->AddCacheEntry(name, value, doc, type);
@@ -2040,7 +2144,7 @@
   }
 
   if (cmValue linkLibsProp = this->GetProperty("LINK_LIBRARIES")) {
-    std::vector<std::string> linkLibs = cmExpandedList(*linkLibsProp);
+    cmList linkLibs{ *linkLibsProp };
 
     for (auto j = linkLibs.begin(); j != linkLibs.end(); ++j) {
       std::string libraryName = *j;
@@ -2115,12 +2219,21 @@
   return &this->CreateNewTarget(name, type).first;
 }
 
+cmTarget* cmMakefile::AddSynthesizedTarget(cmStateEnums::TargetType type,
+                                           const std::string& name)
+{
+  return &this
+            ->CreateNewTarget(name, type, cmTarget::PerConfig::Yes,
+                              cmTarget::Visibility::Generated)
+            .first;
+}
+
 std::pair<cmTarget&, bool> cmMakefile::CreateNewTarget(
   const std::string& name, cmStateEnums::TargetType type,
-  cmTarget::PerConfig perConfig)
+  cmTarget::PerConfig perConfig, cmTarget::Visibility vis)
 {
-  auto ib = this->Targets.emplace(
-    name, cmTarget(name, type, cmTarget::VisibilityNormal, this, perConfig));
+  auto ib =
+    this->Targets.emplace(name, cmTarget(name, type, vis, this, perConfig));
   auto it = ib.first;
   if (!ib.second) {
     return std::make_pair(std::ref(it->second), false);
@@ -2345,7 +2458,7 @@
   }
 
   if (cmValue linkLibsProp = this->GetProperty("LINK_LIBRARIES")) {
-    std::vector<std::string> linkLibs = cmExpandedList(*linkLibsProp);
+    cmList linkLibs{ *linkLibsProp };
 
     for (auto l = linkLibs.begin(); l != linkLibs.end(); ++l) {
       std::string libName = *l;
@@ -2469,6 +2582,11 @@
   return this->GetAppleSDKType() != AppleSDK::MacOS;
 }
 
+bool cmMakefile::PlatformSupportsAppleTextStubs() const
+{
+  return this->IsOn("APPLE") && this->IsSet("CMAKE_TAPI");
+}
+
 const char* cmMakefile::GetSONameFlag(const std::string& language) const
 {
   std::string name = "CMAKE_SHARED_LIBRARY_SONAME";
@@ -2576,18 +2694,6 @@
   return this->GetDefinition(name);
 }
 
-bool cmMakefile::GetDefExpandList(const std::string& name,
-                                  std::vector<std::string>& out,
-                                  bool emptyArgs) const
-{
-  cmValue def = this->GetDefinition(name);
-  if (!def) {
-    return false;
-  }
-  cmExpandList(*def, out, emptyArgs);
-  return true;
-}
-
 std::vector<std::string> cmMakefile::GetDefinitions() const
 {
   std::vector<std::string> res = this->StateSnapshot.ClosureKeys();
@@ -2836,12 +2942,31 @@
      !cmSystemTools::IsSubDirectory(filename, "/CMakeFiles"));
 }
 
-int cmMakefile::GetRecursionDepth() const
+size_t cmMakefile::GetRecursionDepthLimit() const
+{
+  size_t depth = CMake_DEFAULT_RECURSION_LIMIT;
+  if (cmValue depthStr =
+        this->GetDefinition("CMAKE_MAXIMUM_RECURSION_DEPTH")) {
+    unsigned long depthUL;
+    if (cmStrToULong(depthStr.GetCStr(), &depthUL)) {
+      depth = depthUL;
+    }
+  } else if (cm::optional<std::string> depthEnv =
+               cmSystemTools::GetEnvVar("CMAKE_MAXIMUM_RECURSION_DEPTH")) {
+    unsigned long depthUL;
+    if (cmStrToULong(*depthEnv, &depthUL)) {
+      depth = depthUL;
+    }
+  }
+  return depth;
+}
+
+size_t cmMakefile::GetRecursionDepth() const
 {
   return this->RecursionDepth;
 }
 
-void cmMakefile::SetRecursionDepth(int recursionDepth)
+void cmMakefile::SetRecursionDepth(size_t recursionDepth)
 {
   this->RecursionDepth = recursionDepth;
 }
@@ -3209,9 +3334,9 @@
 std::vector<std::string> cmMakefile::GetGeneratorConfigs(
   GeneratorConfigQuery mode) const
 {
-  std::vector<std::string> configs;
+  cmList configs;
   if (this->GetGlobalGenerator()->IsMultiConfig()) {
-    this->GetDefExpandList("CMAKE_CONFIGURATION_TYPES", configs);
+    configs.assign(this->GetDefinition("CMAKE_CONFIGURATION_TYPES"));
   } else if (mode != cmMakefile::OnlyMultiConfig) {
     const std::string& buildType = this->GetSafeDefinition("CMAKE_BUILD_TYPE");
     if (!buildType.empty()) {
@@ -3221,7 +3346,7 @@
   if (mode == cmMakefile::IncludeEmptyConfig && configs.empty()) {
     configs.emplace_back();
   }
-  return configs;
+  return std::move(configs.data());
 }
 
 bool cmMakefile::IsFunctionBlocked(const cmListFileFunction& lff,
@@ -3349,7 +3474,7 @@
     if (i.Delim == cmListFileArgument::Quoted) {
       outArgs.emplace_back(value, true);
     } else {
-      std::vector<std::string> stringArgs = cmExpandedList(value);
+      cmList stringArgs{ value };
       for (std::string const& stringArg : stringArgs) {
         outArgs.emplace_back(stringArg, false);
       }
@@ -3731,6 +3856,12 @@
     return;
   }
   cm->UpdateProgress(message, s);
+
+#ifdef CMake_ENABLE_DEBUGGER
+  if (cm->GetDebugAdapter() != nullptr) {
+    cm->GetDebugAdapter()->OnMessageOutput(MessageType::MESSAGE, message);
+  }
+#endif
 }
 
 std::string cmMakefile::GetModulesFile(const std::string& filename,
@@ -3755,7 +3886,7 @@
   // Always search in CMAKE_MODULE_PATH:
   cmValue cmakeModulePath = this->GetDefinition("CMAKE_MODULE_PATH");
   if (cmakeModulePath) {
-    std::vector<std::string> modulePath = cmExpandedList(*cmakeModulePath);
+    cmList modulePath{ *cmakeModulePath };
 
     // Look through the possible module directories.
     for (std::string itempl : modulePath) {
@@ -4006,10 +4137,6 @@
   return res;
 }
 
-void cmMakefile::SetProperty(const std::string& prop, const char* value)
-{
-  this->StateSnapshot.GetDirectory().SetProperty(prop, value, this->Backtrace);
-}
 void cmMakefile::SetProperty(const std::string& prop, cmValue value)
 {
   this->StateSnapshot.GetDirectory().SetProperty(prop, value, this->Backtrace);
@@ -4098,11 +4225,11 @@
 
 void cmMakefile::AddCMakeDependFilesFromUser()
 {
-  std::vector<std::string> deps;
+  cmList deps;
   if (cmValue deps_str = this->GetProperty("CMAKE_CONFIGURE_DEPENDS")) {
-    cmExpandList(*deps_str, deps);
+    deps.assign(*deps_str);
   }
-  for (std::string const& dep : deps) {
+  for (auto const& dep : deps) {
     if (cmSystemTools::FileIsFullPath(dep)) {
       this->AddCMakeDependFile(dep);
     } else {
@@ -4203,8 +4330,8 @@
   // Create the target.
   std::unique_ptr<cmTarget> target(
     new cmTarget(name, type,
-                 global ? cmTarget::VisibilityImportedGlobally
-                        : cmTarget::VisibilityImported,
+                 global ? cmTarget::Visibility::ImportedGlobally
+                        : cmTarget::Visibility::Imported,
                  this, cmTarget::PerConfig::Yes));
 
   // Add to the set of available imported targets.
@@ -4486,7 +4613,7 @@
   }
 
   // Deprecate old policies.
-  if (status == cmPolicies::OLD && id <= cmPolicies::CMP0108 &&
+  if (status == cmPolicies::OLD && id <= cmPolicies::CMP0114 &&
       !(this->GetCMakeInstance()->GetIsInTryCompile() &&
         (
           // Policies set by cmCoreTryCompile::TryCompileCode.
diff --git a/Source/cmMakefile.h b/Source/cmMakefile.h
index 3866aca..7005942 100644
--- a/Source/cmMakefile.h
+++ b/Source/cmMakefile.h
@@ -241,10 +241,13 @@
 
   std::pair<cmTarget&, bool> CreateNewTarget(
     const std::string& name, cmStateEnums::TargetType type,
-    cmTarget::PerConfig perConfig = cmTarget::PerConfig::Yes);
+    cmTarget::PerConfig perConfig = cmTarget::PerConfig::Yes,
+    cmTarget::Visibility vis = cmTarget::Visibility::Normal);
 
   cmTarget* AddNewTarget(cmStateEnums::TargetType type,
                          const std::string& name);
+  cmTarget* AddSynthesizedTarget(cmStateEnums::TargetType type,
+                                 const std::string& name);
 
   /** Create a target instance for the utility.  */
   cmTarget* AddNewUtilityTarget(const std::string& utilityName,
@@ -299,14 +302,23 @@
    */
   void AddDefinitionBool(const std::string& name, bool);
   //! Add a definition to this makefile and the global cmake cache.
-  void AddCacheDefinition(const std::string& name, const char* value,
-                          const char* doc, cmStateEnums::CacheEntryType type,
+  void AddCacheDefinition(const std::string& name, cmValue value, cmValue doc,
+                          cmStateEnums::CacheEntryType type,
                           bool force = false);
-  void AddCacheDefinition(const std::string& name, const std::string& value,
-                          const char* doc, cmStateEnums::CacheEntryType type,
+  void AddCacheDefinition(const std::string& name, cmValue value,
+                          const std::string& doc,
+                          cmStateEnums::CacheEntryType type,
                           bool force = false)
   {
-    this->AddCacheDefinition(name, value.c_str(), doc, type, force);
+    this->AddCacheDefinition(name, value, cmValue{ doc }, type, force);
+  }
+  void AddCacheDefinition(const std::string& name, const std::string& value,
+                          const std::string& doc,
+                          cmStateEnums::CacheEntryType type,
+                          bool force = false)
+  {
+    this->AddCacheDefinition(name, cmValue{ value }, cmValue{ doc }, type,
+                             force);
   }
 
   /**
@@ -422,7 +434,7 @@
    */
   void SetIncludeRegularExpression(const std::string& regex)
   {
-    this->SetProperty("INCLUDE_REGULAR_EXPRESSION", regex.c_str());
+    this->SetProperty("INCLUDE_REGULAR_EXPRESSION", regex);
   }
   const std::string& GetIncludeRegularExpression() const
   {
@@ -515,8 +527,6 @@
   const std::string& GetRequiredDefinition(const std::string& name) const;
   bool IsDefinitionSet(const std::string&) const;
   bool IsNormalDefinitionSet(const std::string&) const;
-  bool GetDefExpandList(const std::string& name, std::vector<std::string>& out,
-                        bool emptyArgs = false) const;
   /**
    * Get the list of all variables in the current space. If argument
    * cacheonly is specified and is greater than 0, then only cache
@@ -559,6 +569,10 @@
   /** Return whether the target platform is Apple iOS.  */
   bool PlatformIsAppleEmbedded() const;
 
+  /** Return whether the target platform supports generation of text base stubs
+     (.tbd file) describing exports (Apple specific). */
+  bool PlatformSupportsAppleTextStubs() const;
+
   /** Retrieve soname flag for the specified language if supported */
   const char* GetSONameFlag(const std::string& language) const;
 
@@ -796,8 +810,11 @@
                              std::string& debugBuffer) const;
 
   //! Set/Get a property of this directory
-  void SetProperty(const std::string& prop, const char* value);
   void SetProperty(const std::string& prop, cmValue value);
+  void SetProperty(const std::string& prop, std::nullptr_t)
+  {
+    this->SetProperty(prop, cmValue{ nullptr });
+  }
   void SetProperty(const std::string& prop, const std::string& value)
   {
     this->SetProperty(prop, cmValue(value));
@@ -1008,13 +1025,18 @@
 
   bool GetDebugFindPkgMode() const;
 
-  void MaybeWarnCMP0074(std::string const& pkg);
+  void MaybeWarnCMP0074(std::string const& rootVar, cmValue rootDef,
+                        cm::optional<std::string> const& rootEnv);
+  void MaybeWarnCMP0144(std::string const& rootVAR, cmValue rootDEF,
+                        cm::optional<std::string> const& rootENV);
   void MaybeWarnUninitialized(std::string const& variable,
                               const char* sourceFilename) const;
   bool IsProjectFile(const char* filename) const;
 
-  int GetRecursionDepth() const;
-  void SetRecursionDepth(int recursionDepth);
+  size_t GetRecursionDepthLimit() const;
+
+  size_t GetRecursionDepth() const;
+  void SetRecursionDepth(size_t recursionDepth);
 
   std::string NewDeferId() const;
   bool DeferCall(std::string id, std::string fileName, cmListFileFunction lff);
@@ -1080,7 +1102,7 @@
 private:
   cmStateSnapshot StateSnapshot;
   cmListFileBacktrace Backtrace;
-  int RecursionDepth;
+  size_t RecursionDepth = 0;
 
   struct DeferCommand
   {
@@ -1186,6 +1208,7 @@
   bool CheckSystemVars;
   bool CheckCMP0000;
   std::set<std::string> WarnedCMP0074;
+  std::set<std::string> WarnedCMP0144;
   bool IsSourceFileTryCompile;
   mutable bool SuppressSideEffects;
   ImportedTargetScope CurrentImportedTargetScope = ImportedTargetScope::Local;
diff --git a/Source/cmMakefileExecutableTargetGenerator.cxx b/Source/cmMakefileExecutableTargetGenerator.cxx
index e53d28c..4a2b9e8 100644
--- a/Source/cmMakefileExecutableTargetGenerator.cxx
+++ b/Source/cmMakefileExecutableTargetGenerator.cxx
@@ -16,6 +16,7 @@
 #include "cmGlobalUnixMakefileGenerator3.h"
 #include "cmLinkLineComputer.h"
 #include "cmLinkLineDeviceComputer.h"
+#include "cmList.h"
 #include "cmLocalGenerator.h"
 #include "cmLocalUnixMakefileGenerator3.h"
 #include "cmMakefile.h"
@@ -69,6 +70,8 @@
     this->WriteExecutableRule(true);
   }
 
+  this->WriteTargetLinkDependRules();
+
   // Write clean target
   this->WriteTargetCleanRules();
 
@@ -151,11 +154,10 @@
   bool useLinkScript = this->GlobalGenerator->GetUseLinkScript();
 
   // Construct the main link rule.
-  std::vector<std::string> real_link_commands;
   const std::string linkRuleVar = "CMAKE_CUDA_DEVICE_LINK_EXECUTABLE";
   const std::string linkRule = this->GetLinkRule(linkRuleVar);
   std::vector<std::string> commands1;
-  cmExpandList(linkRule, real_link_commands);
+  cmList real_link_commands(linkRule);
 
   bool useResponseFileForObjects =
     this->CheckUseResponseFileForObjects(linkLanguage);
@@ -185,14 +187,15 @@
     std::string linkLibs;
     this->CreateLinkLibs(
       linkLineComputer.get(), linkLibs, useResponseFileForLibs, depends,
-      cmMakefileTargetGenerator::ResponseFlagFor::DeviceLink);
+      linkLanguage, cmMakefileTargetGenerator::ResponseFlagFor::DeviceLink);
 
     // Construct object file lists that may be needed to expand the
     // rule.
     std::string buildObjs;
     this->CreateObjectLists(
       useLinkScript, false, useResponseFileForObjects, buildObjs, depends,
-      false, cmMakefileTargetGenerator::ResponseFlagFor::DeviceLink);
+      false, linkLanguage,
+      cmMakefileTargetGenerator::ResponseFlagFor::DeviceLink);
 
     cmRulePlaceholderExpander::RuleVariables vars;
     std::string objectDir = this->GeneratorTarget->GetSupportDirectory();
@@ -222,18 +225,19 @@
 
     std::string launcher;
 
-    cmValue val = this->LocalGenerator->GetRuleLauncher(this->GeneratorTarget,
-                                                        "RULE_LAUNCH_LINK");
+    std::string val = this->LocalGenerator->GetRuleLauncher(
+      this->GeneratorTarget, "RULE_LAUNCH_LINK",
+      this->Makefile->GetSafeDefinition("CMAKE_BUILD_TYPE"));
     if (cmNonempty(val)) {
-      launcher = cmStrCat(*val, ' ');
+      launcher = cmStrCat(val, ' ');
     }
 
-    std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander(
-      this->LocalGenerator->CreateRulePlaceholderExpander());
+    auto rulePlaceholderExpander =
+      this->LocalGenerator->CreateRulePlaceholderExpander();
 
     // Expand placeholders in the commands.
     rulePlaceholderExpander->SetTargetImpLib(targetOutput);
-    for (std::string& real_link_command : real_link_commands) {
+    for (auto& real_link_command : real_link_commands) {
       real_link_command = cmStrCat(launcher, real_link_command);
       rulePlaceholderExpander->ExpandRuleVariables(this->LocalGenerator,
                                                    real_link_command, vars);
@@ -464,18 +468,18 @@
   bool useLinkScript = this->GlobalGenerator->GetUseLinkScript();
 
   // Construct the main link rule.
-  std::vector<std::string> real_link_commands;
   std::string linkRuleVar = this->GeneratorTarget->GetCreateRuleVariable(
     linkLanguage, this->GetConfigName());
   std::string linkRule = this->GetLinkRule(linkRuleVar);
   std::vector<std::string> commands1;
-  cmExpandList(linkRule, real_link_commands);
+  cmList real_link_commands(linkRule);
+
   if (this->GeneratorTarget->IsExecutableWithExports()) {
     // If a separate rule for creating an import library is specified
     // add it now.
     std::string implibRuleVar =
       cmStrCat("CMAKE_", linkLanguage, "_CREATE_IMPORT_LIBRARY");
-    this->Makefile->GetDefExpandList(implibRuleVar, real_link_commands);
+    real_link_commands.append(this->Makefile->GetDefinition(implibRuleVar));
   }
 
   bool useResponseFileForObjects =
@@ -502,13 +506,13 @@
     // Collect up flags to link in needed libraries.
     std::string linkLibs;
     this->CreateLinkLibs(linkLineComputer.get(), linkLibs,
-                         useResponseFileForLibs, depends);
+                         useResponseFileForLibs, depends, linkLanguage);
 
     // Construct object file lists that may be needed to expand the
     // rule.
     std::string buildObjs;
     this->CreateObjectLists(useLinkScript, false, useResponseFileForObjects,
-                            buildObjs, depends, useWatcomQuote);
+                            buildObjs, depends, useWatcomQuote, linkLanguage);
     if (!this->DeviceLinkObject.empty()) {
       buildObjs += " " +
         this->LocalGenerator->ConvertToOutputFormat(
@@ -587,18 +591,19 @@
 
     std::string launcher;
 
-    cmValue val = this->LocalGenerator->GetRuleLauncher(this->GeneratorTarget,
-                                                        "RULE_LAUNCH_LINK");
+    std::string val = this->LocalGenerator->GetRuleLauncher(
+      this->GeneratorTarget, "RULE_LAUNCH_LINK",
+      this->Makefile->GetSafeDefinition("CMAKE_BUILD_TYPE"));
     if (cmNonempty(val)) {
-      launcher = cmStrCat(*val, ' ');
+      launcher = cmStrCat(val, ' ');
     }
 
-    std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander(
-      this->LocalGenerator->CreateRulePlaceholderExpander());
+    auto rulePlaceholderExpander =
+      this->LocalGenerator->CreateRulePlaceholderExpander();
 
     // Expand placeholders in the commands.
     rulePlaceholderExpander->SetTargetImpLib(targetOutPathImport);
-    for (std::string& real_link_command : real_link_commands) {
+    for (auto& real_link_command : real_link_commands) {
       real_link_command = cmStrCat(launcher, real_link_command);
       rulePlaceholderExpander->ExpandRuleVariables(this->LocalGenerator,
                                                    real_link_command, vars);
diff --git a/Source/cmMakefileLibraryTargetGenerator.cxx b/Source/cmMakefileLibraryTargetGenerator.cxx
index 9669293..fc3caa1 100644
--- a/Source/cmMakefileLibraryTargetGenerator.cxx
+++ b/Source/cmMakefileLibraryTargetGenerator.cxx
@@ -16,6 +16,7 @@
 #include "cmGlobalUnixMakefileGenerator3.h"
 #include "cmLinkLineComputer.h"
 #include "cmLinkLineDeviceComputer.h"
+#include "cmList.h"
 #include "cmLocalGenerator.h"
 #include "cmLocalUnixMakefileGenerator3.h"
 #include "cmMakefile.h"
@@ -61,6 +62,9 @@
   // write in rules for object files and custom commands
   this->WriteTargetBuildRules();
 
+  // Write in the rules for the link dependency file
+  this->WriteTargetLinkDependRules();
+
   // write the link rules
   // Write the rule for this target type.
   switch (this->GeneratorTarget->GetType()) {
@@ -304,7 +308,7 @@
   vars.Language = linkLanguage.c_str();
 
   // Expand the rule variables.
-  std::vector<std::string> real_link_commands;
+  cmList real_link_commands;
   {
     // Set path conversion for link script shells.
     this->LocalGenerator->SetLinkScriptShell(useLinkScript);
@@ -327,14 +331,14 @@
 
     this->CreateLinkLibs(
       linkLineComputer.get(), linkLibs, useResponseFileForLibs, depends,
-      cmMakefileTargetGenerator::ResponseFlagFor::DeviceLink);
+      linkLanguage, cmMakefileTargetGenerator::ResponseFlagFor::DeviceLink);
 
     // Construct object file lists that may be needed to expand the
     // rule.
     std::string buildObjs;
     this->CreateObjectLists(
       useLinkScript, false, // useArchiveRules
-      useResponseFileForObjects, buildObjs, depends, false,
+      useResponseFileForObjects, buildObjs, depends, false, linkLanguage,
       cmMakefileTargetGenerator::ResponseFlagFor::DeviceLink);
 
     std::string objectDir = this->GeneratorTarget->GetSupportDirectory();
@@ -362,22 +366,23 @@
     vars.TargetCompilePDB = targetOutPathCompilePDB.c_str();
 
     std::string launcher;
-    cmValue val = this->LocalGenerator->GetRuleLauncher(this->GeneratorTarget,
-                                                        "RULE_LAUNCH_LINK");
+    std::string val = this->LocalGenerator->GetRuleLauncher(
+      this->GeneratorTarget, "RULE_LAUNCH_LINK",
+      this->Makefile->GetSafeDefinition("CMAKE_BUILD_TYPE"));
     if (cmNonempty(val)) {
-      launcher = cmStrCat(*val, ' ');
+      launcher = cmStrCat(val, ' ');
     }
 
-    std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander(
-      this->LocalGenerator->CreateRulePlaceholderExpander());
+    auto rulePlaceholderExpander =
+      this->LocalGenerator->CreateRulePlaceholderExpander();
 
     // Construct the main link rule and expand placeholders.
     rulePlaceholderExpander->SetTargetImpLib(targetOutput);
     std::string linkRule = this->GetLinkRule(linkRuleVar);
-    cmExpandList(linkRule, real_link_commands);
+    real_link_commands.append(linkRule);
 
     // Expand placeholders.
-    for (std::string& real_link_command : real_link_commands) {
+    for (auto& real_link_command : real_link_commands) {
       real_link_command = cmStrCat(launcher, real_link_command);
       rulePlaceholderExpander->ExpandRuleVariables(this->LocalGenerator,
                                                    real_link_command, vars);
@@ -464,9 +469,20 @@
   std::string outpathImp;
   if (this->GeneratorTarget->IsFrameworkOnApple()) {
     outpath = this->GeneratorTarget->GetDirectory(this->GetConfigName());
+    cmOSXBundleGenerator::SkipParts bundleSkipParts;
+    if (this->GeneratorTarget->HasImportLibrary(this->GetConfigName())) {
+      bundleSkipParts.TextStubs = false;
+    }
     this->OSXBundleGenerator->CreateFramework(this->TargetNames.Output,
-                                              outpath, this->GetConfigName());
+                                              outpath, this->GetConfigName(),
+                                              bundleSkipParts);
     outpath += '/';
+    if (!this->TargetNames.ImportLibrary.empty()) {
+      outpathImp = this->GeneratorTarget->GetDirectory(
+        this->GetConfigName(), cmStateEnums::ImportLibraryArtifact);
+      cmSystemTools::MakeDirectory(outpathImp);
+      outpathImp += '/';
+    }
   } else if (this->GeneratorTarget->IsCFBundleOnApple()) {
     outpath = this->GeneratorTarget->GetDirectory(this->GetConfigName());
     this->OSXBundleGenerator->CreateCFBundle(this->TargetNames.Output, outpath,
@@ -628,9 +644,9 @@
 
   // For static libraries there might be archiving rules.
   bool haveStaticLibraryRule = false;
-  std::vector<std::string> archiveCreateCommands;
-  std::vector<std::string> archiveAppendCommands;
-  std::vector<std::string> archiveFinishCommands;
+  cmList archiveCreateCommands;
+  cmList archiveAppendCommands;
+  cmList archiveFinishCommands;
   std::string::size_type archiveCommandLimit = std::string::npos;
   if (this->GeneratorTarget->GetType() == cmStateEnums::STATIC_LIBRARY) {
     haveStaticLibraryRule = this->Makefile->IsDefinitionSet(linkRuleVar);
@@ -640,21 +656,23 @@
     arCreateVar = this->GeneratorTarget->GetFeatureSpecificLinkRuleVariable(
       arCreateVar, linkLanguage, this->GetConfigName());
 
-    this->Makefile->GetDefExpandList(arCreateVar, archiveCreateCommands);
+    archiveCreateCommands.assign(this->Makefile->GetDefinition(arCreateVar));
+
     std::string arAppendVar =
       cmStrCat("CMAKE_", linkLanguage, "_ARCHIVE_APPEND");
 
     arAppendVar = this->GeneratorTarget->GetFeatureSpecificLinkRuleVariable(
       arAppendVar, linkLanguage, this->GetConfigName());
 
-    this->Makefile->GetDefExpandList(arAppendVar, archiveAppendCommands);
+    archiveAppendCommands.assign(this->Makefile->GetDefinition(arAppendVar));
+
     std::string arFinishVar =
       cmStrCat("CMAKE_", linkLanguage, "_ARCHIVE_FINISH");
 
     arFinishVar = this->GeneratorTarget->GetFeatureSpecificLinkRuleVariable(
       arFinishVar, linkLanguage, this->GetConfigName());
 
-    this->Makefile->GetDefExpandList(arFinishVar, archiveFinishCommands);
+    archiveFinishCommands.assign(this->Makefile->GetDefinition(arFinishVar));
   }
 
   // Decide whether to use archiving rules.
@@ -678,11 +696,12 @@
   }
 
   // Expand the rule variables.
-  std::vector<std::string> real_link_commands;
+  auto rulePlaceholderExpander =
+    this->LocalGenerator->CreateRulePlaceholderExpander();
+  bool useWatcomQuote =
+    this->Makefile->IsOn(linkRuleVar + "_USE_WATCOM_QUOTE");
+  cmList real_link_commands;
   {
-    bool useWatcomQuote =
-      this->Makefile->IsOn(linkRuleVar + "_USE_WATCOM_QUOTE");
-
     // Set path conversion for link script shells.
     this->LocalGenerator->SetLinkScriptShell(useLinkScript);
 
@@ -699,7 +718,7 @@
       linkLineComputer->SetRelink(relink);
 
       this->CreateLinkLibs(linkLineComputer.get(), linkLibs,
-                           useResponseFileForLibs, depends);
+                           useResponseFileForLibs, depends, linkLanguage);
     }
 
     // Construct object file lists that may be needed to expand the
@@ -707,7 +726,7 @@
     std::string buildObjs;
     this->CreateObjectLists(useLinkScript, useArchiveRules,
                             useResponseFileForObjects, buildObjs, depends,
-                            useWatcomQuote);
+                            useWatcomQuote, linkLanguage);
     if (!this->DeviceLinkObject.empty()) {
       buildObjs += " " +
         this->LocalGenerator->ConvertToOutputFormat(
@@ -766,7 +785,7 @@
     if (this->GeneratorTarget->HasSOName(this->GetConfigName())) {
       vars.SONameFlag = this->Makefile->GetSONameFlag(linkLanguage);
       targetOutSOName = this->LocalGenerator->ConvertToOutputFormat(
-        this->TargetNames.SharedObject.c_str(), cmOutputConverter::SHELL);
+        this->TargetNames.SharedObject, cmOutputConverter::SHELL);
       vars.TargetSOName = targetOutSOName.c_str();
     }
     vars.LinkFlags = linkFlags.c_str();
@@ -808,14 +827,13 @@
     }
 
     std::string launcher;
-    cmValue val = this->LocalGenerator->GetRuleLauncher(this->GeneratorTarget,
-                                                        "RULE_LAUNCH_LINK");
+    std::string val = this->LocalGenerator->GetRuleLauncher(
+      this->GeneratorTarget, "RULE_LAUNCH_LINK",
+      this->Makefile->GetSafeDefinition("CMAKE_BUILD_TYPE"));
     if (cmNonempty(val)) {
-      launcher = cmStrCat(*val, ' ');
+      launcher = cmStrCat(val, ' ');
     }
 
-    std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander(
-      this->LocalGenerator->CreateRulePlaceholderExpander());
     // Construct the main link rule and expand placeholders.
     rulePlaceholderExpander->SetTargetImpLib(targetOutPathImport);
     if (useArchiveRules) {
@@ -867,7 +885,7 @@
     } else {
       // Get the set of commands.
       std::string linkRule = this->GetLinkRule(linkRuleVar);
-      cmExpandList(linkRule, real_link_commands);
+      real_link_commands.append(linkRule);
       if (this->UseLWYU) {
         cmValue lwyuCheck =
           this->Makefile->GetDefinition("CMAKE_LINK_WHAT_YOU_USE_CHECK");
@@ -883,7 +901,7 @@
       }
 
       // Expand placeholders.
-      for (std::string& real_link_command : real_link_commands) {
+      for (auto& real_link_command : real_link_commands) {
         real_link_command = cmStrCat(launcher, real_link_command);
         rulePlaceholderExpander->ExpandRuleVariables(this->LocalGenerator,
                                                      real_link_command, vars);
@@ -948,6 +966,86 @@
   this->WriteMakeRule(*this->BuildFileStream, nullptr, outputs, depends,
                       commands, false);
 
+  // Add rule to generate text-based stubs, if required
+  if (this->GeneratorTarget->IsApple() &&
+      this->GeneratorTarget->HasImportLibrary(this->GetConfigName())) {
+    auto genStubsRule =
+      this->Makefile->GetDefinition("CMAKE_CREATE_TEXT_STUBS");
+    cmList genStubs_commands{ genStubsRule };
+
+    std::string TBDFullPath =
+      cmStrCat(outpathImp, this->TargetNames.ImportOutput);
+    std::string TBDFullPathReal =
+      cmStrCat(outpathImp, this->TargetNames.ImportReal);
+    std::string TBDFullPathSO =
+      cmStrCat(outpathImp, this->TargetNames.ImportLibrary);
+
+    // Expand placeholders.
+    cmRulePlaceholderExpander::RuleVariables vars;
+    std::string target = this->LocalGenerator->ConvertToOutputFormat(
+      this->LocalGenerator->MaybeRelativeToCurBinDir(targetFullPathReal),
+      cmOutputConverter::SHELL, useWatcomQuote);
+    vars.Target = target.c_str();
+    std::string TBDOutPathReal = this->LocalGenerator->ConvertToOutputFormat(
+      this->LocalGenerator->MaybeRelativeToCurBinDir(TBDFullPathReal),
+      cmOutputConverter::SHELL, useWatcomQuote);
+    rulePlaceholderExpander->SetTargetImpLib(TBDOutPathReal);
+    for (std::string& command : genStubs_commands) {
+      rulePlaceholderExpander->ExpandRuleVariables(this->LocalGenerator,
+                                                   command, vars);
+    }
+    outputs.clear();
+    outputs.push_back(TBDFullPathReal);
+    if (this->TargetNames.ImportLibrary != this->TargetNames.ImportReal) {
+      outputs.push_back(TBDFullPathSO);
+    }
+    if (this->TargetNames.ImportOutput != this->TargetNames.ImportLibrary &&
+        this->TargetNames.ImportOutput != this->TargetNames.ImportReal) {
+      outputs.push_back(TBDFullPath);
+    }
+    this->ExtraFiles.insert(TBDFullPath);
+
+    depends.clear();
+    depends.push_back(targetFullPathReal);
+
+    // Add a rule to create necessary symlinks for the library.
+    // Frameworks are handled by cmOSXBundleGenerator.
+    if (TBDFullPath != TBDFullPathReal &&
+        !this->GeneratorTarget->IsFrameworkOnApple()) {
+      auto TBDOutPathSO = this->LocalGenerator->ConvertToOutputFormat(
+        this->LocalGenerator->MaybeRelativeToCurBinDir(TBDFullPathSO),
+        cmOutputConverter::SHELL, useWatcomQuote);
+      auto TBDOutPath = this->LocalGenerator->ConvertToOutputFormat(
+        this->LocalGenerator->MaybeRelativeToCurBinDir(TBDFullPath),
+        cmOutputConverter::SHELL, useWatcomQuote);
+
+      std::string symlink =
+        cmStrCat("$(CMAKE_COMMAND) -E cmake_symlink_library ", TBDOutPathReal,
+                 ' ', TBDOutPathSO, ' ', TBDOutPath);
+      commands1.push_back(std::move(symlink));
+      this->LocalGenerator->CreateCDCommand(
+        commands1, this->Makefile->GetCurrentBinaryDirectory(),
+        this->LocalGenerator->GetBinaryDirectory());
+      cm::append(genStubs_commands, commands1);
+      commands1.clear();
+    }
+
+    this->WriteMakeRule(*this->BuildFileStream, nullptr, outputs, depends,
+                        genStubs_commands, false);
+
+    // clean actions for apple specific outputs
+    // clean actions for ImportLibrary are already specified
+    if (this->TargetNames.ImportReal != this->TargetNames.ImportLibrary) {
+      libCleanFiles.insert(
+        this->LocalGenerator->MaybeRelativeToCurBinDir(TBDFullPathReal));
+    }
+    if (this->TargetNames.ImportOutput != this->TargetNames.ImportReal &&
+        this->TargetNames.ImportOutput != this->TargetNames.ImportLibrary) {
+      libCleanFiles.insert(
+        this->LocalGenerator->MaybeRelativeToCurBinDir(TBDFullPath));
+    }
+  }
+
   // Write the main driver rule to build everything in this target.
   this->WriteTargetDriverRule(targetFullPath, relink);
 
diff --git a/Source/cmMakefileTargetGenerator.cxx b/Source/cmMakefileTargetGenerator.cxx
index c40d685..c915e26 100644
--- a/Source/cmMakefileTargetGenerator.cxx
+++ b/Source/cmMakefileTargetGenerator.cxx
@@ -3,6 +3,7 @@
 #include "cmMakefileTargetGenerator.h"
 
 #include <algorithm>
+#include <array>
 #include <cassert>
 #include <cstdio>
 #include <iterator>
@@ -25,9 +26,9 @@
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorTarget.h"
-#include "cmGlobalCommonGenerator.h"
 #include "cmGlobalUnixMakefileGenerator3.h"
 #include "cmLinkLineComputer.h" // IWYU pragma: keep
+#include "cmList.h"
 #include "cmLocalCommonGenerator.h"
 #include "cmLocalGenerator.h"
 #include "cmLocalUnixMakefileGenerator3.h"
@@ -148,6 +149,8 @@
 
   this->LocalGenerator->AppendPositionIndependentLinkerFlags(
     flags, this->GeneratorTarget, this->GetConfigName(), linkLanguage);
+  this->LocalGenerator->AppendDependencyInfoLinkerFlags(
+    flags, this->GeneratorTarget, this->GetConfigName(), linkLanguage);
 }
 
 void cmMakefileTargetGenerator::CreateRuleFile()
@@ -206,27 +209,24 @@
   // -- Write the custom commands for this target
 
   // Evaluates generator expressions and expands prop_value
-  auto evaluatedFiles =
-    [this](const std::string& prop_value) -> std::vector<std::string> {
-    std::vector<std::string> files;
-    cmExpandList(cmGeneratorExpression::Evaluate(
-                   prop_value, this->LocalGenerator, this->GetConfigName(),
-                   this->GeneratorTarget),
-                 files);
+  auto evaluatedFiles = [this](const std::string& prop_value) -> cmList {
+    cmList files{ cmGeneratorExpression::Evaluate(
+      prop_value, this->LocalGenerator, this->GetConfigName(),
+      this->GeneratorTarget) };
     return files;
   };
 
   // Look for additional files registered for cleaning in this directory.
   if (cmValue prop_value =
         this->Makefile->GetProperty("ADDITIONAL_MAKE_CLEAN_FILES")) {
-    std::vector<std::string> const files = evaluatedFiles(*prop_value);
+    auto const files = evaluatedFiles(*prop_value);
     this->CleanFiles.insert(files.begin(), files.end());
   }
 
   // Look for additional files registered for cleaning in this target.
   if (cmValue prop_value =
         this->GeneratorTarget->GetProperty("ADDITIONAL_CLEAN_FILES")) {
-    std::vector<std::string> const files = evaluatedFiles(*prop_value);
+    auto const files = evaluatedFiles(*prop_value);
     // For relative path support
     std::string const& binaryDir =
       this->LocalGenerator->GetCurrentBinaryDirectory();
@@ -415,9 +415,11 @@
     this->GlobalGenerator->SupportsCompilerDependencies() &&
     (!this->Makefile->IsDefinitionSet(depsUseCompiler) ||
      this->Makefile->IsOn(depsUseCompiler));
+  bool linkerGenerateDeps =
+    this->GeneratorTarget->HasLinkDependencyFile(this->GetConfigName());
 
-  if (compilerGenerateDeps || ccGenerateDeps) {
-    std::string compilerDependFile =
+  if (compilerGenerateDeps || linkerGenerateDeps || ccGenerateDeps) {
+    std::string const compilerDependFile =
       cmStrCat(this->TargetBuildDirectoryFull, "/compiler_depend.make");
     *this->BuildFileStream << "# Include any dependencies generated by the "
                               "compiler for this target.\n"
@@ -540,8 +542,8 @@
     *this->FlagFileStream << language << "_DEFINES = " << defines << "\n\n";
     *this->FlagFileStream << language << "_INCLUDES = " << includes << "\n\n";
 
-    std::vector<std::string> architectures;
-    this->GeneratorTarget->GetAppleArchs(this->GetConfigName(), architectures);
+    std::vector<std::string> architectures =
+      this->GeneratorTarget->GetAppleArchs(this->GetConfigName(), language);
     architectures.emplace_back();
 
     for (const std::string& arch : architectures) {
@@ -610,17 +612,17 @@
   }
 
   // Use compiler to generate dependencies, if supported.
-  bool compilerGenerateDeps =
+  bool const compilerGenerateDeps =
     this->GlobalGenerator->SupportsCompilerDependencies() &&
     cmIsOn(this->Makefile->GetDefinition(
       cmStrCat("CMAKE_", lang, "_DEPENDS_USE_COMPILER")));
-  auto scanner = compilerGenerateDeps ? cmDependencyScannerKind::Compiler
-                                      : cmDependencyScannerKind::CMake;
+  auto const scanner = compilerGenerateDeps ? cmDependencyScannerKind::Compiler
+                                            : cmDependencyScannerKind::CMake;
 
   // Get the full path name of the object file.
   std::string const& objectName =
     this->GeneratorTarget->GetObjectName(&source);
-  std::string obj =
+  std::string const obj =
     cmStrCat(this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget),
              '/', objectName);
 
@@ -639,7 +641,7 @@
   // Create the directory containing the object file.  This may be a
   // subdirectory under the target's directory.
   {
-    std::string dir = cmSystemTools::GetFilenamePath(obj);
+    std::string const dir = cmSystemTools::GetFilenamePath(obj);
     cmSystemTools::MakeDirectory(this->LocalGenerator->ConvertToFullPath(dir));
   }
 
@@ -653,7 +655,7 @@
   std::string objFullPath =
     cmStrCat(this->LocalGenerator->GetCurrentBinaryDirectory(), '/', obj);
   objFullPath = cmSystemTools::CollapseFullPath(objFullPath);
-  std::string srcFullPath =
+  std::string const srcFullPath =
     cmSystemTools::CollapseFullPath(source.GetFullPath());
   this->LocalGenerator->AddImplicitDepends(this->GeneratorTarget, lang,
                                            objFullPath, srcFullPath, scanner);
@@ -666,12 +668,12 @@
   // generate the depend scanning rule
   this->WriteObjectDependRules(source, depends);
 
-  std::string config = this->GetConfigName();
-  std::string configUpper = cmSystemTools::UpperCase(config);
+  std::string const config = this->GetConfigName();
+  std::string const configUpper = cmSystemTools::UpperCase(config);
 
   // Add precompile headers dependencies
-  std::vector<std::string> architectures;
-  this->GeneratorTarget->GetAppleArchs(config, architectures);
+  std::vector<std::string> architectures =
+    this->GeneratorTarget->GetAppleArchs(config, lang);
   if (architectures.empty()) {
     architectures.emplace_back();
   }
@@ -723,7 +725,8 @@
   this->GeneratorTarget->AddExplicitLanguageFlags(flags, source);
 
   // Add language-specific flags.
-  std::string langFlags = cmStrCat("$(", lang, "_FLAGS", filterArch, ")");
+  std::string const langFlags =
+    cmStrCat("$(", lang, "_FLAGS", filterArch, ")");
   this->LocalGenerator->AppendFlags(flags, langFlags);
 
   cmGeneratorExpressionInterpreter genexInterpreter(
@@ -742,7 +745,7 @@
       cmSystemTools::GetFilenameWithoutLastExtension(objectName);
     ispcSource = cmSystemTools::GetFilenameWithoutLastExtension(ispcSource);
 
-    cmValue ispcSuffixProp =
+    cmValue const ispcSuffixProp =
       this->GeneratorTarget->GetProperty("ISPC_HEADER_SUFFIX");
     assert(ispcSuffixProp);
 
@@ -781,7 +784,7 @@
   // Add precompile headers compile options.
   if (!pchSources.empty() && !source.GetProperty("SKIP_PRECOMPILE_HEADERS")) {
     std::string pchOptions;
-    auto pchIt = pchSources.find(source.GetFullPath());
+    auto const pchIt = pchSources.find(source.GetFullPath());
     if (pchIt != pchSources.end()) {
       pchOptions = this->GeneratorTarget->GetPchCreateCompileOptions(
         config, lang, pchIt->second);
@@ -827,7 +830,8 @@
                           << "_DEFINES = " << evaluatedDefs << "\n"
                           << "\n";
   }
-  std::string defPropName = cmStrCat("COMPILE_DEFINITIONS_", configUpper);
+  std::string const defPropName =
+    cmStrCat("COMPILE_DEFINITIONS_", configUpper);
   if (cmValue config_compile_defs = source.GetProperty(defPropName)) {
     const std::string& evaluatedDefs =
       genexInterpreter.Evaluate(*config_compile_defs, COMPILE_DEFINITIONS);
@@ -838,7 +842,7 @@
   }
 
   // Get the output paths for source and object files.
-  std::string sourceFile = this->LocalGenerator->ConvertToOutputFormat(
+  std::string const sourceFile = this->LocalGenerator->ConvertToOutputFormat(
     source.GetFullPath(), cmOutputConverter::SHELL);
 
   // Construct the build message.
@@ -894,7 +898,7 @@
       targetOutPathCompilePDB.back() = '/';
     }
 
-    std::string compilePdbOutputPath =
+    std::string const compilePdbOutputPath =
       this->GeneratorTarget->GetCompilePDBDirectory(this->GetConfigName());
     cmSystemTools::MakeDirectory(compilePdbOutputPath);
   }
@@ -907,7 +911,7 @@
   vars.TargetPDB = targetOutPathPDB.c_str();
   vars.TargetCompilePDB = targetOutPathCompilePDB.c_str();
   vars.Source = sourceFile.c_str();
-  std::string shellObj =
+  std::string const shellObj =
     this->LocalGenerator->ConvertToOutputFormat(obj, cmOutputConverter::SHELL);
   vars.Object = shellObj.c_str();
   std::string objectDir = this->GeneratorTarget->GetSupportDirectory();
@@ -957,15 +961,15 @@
   // At the moment, it is assumed that C, C++, Fortran, and CUDA have both
   // assembly and preprocessor capabilities. The same is true for the
   // ability to export compile commands
-  bool lang_has_preprocessor =
+  bool const lang_has_preprocessor =
     ((lang == "C") || (lang == "CXX") || (lang == "OBJC") ||
      (lang == "OBJCXX") || (lang == "Fortran") || (lang == "CUDA") ||
      lang == "ISPC" || lang == "HIP" || lang == "ASM");
   bool const lang_has_assembly = lang_has_preprocessor;
   bool const lang_can_export_cmds = lang_has_preprocessor;
 
-  std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander(
-    this->LocalGenerator->CreateRulePlaceholderExpander());
+  auto rulePlaceholderExpander =
+    this->LocalGenerator->CreateRulePlaceholderExpander();
 
   // Construct the compile rules.
   {
@@ -977,11 +981,23 @@
           this->Makefile->GetRequiredDefinition("_CMAKE_CUDA_RDC_FLAG");
         cudaCompileMode = cmStrCat(cudaCompileMode, rdcFlag, " ");
       }
-      if (this->GeneratorTarget->GetPropertyAsBool("CUDA_PTX_COMPILATION")) {
-        const std::string& ptxFlag =
-          this->Makefile->GetRequiredDefinition("_CMAKE_CUDA_PTX_FLAG");
-        cudaCompileMode = cmStrCat(cudaCompileMode, ptxFlag);
-      } else {
+
+      static std::array<cm::string_view, 4> const compileModes{
+        { "PTX"_s, "CUBIN"_s, "FATBIN"_s, "OPTIX"_s }
+      };
+      bool useNormalCompileMode = true;
+      for (cm::string_view mode : compileModes) {
+        auto propName = cmStrCat("CUDA_", mode, "_COMPILATION");
+        auto defName = cmStrCat("_CMAKE_CUDA_", mode, "_FLAG");
+        if (this->GeneratorTarget->GetPropertyAsBool(propName)) {
+          const std::string& flag =
+            this->Makefile->GetRequiredDefinition(defName);
+          cudaCompileMode = cmStrCat(cudaCompileMode, flag);
+          useNormalCompileMode = false;
+          break;
+        }
+      }
+      if (useNormalCompileMode) {
         const std::string& wholeFlag =
           this->Makefile->GetRequiredDefinition("_CMAKE_CUDA_WHOLE_FLAG");
         cudaCompileMode = cmStrCat(cudaCompileMode, wholeFlag);
@@ -989,10 +1005,10 @@
       vars.CudaCompileMode = cudaCompileMode.c_str();
     }
 
-    std::vector<std::string> compileCommands;
+    cmList compileCommands;
     const std::string& compileRule = this->Makefile->GetRequiredDefinition(
       "CMAKE_" + lang + "_COMPILE_OBJECT");
-    cmExpandList(compileRule, compileCommands);
+    compileCommands.assign(compileRule);
 
     if (this->GeneratorTarget->GetPropertyAsBool("EXPORT_COMPILE_COMMANDS") &&
         lang_can_export_cmds && compileCommands.size() == 1) {
@@ -1001,31 +1017,31 @@
       // no launcher for CMAKE_EXPORT_COMPILE_COMMANDS
       rulePlaceholderExpander->ExpandRuleVariables(this->LocalGenerator,
                                                    compileCommand, vars);
-      std::string workingDirectory =
+      std::string const workingDirectory =
         this->LocalGenerator->GetCurrentBinaryDirectory();
       std::string::size_type lfPos = compileCommand.find(langFlags);
       if (lfPos != std::string::npos) {
         compileCommand.replace(lfPos, langFlags.size(),
                                this->GetFlags(lang, this->GetConfigName()));
       }
-      std::string langDefines = std::string("$(") + lang + "_DEFINES)";
-      std::string::size_type ldPos = compileCommand.find(langDefines);
+      std::string const langDefines = std::string("$(") + lang + "_DEFINES)";
+      std::string::size_type const ldPos = compileCommand.find(langDefines);
       if (ldPos != std::string::npos) {
         compileCommand.replace(ldPos, langDefines.size(),
                                this->GetDefines(lang, this->GetConfigName()));
       }
-      std::string langIncludes = std::string("$(") + lang + "_INCLUDES)";
-      std::string::size_type liPos = compileCommand.find(langIncludes);
+      std::string const langIncludes = std::string("$(") + lang + "_INCLUDES)";
+      std::string::size_type const liPos = compileCommand.find(langIncludes);
       if (liPos != std::string::npos) {
         compileCommand.replace(liPos, langIncludes.size(),
                                this->GetIncludes(lang, this->GetConfigName()));
       }
 
-      cmValue eliminate[] = {
+      cmValue const eliminate[] = {
         this->Makefile->GetDefinition("CMAKE_START_TEMP_FILE"),
         this->Makefile->GetDefinition("CMAKE_END_TEMP_FILE")
       };
-      for (cmValue el : eliminate) {
+      for (cmValue const& el : eliminate) {
         if (el) {
           cmSystemTools::ReplaceString(compileCommand, *el, "");
         }
@@ -1037,123 +1053,23 @@
 
     // See if we need to use a compiler launcher like ccache or distcc
     std::string compilerLauncher;
-    if (!compileCommands.empty() &&
-        (lang == "C" || lang == "CXX" || lang == "Fortran" || lang == "CUDA" ||
-         lang == "HIP" || lang == "ISPC" || lang == "OBJC" ||
-         lang == "OBJCXX")) {
-      std::string const clauncher_prop = lang + "_COMPILER_LAUNCHER";
-      cmValue clauncher = this->GeneratorTarget->GetProperty(clauncher_prop);
-      std::string evaluatedClauncher = cmGeneratorExpression::Evaluate(
-        *clauncher, this->LocalGenerator, config);
-      if (!evaluatedClauncher.empty()) {
-        compilerLauncher = evaluatedClauncher;
-      }
+    if (!compileCommands.empty()) {
+      compilerLauncher = GetCompilerLauncher(lang, config);
     }
 
-    // Maybe insert an include-what-you-use runner.
-    if (!compileCommands.empty() &&
-        (lang == "C" || lang == "CXX" || lang == "OBJC" || lang == "OBJCXX")) {
-      std::string const tidy_prop = lang + "_CLANG_TIDY";
-      cmValue tidy = this->GeneratorTarget->GetProperty(tidy_prop);
-      cmValue iwyu = nullptr;
-      cmValue cpplint = nullptr;
-      cmValue cppcheck = nullptr;
-      if (lang == "C" || lang == "CXX") {
-        std::string const iwyu_prop = lang + "_INCLUDE_WHAT_YOU_USE";
-        iwyu = this->GeneratorTarget->GetProperty(iwyu_prop);
-        std::string const cpplint_prop = lang + "_CPPLINT";
-        cpplint = this->GeneratorTarget->GetProperty(cpplint_prop);
-        std::string const cppcheck_prop = lang + "_CPPCHECK";
-        cppcheck = this->GeneratorTarget->GetProperty(cppcheck_prop);
-      }
-      if (cmNonempty(iwyu) || cmNonempty(tidy) || cmNonempty(cpplint) ||
-          cmNonempty(cppcheck)) {
-        std::string run_iwyu = "$(CMAKE_COMMAND) -E __run_co_compile";
-        if (!compilerLauncher.empty()) {
-          // In __run_co_compile case the launcher command is supplied
-          // via --launcher=<maybe-list> and consumed
-          run_iwyu += " --launcher=";
-          run_iwyu += this->LocalGenerator->EscapeForShell(compilerLauncher);
-          compilerLauncher.clear();
-        }
-        if (cmNonempty(iwyu)) {
-          run_iwyu += " --iwyu=";
-
-          // Only add --driver-mode if it is not already specified, as adding
-          // it unconditionally might override a user-specified driver-mode
-          if (iwyu.Get()->find("--driver-mode=") == std::string::npos) {
-            cmValue p = this->Makefile->GetDefinition(
-              cmStrCat("CMAKE_", lang, "_INCLUDE_WHAT_YOU_USE_DRIVER_MODE"));
-            std::string driverMode;
-
-            if (cmNonempty(p)) {
-              driverMode = *p;
-            } else {
-              driverMode = lang == "C" ? "gcc" : "g++";
-            }
-
-            run_iwyu += this->LocalGenerator->EscapeForShell(
-              cmStrCat(*iwyu, ";--driver-mode=", driverMode));
-          } else {
-            run_iwyu += this->LocalGenerator->EscapeForShell(*iwyu);
-          }
-        }
-        if (cmNonempty(tidy)) {
-          run_iwyu += " --tidy=";
-          cmValue p = this->Makefile->GetDefinition("CMAKE_" + lang +
-                                                    "_CLANG_TIDY_DRIVER_MODE");
-          std::string driverMode;
-          if (cmNonempty(p)) {
-            driverMode = *p;
-          } else {
-            driverMode = lang == "C" ? "gcc" : "g++";
-          }
-          std::string d =
-            this->GeneratorTarget->GetClangTidyExportFixesDirectory(lang);
-          std::string exportFixes;
-          if (!d.empty()) {
-            this->GlobalCommonGenerator->AddClangTidyExportFixesDir(d);
-            std::string fixesFile = cmSystemTools::CollapseFullPath(cmStrCat(
-              d, '/',
-              this->LocalGenerator->MaybeRelativeToTopBinDir(cmStrCat(
-                this->LocalGenerator->GetCurrentBinaryDirectory(), '/',
-                this->LocalGenerator->GetTargetDirectory(
-                  this->GeneratorTarget),
-                '/', objectName, ".yaml"))));
-            this->GlobalCommonGenerator->AddClangTidyExportFixesFile(
-              fixesFile);
-            cmSystemTools::MakeDirectory(
-              cmSystemTools::GetFilenamePath(fixesFile));
-            fixesFile =
-              this->LocalGenerator->MaybeRelativeToCurBinDir(fixesFile);
-            exportFixes = cmStrCat(";--export-fixes=", fixesFile);
-          }
-          run_iwyu += this->LocalGenerator->EscapeForShell(
-            cmStrCat(*tidy, ";--extra-arg-before=--driver-mode=", driverMode,
-                     exportFixes));
-        }
-        if (cmNonempty(cpplint)) {
-          run_iwyu += " --cpplint=";
-          run_iwyu += this->LocalGenerator->EscapeForShell(*cpplint);
-        }
-        if (cmNonempty(cppcheck)) {
-          run_iwyu += " --cppcheck=";
-          run_iwyu += this->LocalGenerator->EscapeForShell(*cppcheck);
-        }
-        if (cmNonempty(tidy) || (cmNonempty(cpplint)) ||
-            (cmNonempty(cppcheck))) {
-          run_iwyu += " --source=";
-          run_iwyu += sourceFile;
-        }
-        run_iwyu += " -- ";
-        compileCommands.front().insert(0, run_iwyu);
+    cmValue const skipCodeCheck = source.GetProperty("SKIP_LINTING");
+    if (!skipCodeCheck.IsOn()) {
+      std::string const codeCheck = this->GenerateCodeCheckRules(
+        source, compilerLauncher, "$(CMAKE_COMMAND)", config, nullptr);
+      if (!codeCheck.empty()) {
+        compileCommands.front().insert(0, codeCheck);
       }
     }
 
     // If compiler launcher was specified and not consumed above, it
     // goes to the beginning of the command line.
     if (!compileCommands.empty() && !compilerLauncher.empty()) {
-      std::vector<std::string> args = cmExpandedList(compilerLauncher, true);
+      cmList args{ compilerLauncher, cmList::EmptyElements::Yes };
       if (!args.empty()) {
         args[0] = this->LocalGenerator->ConvertToOutputFormat(
           args[0], cmOutputConverter::SHELL);
@@ -1161,15 +1077,16 @@
           i = this->LocalGenerator->EscapeForShell(i);
         }
       }
-      compileCommands.front().insert(0, cmJoin(args, " ") + " ");
+      compileCommands.front().insert(0, args.join(" ") + " ");
     }
 
     std::string launcher;
     {
-      cmValue val = this->LocalGenerator->GetRuleLauncher(
-        this->GeneratorTarget, "RULE_LAUNCH_COMPILE");
+      std::string val = this->LocalGenerator->GetRuleLauncher(
+        this->GeneratorTarget, "RULE_LAUNCH_COMPILE",
+        this->Makefile->GetSafeDefinition("CMAKE_BUILD_TYPE"));
       if (cmNonempty(val)) {
-        launcher = cmStrCat(*val, ' ');
+        launcher = cmStrCat(val, ' ');
       }
     }
 
@@ -1192,9 +1109,7 @@
       const auto& extraCommands = this->Makefile->GetSafeDefinition(
         cmStrCat("CMAKE_", lang, "_DEPENDS_EXTRA_COMMANDS"));
       if (!extraCommands.empty()) {
-        auto commandList = cmExpandedList(extraCommands);
-        compileCommands.insert(compileCommands.end(), commandList.cbegin(),
-                               commandList.cend());
+        compileCommands.append(extraCommands);
       }
 
       const auto& depFormat = this->Makefile->GetRequiredDefinition(
@@ -1235,14 +1150,15 @@
   }
 
   // Check for extra outputs created by the compilation.
-  std::vector<std::string> outputs(1, relativeObj);
+  cmList outputs;
+  outputs.emplace_back(relativeObj);
   if (cmValue extra_outputs_str = source.GetProperty("OBJECT_OUTPUTS")) {
     std::string evaluated_outputs = cmGeneratorExpression::Evaluate(
       *extra_outputs_str, this->LocalGenerator, config);
 
     if (!evaluated_outputs.empty()) {
       // Register these as extra files to clean.
-      cmExpandList(evaluated_outputs, outputs);
+      outputs.append(evaluated_outputs);
     }
   }
   if (!ispcHeaderRelative.empty()) {
@@ -1281,8 +1197,8 @@
 
     if (do_preprocess_rules) {
       commands.clear();
-      std::string relativeObjI = relativeObjBase + ".i";
-      std::string objI = objBase + ".i";
+      std::string const relativeObjI = relativeObjBase + ".i";
+      std::string const objI = objBase + ".i";
 
       std::string preprocessEcho =
         cmStrCat("Preprocessing ", lang, " source to ", objI);
@@ -1293,8 +1209,7 @@
         cmStrCat("CMAKE_", lang, "_CREATE_PREPROCESSED_SOURCE");
       if (cmValue preprocessRule =
             this->Makefile->GetDefinition(preprocessRuleVar)) {
-        std::vector<std::string> preprocessCommands =
-          cmExpandedList(*preprocessRule);
+        cmList preprocessCommands{ *preprocessRule };
 
         std::string shellObjI = this->LocalGenerator->ConvertToOutputFormat(
           objI, cmOutputConverter::SHELL);
@@ -1338,8 +1253,7 @@
         cmStrCat("CMAKE_", lang, "_CREATE_ASSEMBLY_SOURCE");
       if (cmValue assemblyRule =
             this->Makefile->GetDefinition(assemblyRuleVar)) {
-        std::vector<std::string> assemblyCommands =
-          cmExpandedList(*assemblyRule);
+        cmList assemblyCommands{ *assemblyRule };
 
         std::string shellObjS = this->LocalGenerator->ConvertToOutputFormat(
           objS, cmOutputConverter::SHELL);
@@ -1376,7 +1290,7 @@
   std::vector<std::string> commands;
 
   // Construct the clean target name.
-  std::string cleanTarget = cmStrCat(
+  std::string const cleanTarget = cmStrCat(
     this->LocalGenerator->GetRelativeTargetDirectory(this->GeneratorTarget),
     "/clean");
 
@@ -1404,7 +1318,7 @@
   }
 
   // Check whether we need to bother checking for a symbolic output.
-  bool need_symbolic = this->GlobalGenerator->GetNeedSymbolicMark();
+  bool const need_symbolic = this->GlobalGenerator->GetNeedSymbolicMark();
 
   // Check whether the first output is marked as symbolic.
   if (need_symbolic) {
@@ -1434,7 +1348,7 @@
 
     bool o_symbolic = false;
     if (need_symbolic) {
-      if (cmSourceFile* sf = this->Makefile->GetSource(output)) {
+      if (cmSourceFile const* sf = this->Makefile->GetSource(output)) {
         o_symbolic = sf->GetPropertyAsBool("SYMBOLIC");
       }
     }
@@ -1456,6 +1370,38 @@
   return symbolic;
 }
 
+void cmMakefileTargetGenerator::WriteTargetLinkDependRules()
+{
+  if (!this->GeneratorTarget->HasLinkDependencyFile(this->GetConfigName())) {
+    return;
+  }
+
+  auto depFile = this->LocalGenerator->GetLinkDependencyFile(
+    this->GeneratorTarget, this->GetConfigName());
+  this->CleanFiles.insert(depFile);
+  this->LocalGenerator->AddImplicitDepends(
+    this->GeneratorTarget, "LINK",
+    this->GeneratorTarget->GetFullPath(this->GetConfigName()), depFile,
+    cmDependencyScannerKind::Compiler);
+}
+std::string cmMakefileTargetGenerator::GetClangTidyReplacementsFilePath(
+  std::string const& directory, cmSourceFile const& source,
+  std::string const& config) const
+{
+  (void)config;
+  auto const& objectName = this->GeneratorTarget->GetObjectName(&source);
+  auto fixesFile = cmSystemTools::CollapseFullPath(cmStrCat(
+    directory, '/',
+    this->GeneratorTarget->GetLocalGenerator()->MaybeRelativeToTopBinDir(
+      cmStrCat(this->GeneratorTarget->GetLocalGenerator()
+                 ->GetCurrentBinaryDirectory(),
+               '/',
+               this->GeneratorTarget->GetLocalGenerator()->GetTargetDirectory(
+                 this->GeneratorTarget),
+               '/', objectName, ".yaml"))));
+  return fixesFile;
+}
+
 void cmMakefileTargetGenerator::WriteTargetDependRules()
 {
   // must write the targets depend info file
@@ -1497,7 +1443,7 @@
        "# Targets to which this target links which contain Fortran sources.\n"
        "set(CMAKE_Fortran_TARGET_LINKED_INFO_FILES\n";
     /* clang-format on */
-    std::vector<std::string> dirs =
+    std::vector<std::string> const dirs =
       this->GetLinkedTargetDirectories("Fortran", this->GetConfigName());
     for (std::string const& d : dirs) {
       *this->InfoFileStream << "  \"" << d << "/DependInfo.cmake\"\n";
@@ -1529,7 +1475,7 @@
   std::vector<std::string> commands;
 
   // Construct the name of the dependency generation target.
-  std::string depTarget = cmStrCat(
+  std::string const depTarget = cmStrCat(
     this->LocalGenerator->GetRelativeTargetDirectory(this->GeneratorTarget),
     "/depend");
 
@@ -1626,7 +1572,7 @@
   }
 
   cmLocalUnixMakefileGenerator3* localGen{ this->LocalGenerator };
-  std::vector<std::string> architectures = cmExpandedList(architecturesStr);
+  cmList architectures{ architecturesStr };
   std::string const& relPath = localGen->GetHomeRelativeOutputPath();
 
   // Ensure there are no duplicates.
@@ -1639,7 +1585,7 @@
       deps.emplace_back(cmStrCat(relPath, obj));
     }
 
-    std::unordered_set<std::string> depsSet(deps.begin(), deps.end());
+    std::unordered_set<std::string> const depsSet(deps.begin(), deps.end());
     deps.clear();
     std::copy(depsSet.begin(), depsSet.end(), std::back_inserter(deps));
     return deps;
@@ -1723,12 +1669,11 @@
   this->GetDeviceLinkFlags(linkFlags, "CUDA");
   vars.LinkFlags = linkFlags.c_str();
 
-  std::string flags = this->GetFlags("CUDA", this->GetConfigName());
+  std::string const flags = this->GetFlags("CUDA", this->GetConfigName());
   vars.Flags = flags.c_str();
 
   std::string compileCmd = this->GetLinkRule("CMAKE_CUDA_DEVICE_LINK_COMPILE");
-  std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander(
-    localGen->CreateRulePlaceholderExpander());
+  auto rulePlaceholderExpander = localGen->CreateRulePlaceholderExpander();
   rulePlaceholderExpander->ExpandRuleVariables(localGen, compileCmd, vars);
 
   commands.emplace_back(compileCmd);
@@ -1778,8 +1723,8 @@
 
   // Write the rule.
   const std::vector<std::string>& outputs = ccg.GetOutputs();
-  bool symbolic = this->WriteMakeRule(*this->BuildFileStream, nullptr, outputs,
-                                      depends, commands);
+  bool const symbolic = this->WriteMakeRule(*this->BuildFileStream, nullptr,
+                                            outputs, depends, commands);
 
   // Symbolic inputs are not expected to exist, so add dummy rules.
   if (this->CMP0113New && !depends.empty()) {
@@ -2009,8 +1954,14 @@
     return;
   }
 
-  // Loop over all library dependencies.
   const std::string& cfg = this->GetConfigName();
+
+  if (this->GeneratorTarget->HasLinkDependencyFile(cfg)) {
+    depends.push_back(
+      cmStrCat(this->TargetBuildDirectoryFull, "/compiler_depend.ts"));
+  }
+
+  // Loop over all library dependencies.
   if (cmComputeLinkInformation* cli =
         this->GeneratorTarget->GetLinkInformation(cfg)) {
     cm::append(depends, cli->GetDepends());
@@ -2174,16 +2125,16 @@
 
 std::string cmMakefileTargetGenerator::CreateResponseFile(
   const std::string& name, std::string const& options,
-  std::vector<std::string>& makefile_depends)
+  std::vector<std::string>& makefile_depends, std::string const& language)
 {
   // FIXME: Find a better way to determine the response file encoding,
   // perhaps using tool-specific platform information variables.
   // For now, use the makefile encoding as a heuristic.
   codecvt::Encoding responseEncoding =
     this->GlobalGenerator->GetMakefileEncoding();
-  // Non-MSVC tooling may not understand a BOM.
+  // Non-MSVC tooling doesn't understand BOM encoded files.
   if (responseEncoding == codecvt::UTF8_WITH_BOM &&
-      !this->Makefile->IsOn("MSVC")) {
+      (language == "CUDA" || !this->Makefile->IsOn("MSVC"))) {
     responseEncoding = codecvt::UTF8;
   }
 
@@ -2220,7 +2171,7 @@
 void cmMakefileTargetGenerator::CreateLinkLibs(
   cmLinkLineComputer* linkLineComputer, std::string& linkLibs,
   bool useResponseFile, std::vector<std::string>& makefile_depends,
-  ResponseFlagFor responseMode)
+  std::string const& linkLanguage, ResponseFlagFor responseMode)
 {
   std::string frameworkPath;
   std::string linkPath;
@@ -2238,8 +2189,9 @@
     // Create this response file.
     std::string responseFileName =
       (responseMode == Link) ? "linkLibs.rsp" : "deviceLinkLibs.rsp";
-    std::string link_rsp =
-      this->CreateResponseFile(responseFileName, linkLibs, makefile_depends);
+    std::string responseLang = (responseMode == Link) ? linkLanguage : "CUDA";
+    std::string link_rsp = this->CreateResponseFile(
+      responseFileName, linkLibs, makefile_depends, responseLang);
 
     // Reference the response file.
     linkLibs = cmStrCat(responseFlag,
@@ -2251,7 +2203,8 @@
 void cmMakefileTargetGenerator::CreateObjectLists(
   bool useLinkScript, bool useArchiveRules, bool useResponseFile,
   std::string& buildObjs, std::vector<std::string>& makefile_depends,
-  bool useWatcomQuote, ResponseFlagFor responseMode)
+  bool useWatcomQuote, std::string const& linkLanguage,
+  ResponseFlagFor responseMode)
 {
   std::string variableName;
   std::string variableNameExternal;
@@ -2259,7 +2212,7 @@
                              useWatcomQuote);
   if (useResponseFile) {
     // MSVC response files cannot exceed 128K.
-    std::string::size_type const responseFileLimit = 131000;
+    std::string::size_type constexpr responseFileLimit = 131000;
 
     // Construct the individual object list strings.
     std::vector<std::string> object_strings;
@@ -2280,7 +2233,7 @@
 
       // Create this response file.
       std::string objects_rsp = this->CreateResponseFile(
-        responseFileName, object_strings[i], makefile_depends);
+        responseFileName, object_strings[i], makefile_depends, linkLanguage);
 
       // Separate from previous response file references.
       buildObjs += sep;
@@ -2332,8 +2285,8 @@
     }
     std::string name = cmStrCat("includes_", lang, ".rsp");
     std::string arg = std::move(responseFlag) +
-      this->CreateResponseFile(name, includeFlags,
-                               this->FlagFileDepends[lang]);
+      this->CreateResponseFile(name, includeFlags, this->FlagFileDepends[lang],
+                               lang);
     this->LocalGenerator->AppendFlags(flags, arg);
   } else {
     this->LocalGenerator->AppendFlags(flags, includeFlags);
@@ -2392,7 +2345,8 @@
   std::string responseFlag = "@";
   std::string responseFlagVar;
 
-  auto lang = this->GeneratorTarget->GetLinkerLanguage(this->GetConfigName());
+  auto const lang =
+    this->GeneratorTarget->GetLinkerLanguage(this->GetConfigName());
   if (mode == cmMakefileTargetGenerator::ResponseFlagFor::Link) {
     responseFlagVar = cmStrCat("CMAKE_", lang, "_RESPONSE_FILE_LINK_FLAG");
   } else if (mode == cmMakefileTargetGenerator::ResponseFlagFor::DeviceLink) {
diff --git a/Source/cmMakefileTargetGenerator.h b/Source/cmMakefileTargetGenerator.h
index 5d614fe..08d6945 100644
--- a/Source/cmMakefileTargetGenerator.h
+++ b/Source/cmMakefileTargetGenerator.h
@@ -77,9 +77,15 @@
   // write the clean rules for this target
   void WriteTargetCleanRules();
 
+  // write the linker depend rules for this target
+  void WriteTargetLinkDependRules();
   // write the depend rules for this target
   void WriteTargetDependRules();
 
+  std::string GetClangTidyReplacementsFilePath(
+    std::string const& directory, cmSourceFile const& source,
+    std::string const& config) const override;
+
   // write rules for macOS Application Bundle content.
   struct MacOSXContentGeneratorType
     : cmOSXBundleGenerator::MacOSXContentGeneratorType
@@ -160,7 +166,8 @@
       response file name.  */
   std::string CreateResponseFile(const std::string& name,
                                  std::string const& options,
-                                 std::vector<std::string>& makefile_depends);
+                                 std::vector<std::string>& makefile_depends,
+                                 std::string const& language);
 
   bool CheckUseResponseFileForObjects(std::string const& l) const;
   bool CheckUseResponseFileForLibraries(std::string const& l) const;
@@ -175,13 +182,14 @@
   void CreateLinkLibs(cmLinkLineComputer* linkLineComputer,
                       std::string& linkLibs, bool useResponseFile,
                       std::vector<std::string>& makefile_depends,
+                      std::string const& linkLanguage,
                       ResponseFlagFor responseMode = ResponseFlagFor::Link);
 
   /** Create lists of object files for linking and cleaning.  */
   void CreateObjectLists(bool useLinkScript, bool useArchiveRules,
                          bool useResponseFile, std::string& buildObjs,
                          std::vector<std::string>& makefile_depends,
-                         bool useWatcomQuote,
+                         bool useWatcomQuote, std::string const& linkLanguage,
                          ResponseFlagFor responseMode = ResponseFlagFor::Link);
 
   /** Add commands for generate def files */
diff --git a/Source/cmMarkAsAdvancedCommand.cxx b/Source/cmMarkAsAdvancedCommand.cxx
index 73e5f33..87421f5 100644
--- a/Source/cmMarkAsAdvancedCommand.cxx
+++ b/Source/cmMarkAsAdvancedCommand.cxx
@@ -83,7 +83,8 @@
     if (oldBehavior) {
       if (!state->GetCacheEntryValue(variable)) {
         status.GetMakefile().GetCMakeInstance()->AddCacheEntry(
-          variable, nullptr, nullptr, cmStateEnums::UNINITIALIZED);
+          variable, cmValue{ nullptr }, cmValue{ nullptr },
+          cmStateEnums::UNINITIALIZED);
         overwrite = true;
       }
     }
diff --git a/Source/cmMessageCommand.cxx b/Source/cmMessageCommand.cxx
index 52373f3..68b3a5d 100644
--- a/Source/cmMessageCommand.cxx
+++ b/Source/cmMessageCommand.cxx
@@ -3,6 +3,7 @@
 #include "cmMessageCommand.h"
 
 #include <cassert>
+#include <memory>
 #include <utility>
 
 #include <cm/string_view>
@@ -10,6 +11,7 @@
 
 #include "cmConfigureLog.h"
 #include "cmExecutionStatus.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
 #include "cmMessenger.h"
@@ -18,6 +20,10 @@
 #include "cmSystemTools.h"
 #include "cmake.h"
 
+#ifdef CMake_ENABLE_DEBUGGER
+#  include "cmDebuggerAdapter.h"
+#endif
+
 namespace {
 
 enum class CheckingType
@@ -31,13 +37,13 @@
 std::string IndentText(std::string text, cmMakefile& mf)
 {
   auto indent =
-    cmJoin(cmExpandedList(mf.GetSafeDefinition("CMAKE_MESSAGE_INDENT")), "");
+    cmList{ mf.GetSafeDefinition("CMAKE_MESSAGE_INDENT") }.join("");
 
   const auto showContext = mf.GetCMakeInstance()->GetShowLogContext() ||
     mf.IsOn("CMAKE_MESSAGE_CONTEXT_SHOW");
   if (showContext) {
-    auto context = cmJoin(
-      cmExpandedList(mf.GetSafeDefinition("CMAKE_MESSAGE_CONTEXT")), ".");
+    auto context =
+      cmList{ mf.GetSafeDefinition("CMAKE_MESSAGE_CONTEXT") }.join(".");
     if (!context.empty()) {
       indent.insert(0u, cmStrCat("["_s, context, "] "_s));
     }
@@ -201,6 +207,12 @@
 
     case Message::LogLevel::LOG_NOTICE:
       cmSystemTools::Message(IndentText(message, mf));
+#ifdef CMake_ENABLE_DEBUGGER
+      if (mf.GetCMakeInstance()->GetDebugAdapter() != nullptr) {
+        mf.GetCMakeInstance()->GetDebugAdapter()->OnMessageOutput(type,
+                                                                  message);
+      }
+#endif
       break;
 
     case Message::LogLevel::LOG_STATUS:
diff --git a/Source/cmMessenger.cxx b/Source/cmMessenger.cxx
index ff513be..4e975d1 100644
--- a/Source/cmMessenger.cxx
+++ b/Source/cmMessenger.cxx
@@ -16,53 +16,44 @@
 
 #include "cmsys/Terminal.h"
 
+#ifdef CMake_ENABLE_DEBUGGER
+#  include "cmDebuggerAdapter.h"
+#endif
+
 MessageType cmMessenger::ConvertMessageType(MessageType t) const
 {
-  bool warningsAsErrors;
-
   if (t == MessageType::AUTHOR_WARNING || t == MessageType::AUTHOR_ERROR) {
-    warningsAsErrors = this->GetDevWarningsAsErrors();
-    if (warningsAsErrors && t == MessageType::AUTHOR_WARNING) {
-      t = MessageType::AUTHOR_ERROR;
-    } else if (!warningsAsErrors && t == MessageType::AUTHOR_ERROR) {
-      t = MessageType::AUTHOR_WARNING;
+    if (this->GetDevWarningsAsErrors()) {
+      return MessageType::AUTHOR_ERROR;
     }
-  } else if (t == MessageType::DEPRECATION_WARNING ||
-             t == MessageType::DEPRECATION_ERROR) {
-    warningsAsErrors = this->GetDeprecatedWarningsAsErrors();
-    if (warningsAsErrors && t == MessageType::DEPRECATION_WARNING) {
-      t = MessageType::DEPRECATION_ERROR;
-    } else if (!warningsAsErrors && t == MessageType::DEPRECATION_ERROR) {
-      t = MessageType::DEPRECATION_WARNING;
-    }
+    return MessageType::AUTHOR_WARNING;
   }
-
+  if (t == MessageType::DEPRECATION_WARNING ||
+      t == MessageType::DEPRECATION_ERROR) {
+    if (this->GetDeprecatedWarningsAsErrors()) {
+      return MessageType::DEPRECATION_ERROR;
+    }
+    return MessageType::DEPRECATION_WARNING;
+  }
   return t;
 }
 
 bool cmMessenger::IsMessageTypeVisible(MessageType t) const
 {
-  bool isVisible = true;
-
   if (t == MessageType::DEPRECATION_ERROR) {
-    if (!this->GetDeprecatedWarningsAsErrors()) {
-      isVisible = false;
-    }
-  } else if (t == MessageType::DEPRECATION_WARNING) {
-    if (this->GetSuppressDeprecatedWarnings()) {
-      isVisible = false;
-    }
-  } else if (t == MessageType::AUTHOR_ERROR) {
-    if (!this->GetDevWarningsAsErrors()) {
-      isVisible = false;
-    }
-  } else if (t == MessageType::AUTHOR_WARNING) {
-    if (this->GetSuppressDevWarnings()) {
-      isVisible = false;
-    }
+    return this->GetDeprecatedWarningsAsErrors();
+  }
+  if (t == MessageType::DEPRECATION_WARNING) {
+    return !this->GetSuppressDeprecatedWarnings();
+  }
+  if (t == MessageType::AUTHOR_ERROR) {
+    return this->GetDevWarningsAsErrors();
+  }
+  if (t == MessageType::AUTHOR_WARNING) {
+    return !this->GetSuppressDevWarnings();
   }
 
-  return isVisible;
+  return true;
 }
 
 static bool printMessagePreamble(MessageType t, std::ostream& msg)
@@ -220,6 +211,12 @@
   PrintCallStack(msg, backtrace, this->TopSource);
 
   displayMessage(t, msg);
+
+#ifdef CMake_ENABLE_DEBUGGER
+  if (DebuggerAdapter != nullptr) {
+    DebuggerAdapter->OnMessageOutput(t, msg.str());
+  }
+#endif
 }
 
 void cmMessenger::PrintBacktraceTitle(std::ostream& out,
diff --git a/Source/cmMessenger.h b/Source/cmMessenger.h
index 451add0..bdefb00 100644
--- a/Source/cmMessenger.h
+++ b/Source/cmMessenger.h
@@ -5,6 +5,7 @@
 #include "cmConfigure.h" // IWYU pragma: keep
 
 #include <iosfwd>
+#include <memory>
 #include <string>
 
 #include <cm/optional>
@@ -12,6 +13,12 @@
 #include "cmListFileCache.h"
 #include "cmMessageType.h"
 
+#ifdef CMake_ENABLE_DEBUGGER
+namespace cmDebugger {
+class cmDebuggerAdapter;
+}
+#endif
+
 class cmMessenger
 {
 public:
@@ -55,6 +62,13 @@
   // Print the top of a backtrace.
   void PrintBacktraceTitle(std::ostream& out,
                            cmListFileBacktrace const& bt) const;
+#ifdef CMake_ENABLE_DEBUGGER
+  void SetDebuggerAdapter(
+    std::shared_ptr<cmDebugger::cmDebuggerAdapter> const& debuggerAdapter)
+  {
+    DebuggerAdapter = debuggerAdapter;
+  }
+#endif
 
 private:
   bool IsMessageTypeVisible(MessageType t) const;
@@ -66,4 +80,7 @@
   bool SuppressDeprecatedWarnings = false;
   bool DevWarningsAsErrors = false;
   bool DeprecatedWarningsAsErrors = false;
+#ifdef CMake_ENABLE_DEBUGGER
+  std::shared_ptr<cmDebugger::cmDebuggerAdapter> DebuggerAdapter;
+#endif
 };
diff --git a/Source/cmNinjaNormalTargetGenerator.cxx b/Source/cmNinjaNormalTargetGenerator.cxx
index a1633ca..063ca6b 100644
--- a/Source/cmNinjaNormalTargetGenerator.cxx
+++ b/Source/cmNinjaNormalTargetGenerator.cxx
@@ -23,6 +23,7 @@
 #include "cmGlobalNinjaGenerator.h"
 #include "cmLinkLineComputer.h"
 #include "cmLinkLineDeviceComputer.h"
+#include "cmList.h"
 #include "cmLocalCommonGenerator.h"
 #include "cmLocalGenerator.h"
 #include "cmLocalNinjaGenerator.h"
@@ -204,6 +205,31 @@
     '_', config);
 }
 
+std::string cmNinjaNormalTargetGenerator::TextStubsGeneratorRule(
+  const std::string& config) const
+{
+  return cmStrCat(
+    "TEXT_STUBS_GENERATOR__",
+    cmGlobalNinjaGenerator::EncodeRuleName(this->GeneratorTarget->GetName()),
+    '_', config);
+}
+
+bool cmNinjaNormalTargetGenerator::CheckUseResponseFileForLibraries(
+  const std::string& l) const
+{
+  // Check for an explicit setting one way or the other.
+  std::string const responseVar =
+    "CMAKE_" + l + "_USE_RESPONSE_FILE_FOR_LIBRARIES";
+
+  // If the option is defined, read it's value
+  if (cmValue val = this->Makefile->GetDefinition(responseVar)) {
+    return val.IsOn();
+  }
+
+  // Default to true
+  return true;
+}
+
 struct cmNinjaRemoveNoOpCommands
 {
   bool operator()(std::string const& cmd)
@@ -242,9 +268,16 @@
       } else {
         rule.RspContent = "$in_newline";
       }
-      rule.RspContent += " $LINK_LIBRARIES";
+
+      // add the link command in the file if necessary
+      if (this->CheckUseResponseFileForLibraries("CUDA")) {
+        rule.RspContent += " $LINK_LIBRARIES";
+        vars.LinkLibraries = "";
+      } else {
+        vars.LinkLibraries = "$LINK_PATH $LINK_LIBRARIES";
+      }
+
       vars.Objects = responseFlag.c_str();
-      vars.LinkLibraries = "";
     }
 
     vars.ObjectDir = "$OBJECT_DIR";
@@ -263,14 +296,14 @@
     vars.LanguageCompileFlags = "$LANGUAGE_COMPILE_FLAGS";
 
     std::string launcher;
-    cmValue val = this->GetLocalGenerator()->GetRuleLauncher(
-      this->GetGeneratorTarget(), "RULE_LAUNCH_LINK");
+    std::string val = this->GetLocalGenerator()->GetRuleLauncher(
+      this->GetGeneratorTarget(), "RULE_LAUNCH_LINK", config);
     if (cmNonempty(val)) {
-      launcher = cmStrCat(*val, ' ');
+      launcher = cmStrCat(val, ' ');
     }
 
-    std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander(
-      this->GetLocalGenerator()->CreateRulePlaceholderExpander());
+    auto rulePlaceholderExpander =
+      this->GetLocalGenerator()->CreateRulePlaceholderExpander();
 
     // Rule for linking library/executable.
     std::vector<std::string> linkCmds = this->ComputeDeviceLinkCmd();
@@ -329,8 +362,8 @@
 
   std::string compileCmd = this->GetMakefile()->GetRequiredDefinition(
     "CMAKE_CUDA_DEVICE_LINK_COMPILE");
-  std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander(
-    this->GetLocalGenerator()->CreateRulePlaceholderExpander());
+  auto rulePlaceholderExpander =
+    this->GetLocalGenerator()->CreateRulePlaceholderExpander();
   rulePlaceholderExpander->ExpandRuleVariables(this->GetLocalGenerator(),
                                                compileCmd, vars);
 
@@ -384,6 +417,13 @@
     std::string cmakeVarLang =
       cmStrCat("CMAKE_", this->TargetLinkLanguage(config));
 
+    if (this->GeneratorTarget->HasLinkDependencyFile(config)) {
+      auto DepFileFormat = this->GetMakefile()->GetDefinition(
+        cmStrCat(cmakeVarLang, "_LINKER_DEPFILE_FORMAT"));
+      rule.DepType = DepFileFormat;
+      rule.DepFile = "$DEP_FILE";
+    }
+
     // build response file name
     std::string cmakeLinkVar = cmakeVarLang + "_RESPONSE_FILE_LINK_FLAG";
     cmValue flag = this->GetMakefile()->GetDefinition(cmakeLinkVar);
@@ -407,13 +447,20 @@
       } else {
         rule.RspContent = "$in_newline";
       }
-      rule.RspContent += " $LINK_PATH $LINK_LIBRARIES";
+
+      // If libraries in rsp is enable
+      if (this->CheckUseResponseFileForLibraries(lang)) {
+        rule.RspContent += " $LINK_PATH $LINK_LIBRARIES";
+        vars.LinkLibraries = "";
+      } else {
+        vars.LinkLibraries = "$LINK_PATH $LINK_LIBRARIES";
+      }
+
       if (this->TargetLinkLanguage(config) == "Swift") {
         vars.SwiftSources = responseFlag.c_str();
       } else {
         vars.Objects = responseFlag.c_str();
       }
-      vars.LinkLibraries = "";
     }
 
     vars.ObjectDir = "$OBJECT_DIR";
@@ -458,14 +505,14 @@
     }
 
     std::string launcher;
-    cmValue val = this->GetLocalGenerator()->GetRuleLauncher(
-      this->GetGeneratorTarget(), "RULE_LAUNCH_LINK");
+    std::string val = this->GetLocalGenerator()->GetRuleLauncher(
+      this->GetGeneratorTarget(), "RULE_LAUNCH_LINK", config);
     if (cmNonempty(val)) {
-      launcher = cmStrCat(*val, ' ');
+      launcher = cmStrCat(val, ' ');
     }
 
-    std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander(
-      this->GetLocalGenerator()->CreateRulePlaceholderExpander());
+    auto rulePlaceholderExpander =
+      this->GetLocalGenerator()->CreateRulePlaceholderExpander();
 
     // Rule for linking library/executable.
     std::vector<std::string> linkCmds = this->ComputeLinkCmd(config);
@@ -527,11 +574,50 @@
       this->GetGlobalGenerator()->AddRule(rule);
     }
   }
+
+  if (this->GetGeneratorTarget()->IsApple() &&
+      this->GetGeneratorTarget()->HasImportLibrary(config)) {
+    cmNinjaRule rule(this->TextStubsGeneratorRule(config));
+    rule.Comment = cmStrCat("Rule for generating text-based stubs for ",
+                            this->GetVisibleTypeName(), '.');
+    rule.Description = "Creating text-based stubs $out";
+
+    std::string cmd =
+      this->GetMakefile()->GetDefinition("CMAKE_CREATE_TEXT_STUBS");
+    auto rulePlaceholderExpander =
+      this->GetLocalGenerator()->CreateRulePlaceholderExpander();
+    cmRulePlaceholderExpander::RuleVariables vars;
+    vars.Target = "$in";
+    rulePlaceholderExpander->SetTargetImpLib("$out");
+    rulePlaceholderExpander->ExpandRuleVariables(this->GetLocalGenerator(),
+                                                 cmd, vars);
+
+    rule.Command =
+      this->GetLocalGenerator()->BuildCommandLine({ cmd }, config, config);
+    this->GetGlobalGenerator()->AddRule(rule);
+
+    if (tgtNames.ImportOutput != tgtNames.ImportReal &&
+        !this->GetGeneratorTarget()->IsFrameworkOnApple()) {
+      cmNinjaRule slRule("CMAKE_SYMLINK_IMPORT_LIBRARY");
+      {
+        std::string cmakeCommand =
+          this->GetLocalGenerator()->ConvertToOutputFormat(
+            cmSystemTools::GetCMakeCommand(), cmOutputConverter::SHELL);
+        std::string slCmd =
+          cmStrCat(cmakeCommand, " -E cmake_symlink_library $in $SONAME $out");
+        slRule.Command = this->GetLocalGenerator()->BuildCommandLine(
+          { slCmd }, config, config);
+      }
+      slRule.Description = "Creating import library symlink $out";
+      slRule.Comment = "Rule for creating import library symlink.";
+      this->GetGlobalGenerator()->AddRule(slRule);
+    }
+  }
 }
 
 std::vector<std::string> cmNinjaNormalTargetGenerator::ComputeDeviceLinkCmd()
 {
-  std::vector<std::string> linkCmds;
+  cmList linkCmds;
 
   // this target requires separable cuda compilation
   // now build the correct command depending on if the target is
@@ -540,23 +626,23 @@
     case cmStateEnums::STATIC_LIBRARY:
     case cmStateEnums::SHARED_LIBRARY:
     case cmStateEnums::MODULE_LIBRARY: {
-      this->GetMakefile()->GetDefExpandList("CMAKE_CUDA_DEVICE_LINK_LIBRARY",
-                                            linkCmds);
+      linkCmds.assign(
+        this->GetMakefile()->GetDefinition("CMAKE_CUDA_DEVICE_LINK_LIBRARY"));
     } break;
     case cmStateEnums::EXECUTABLE: {
-      this->GetMakefile()->GetDefExpandList(
-        "CMAKE_CUDA_DEVICE_LINK_EXECUTABLE", linkCmds);
+      linkCmds.assign(this->GetMakefile()->GetDefinition(
+        "CMAKE_CUDA_DEVICE_LINK_EXECUTABLE"));
     } break;
     default:
       break;
   }
-  return linkCmds;
+  return std::move(linkCmds.data());
 }
 
 std::vector<std::string> cmNinjaNormalTargetGenerator::ComputeLinkCmd(
   const std::string& config)
 {
-  std::vector<std::string> linkCmds;
+  cmList linkCmds;
   cmMakefile* mf = this->GetMakefile();
   {
     // If we have a rule variable prefer it. In the case of static libraries
@@ -575,7 +661,7 @@
           linkCmdStr += *rule;
         }
       }
-      cmExpandList(linkCmdStr, linkCmds);
+      linkCmds.assign(linkCmdStr);
       if (this->UseLWYU) {
         cmValue lwyuCheck = mf->GetDefinition("CMAKE_LINK_WHAT_YOU_USE_CHECK");
         if (lwyuCheck) {
@@ -594,7 +680,7 @@
           linkCmds.push_back(std::move(cmakeCommand));
         }
       }
-      return linkCmds;
+      return std::move(linkCmds.data());
     }
   }
   switch (this->GetGeneratorTarget()->GetType()) {
@@ -615,7 +701,7 @@
           linkCmdVar, this->TargetLinkLanguage(config), config);
 
         std::string const& linkCmd = mf->GetRequiredDefinition(linkCmdVar);
-        cmExpandList(linkCmd, linkCmds);
+        linkCmds.append(linkCmd);
       }
       {
         std::string linkCmdVar = cmStrCat(
@@ -625,7 +711,7 @@
           linkCmdVar, this->TargetLinkLanguage(config), config);
 
         std::string const& linkCmd = mf->GetRequiredDefinition(linkCmdVar);
-        cmExpandList(linkCmd, linkCmds);
+        linkCmds.append(linkCmd);
       }
 #ifdef __APPLE__
       // On macOS ranlib truncates the fractional part of the static archive
@@ -649,7 +735,7 @@
     default:
       assert(false && "Unexpected target type");
   }
-  return linkCmds;
+  return std::move(linkCmds.data());
 }
 
 void cmNinjaNormalTargetGenerator::WriteDeviceLinkStatement(
@@ -706,7 +792,7 @@
     }
 
     this->WriteDeviceLinkRules(config);
-    this->WriteDeviceLinkStatements(config, cmExpandedList(architecturesStr),
+    this->WriteDeviceLinkStatements(config, cmList{ architecturesStr },
                                     targetOutputReal);
   } else {
     this->WriteNvidiaDeviceLinkStatement(config, fileConfig, targetOutputDir,
@@ -1030,9 +1116,12 @@
       // the current configuration has a postfix. The non-postfix configuration
       // Info.plist can be used by all the other configurations.
       if (!postFix.empty()) {
-        bundleSkipParts.infoPlist = true;
+        bundleSkipParts.InfoPlist = true;
       }
     }
+    if (gt->HasImportLibrary(config)) {
+      bundleSkipParts.TextStubs = false;
+    }
 
     this->OSXBundleGenerator->CreateFramework(
       tgtNames.Output, gt->GetDirectory(config), config, bundleSkipParts);
@@ -1052,6 +1141,14 @@
   cmNinjaBuild linkBuild(this->LanguageLinkerRule(config));
   cmNinjaVars& vars = linkBuild.Variables;
 
+  if (this->GeneratorTarget->HasLinkDependencyFile(config)) {
+    vars["DEP_FILE"] = this->GetLocalGenerator()->ConvertToOutputFormat(
+      this->ConvertToNinjaPath(
+        this->GetLocalGenerator()->GetLinkDependencyFile(this->GeneratorTarget,
+                                                         config)),
+      cmOutputConverter::SHELL);
+  }
+
   // Compute the comment.
   linkBuild.Comment =
     cmStrCat("Link the ", this->GetVisibleTypeName(), ' ', targetOutputReal);
@@ -1214,7 +1311,7 @@
 
   cmGlobalNinjaGenerator::CCOutputs byproducts(this->GetGlobalGenerator());
 
-  if (!tgtNames.ImportLibrary.empty()) {
+  if (!gt->IsApple() && !tgtNames.ImportLibrary.empty()) {
     const std::string impLibPath = localGen.ConvertToOutputFormat(
       targetOutputImplib, cmOutputConverter::SHELL);
     vars["TARGET_IMPLIB"] = impLibPath;
@@ -1471,6 +1568,55 @@
   // Add aliases for the file name and the target name.
   globalGen->AddTargetAlias(tgtNames.Output, gt, config);
   globalGen->AddTargetAlias(this->GetTargetName(), gt, config);
+
+  if (this->GetGeneratorTarget()->IsApple() &&
+      this->GetGeneratorTarget()->HasImportLibrary(config)) {
+    auto dirTBD =
+      gt->GetDirectory(config, cmStateEnums::ImportLibraryArtifact);
+    auto targetTBD =
+      this->ConvertToNinjaPath(cmStrCat(dirTBD, '/', tgtNames.ImportReal));
+    this->EnsureParentDirectoryExists(targetTBD);
+    cmNinjaBuild build(this->TextStubsGeneratorRule(config));
+    build.Comment = cmStrCat("Generate the text-based stubs file ", targetTBD);
+    build.Outputs.push_back(targetTBD);
+    build.ExplicitDeps.push_back(targetOutputReal);
+    globalGen->WriteBuild(this->GetImplFileStream(fileConfig), build);
+
+    if (tgtNames.ImportOutput != tgtNames.ImportReal &&
+        !this->GetGeneratorTarget()->IsFrameworkOnApple()) {
+      auto outputTBD =
+        this->ConvertToNinjaPath(cmStrCat(dirTBD, '/', tgtNames.ImportOutput));
+      std::string const soNameTBD = this->ConvertToNinjaPath(
+        cmStrCat(dirTBD, '/', tgtNames.ImportLibrary));
+
+      cmNinjaBuild slBuild("CMAKE_SYMLINK_IMPORT_LIBRARY");
+      slBuild.Comment = cmStrCat("Create import library symlink ", outputTBD);
+      cmNinjaVars slVars;
+
+      // If one link has to be created.
+      if (targetTBD == soNameTBD || outputTBD == soNameTBD) {
+        slVars["SONAME"] = this->GetLocalGenerator()->ConvertToOutputFormat(
+          soNameTBD, cmOutputConverter::SHELL);
+      } else {
+        slVars["SONAME"].clear();
+        slBuild.Outputs.push_back(soNameTBD);
+        if (firstForConfig) {
+          globalGen->GetByproductsForCleanTarget(config).push_back(soNameTBD);
+        }
+      }
+      slBuild.Outputs.push_back(outputTBD);
+      if (firstForConfig) {
+        globalGen->GetByproductsForCleanTarget(config).push_back(outputTBD);
+      }
+      slBuild.ExplicitDeps.push_back(targetTBD);
+      slBuild.Variables = std::move(slVars);
+
+      globalGen->WriteBuild(this->GetImplFileStream(fileConfig), slBuild);
+    }
+
+    // Add alias for the import file name
+    globalGen->AddTargetAlias(tgtNames.ImportOutput, gt, config);
+  }
 }
 
 void cmNinjaNormalTargetGenerator::WriteObjectLibStatement(
diff --git a/Source/cmNinjaNormalTargetGenerator.h b/Source/cmNinjaNormalTargetGenerator.h
index 30127fe..187ea46 100644
--- a/Source/cmNinjaNormalTargetGenerator.h
+++ b/Source/cmNinjaNormalTargetGenerator.h
@@ -25,7 +25,8 @@
   std::string LanguageLinkerCudaDeviceCompileRule(
     const std::string& config) const;
   std::string LanguageLinkerCudaFatbinaryRule(const std::string& config) const;
-
+  std::string TextStubsGeneratorRule(const std::string& config) const;
+  bool CheckUseResponseFileForLibraries(const std::string& config) const;
   const char* GetVisibleTypeName() const;
   void WriteLanguagesRules(const std::string& config);
 
diff --git a/Source/cmNinjaTargetGenerator.cxx b/Source/cmNinjaTargetGenerator.cxx
index 5abf609..25e00d3 100644
--- a/Source/cmNinjaTargetGenerator.cxx
+++ b/Source/cmNinjaTargetGenerator.cxx
@@ -3,11 +3,13 @@
 #include "cmNinjaTargetGenerator.h"
 
 #include <algorithm>
+#include <array>
 #include <cassert>
 #include <functional>
 #include <iterator>
 #include <map>
 #include <ostream>
+#include <type_traits>
 #include <unordered_map>
 #include <unordered_set>
 #include <utility>
@@ -27,8 +29,8 @@
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorTarget.h"
-#include "cmGlobalCommonGenerator.h"
 #include "cmGlobalNinjaGenerator.h"
+#include "cmList.h"
 #include "cmLocalGenerator.h"
 #include "cmLocalNinjaGenerator.h"
 #include "cmMakefile.h"
@@ -168,9 +170,9 @@
   cmSourceFile const* source, const std::string& language,
   const std::string& config)
 {
-  std::vector<std::string> architectures;
   std::unordered_map<std::string, std::string> pchSources;
-  this->GeneratorTarget->GetAppleArchs(config, architectures);
+  std::vector<std::string> architectures =
+    this->GeneratorTarget->GetAppleArchs(config, language);
   if (architectures.empty()) {
     architectures.emplace_back();
   }
@@ -396,15 +398,15 @@
 }
 
 std::string cmNinjaTargetGenerator::GetClangTidyReplacementsFilePath(
-  const std::string& directory, cmSourceFile const* source,
-  const std::string& config) const
+  std::string const& directory, cmSourceFile const& source,
+  std::string const& config) const
 {
-  std::string path = this->LocalGenerator->GetHomeRelativeOutputPath();
+  auto path = this->LocalGenerator->GetHomeRelativeOutputPath();
   if (!path.empty()) {
     path += '/';
   }
   path = cmStrCat(directory, '/', path);
-  std::string const& objectName = this->GeneratorTarget->GetObjectName(source);
+  auto const& objectName = this->GeneratorTarget->GetObjectName(&source);
   path =
     cmStrCat(std::move(path),
              this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget),
@@ -537,12 +539,12 @@
 
 namespace {
 // Create the command to run the dependency scanner
-std::string GetScanCommand(const std::string& cmakeCmd, const std::string& tdi,
-                           const std::string& lang, const std::string& ppFile,
-                           const std::string& ddiFile)
+std::string GetScanCommand(cm::string_view cmakeCmd, cm::string_view tdi,
+                           cm::string_view lang, cm::string_view srcFile,
+                           cm::string_view ddiFile)
 {
   return cmStrCat(cmakeCmd, " -E cmake_ninja_depends --tdi=", tdi,
-                  " --lang=", lang, " --pp=", ppFile,
+                  " --lang=", lang, " --src=", srcFile, " --out=$out",
                   " --dep=$DEP_FILE --obj=$OBJ_FILE --ddi=", ddiFile);
 }
 
@@ -662,18 +664,18 @@
   std::string const modmapFormat =
     this->Makefile->GetSafeDefinition(modmapFormatVar);
 
-  std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander(
-    this->GetLocalGenerator()->CreateRulePlaceholderExpander());
+  auto rulePlaceholderExpander =
+    this->GetLocalGenerator()->CreateRulePlaceholderExpander();
 
   std::string const tdi = this->GetLocalGenerator()->ConvertToOutputFormat(
     this->ConvertToNinjaPath(this->GetTargetDependInfoPath(lang, config)),
     cmLocalGenerator::SHELL);
 
   std::string launcher;
-  cmValue val = this->GetLocalGenerator()->GetRuleLauncher(
-    this->GetGeneratorTarget(), "RULE_LAUNCH_COMPILE");
+  std::string val = this->GetLocalGenerator()->GetRuleLauncher(
+    this->GetGeneratorTarget(), "RULE_LAUNCH_COMPILE", config);
   if (cmNonempty(val)) {
-    launcher = cmStrCat(*val, ' ');
+    launcher = cmStrCat(val, ' ');
   }
 
   std::string const cmakeCmd =
@@ -686,7 +688,7 @@
 
     // Rule to scan dependencies of sources that need preprocessing.
     {
-      std::vector<std::string> scanCommands;
+      cmList scanCommands;
       std::string scanRuleName;
       std::string ppFileName;
       if (compilationPreprocesses) {
@@ -694,8 +696,8 @@
         ppFileName = "$PREPROCESSED_OUTPUT_FILE";
         std::string const& scanCommand = mf->GetRequiredDefinition(
           cmStrCat("CMAKE_EXPERIMENTAL_", lang, "_SCANDEP_SOURCE"));
-        cmExpandList(scanCommand, scanCommands);
-        for (std::string& i : scanCommands) {
+        scanCommands.assign(scanCommand);
+        for (auto& i : scanCommands) {
           i = cmStrCat(launcher, i);
         }
       } else {
@@ -703,8 +705,8 @@
         ppFileName = "$out";
         std::string const& ppCommmand = mf->GetRequiredDefinition(
           cmStrCat("CMAKE_", lang, "_PREPROCESS_SOURCE"));
-        cmExpandList(ppCommmand, scanCommands);
-        for (std::string& i : scanCommands) {
+        scanCommands.assign(ppCommmand);
+        for (auto& i : scanCommands) {
           i = cmStrCat(launcher, i);
         }
         scanCommands.emplace_back(GetScanCommand(cmakeCmd, tdi, lang, "$out",
@@ -859,11 +861,22 @@
         this->Makefile->GetRequiredDefinition("_CMAKE_CUDA_RDC_FLAG");
       cudaCompileMode = cmStrCat(cudaCompileMode, rdcFlag, " ");
     }
-    if (this->GeneratorTarget->GetPropertyAsBool("CUDA_PTX_COMPILATION")) {
-      const std::string& ptxFlag =
-        this->Makefile->GetRequiredDefinition("_CMAKE_CUDA_PTX_FLAG");
-      cudaCompileMode = cmStrCat(cudaCompileMode, ptxFlag);
-    } else {
+    static std::array<cm::string_view, 4> const compileModes{
+      { "PTX"_s, "CUBIN"_s, "FATBIN"_s, "OPTIX"_s }
+    };
+    bool useNormalCompileMode = true;
+    for (cm::string_view mode : compileModes) {
+      auto propName = cmStrCat("CUDA_", mode, "_COMPILATION");
+      auto defName = cmStrCat("_CMAKE_CUDA_", mode, "_FLAG");
+      if (this->GeneratorTarget->GetPropertyAsBool(propName)) {
+        const std::string& flag =
+          this->Makefile->GetRequiredDefinition(defName);
+        cudaCompileMode = cmStrCat(cudaCompileMode, flag);
+        useNormalCompileMode = false;
+        break;
+      }
+    }
+    if (useNormalCompileMode) {
       const std::string& wholeFlag =
         this->Makefile->GetRequiredDefinition("_CMAKE_CUDA_WHOLE_FLAG");
       cudaCompileMode = cmStrCat(cudaCompileMode, wholeFlag);
@@ -872,133 +885,13 @@
   }
 
   // Rule for compiling object file.
-  std::vector<std::string> compileCmds;
   const std::string cmdVar = cmStrCat("CMAKE_", lang, "_COMPILE_OBJECT");
   const std::string& compileCmd = mf->GetRequiredDefinition(cmdVar);
-  cmExpandList(compileCmd, compileCmds);
+  cmList compileCmds(compileCmd);
 
-  // See if we need to use a compiler launcher like ccache or distcc
-  std::string compilerLauncher;
-  if (!compileCmds.empty() &&
-      (lang == "C" || lang == "CXX" || lang == "Fortran" || lang == "CUDA" ||
-       lang == "HIP" || lang == "ISPC" || lang == "OBJC" ||
-       lang == "OBJCXX")) {
-    std::string const clauncher_prop = cmStrCat(lang, "_COMPILER_LAUNCHER");
-    cmValue clauncher = this->GeneratorTarget->GetProperty(clauncher_prop);
-    std::string evaluatedClauncher = cmGeneratorExpression::Evaluate(
-      *clauncher, this->LocalGenerator, config);
-    if (!evaluatedClauncher.empty()) {
-      compilerLauncher = evaluatedClauncher;
-    }
-  }
-
-  // Maybe insert an include-what-you-use runner.
-  if (!compileCmds.empty() &&
-      (lang == "C" || lang == "CXX" || lang == "OBJC" || lang == "OBJCXX")) {
-    std::string const tidy_prop = cmStrCat(lang, "_CLANG_TIDY");
-    cmValue tidy = this->GeneratorTarget->GetProperty(tidy_prop);
-    cmValue iwyu = nullptr;
-    cmValue cpplint = nullptr;
-    cmValue cppcheck = nullptr;
-    if (lang == "C" || lang == "CXX") {
-      std::string const iwyu_prop = cmStrCat(lang, "_INCLUDE_WHAT_YOU_USE");
-      iwyu = this->GeneratorTarget->GetProperty(iwyu_prop);
-      std::string const cpplint_prop = cmStrCat(lang, "_CPPLINT");
-      cpplint = this->GeneratorTarget->GetProperty(cpplint_prop);
-      std::string const cppcheck_prop = cmStrCat(lang, "_CPPCHECK");
-      cppcheck = this->GeneratorTarget->GetProperty(cppcheck_prop);
-    }
-    if (cmNonempty(iwyu) || cmNonempty(tidy) || cmNonempty(cpplint) ||
-        cmNonempty(cppcheck)) {
-      std::string run_iwyu = cmStrCat(cmakeCmd, " -E __run_co_compile");
-      if (!compilerLauncher.empty()) {
-        // In __run_co_compile case the launcher command is supplied
-        // via --launcher=<maybe-list> and consumed
-        run_iwyu +=
-          cmStrCat(" --launcher=",
-                   this->LocalGenerator->EscapeForShell(compilerLauncher));
-        compilerLauncher.clear();
-      }
-      if (cmNonempty(iwyu)) {
-        run_iwyu += " --iwyu=";
-
-        // Only add --driver-mode if it is not already specified, as adding
-        // it unconditionally might override a user-specified driver-mode
-        if (iwyu.Get()->find("--driver-mode=") == std::string::npos) {
-          cmValue p = this->Makefile->GetDefinition(
-            cmStrCat("CMAKE_", lang, "_INCLUDE_WHAT_YOU_USE_DRIVER_MODE"));
-          std::string driverMode;
-
-          if (cmNonempty(p)) {
-            driverMode = *p;
-          } else {
-            driverMode = lang == "C" ? "gcc" : "g++";
-          }
-
-          run_iwyu += this->LocalGenerator->EscapeForShell(
-            cmStrCat(*iwyu, ";--driver-mode=", driverMode));
-        } else {
-          run_iwyu += this->LocalGenerator->EscapeForShell(*iwyu);
-        }
-      }
-      if (cmNonempty(tidy)) {
-        run_iwyu += " --tidy=";
-        cmValue p = this->Makefile->GetDefinition(
-          cmStrCat("CMAKE_", lang, "_CLANG_TIDY_DRIVER_MODE"));
-        std::string driverMode;
-        if (cmNonempty(p)) {
-          driverMode = *p;
-        } else {
-          driverMode = lang == "C" ? "gcc" : "g++";
-        }
-        const bool haveClangTidyExportFixesDir =
-          !this->GeneratorTarget->GetClangTidyExportFixesDirectory(lang)
-             .empty();
-        std::string exportFixes;
-        if (haveClangTidyExportFixesDir) {
-          exportFixes = ";--export-fixes=$CLANG_TIDY_EXPORT_FIXES";
-        }
-        run_iwyu += this->GetLocalGenerator()->EscapeForShell(
-          cmStrCat(*tidy, ";--extra-arg-before=--driver-mode=", driverMode,
-                   exportFixes));
-        if (haveClangTidyExportFixesDir) {
-          std::string search = cmStrCat(
-            this->GetLocalGenerator()->GetState()->UseWindowsShell() ? ""
-                                                                     : "\\",
-            "$$CLANG_TIDY_EXPORT_FIXES");
-          auto loc = run_iwyu.rfind(search);
-          run_iwyu.replace(loc, search.length(), "$CLANG_TIDY_EXPORT_FIXES");
-        }
-      }
-      if (cmNonempty(cpplint)) {
-        run_iwyu += cmStrCat(
-          " --cpplint=", this->GetLocalGenerator()->EscapeForShell(*cpplint));
-      }
-      if (cmNonempty(cppcheck)) {
-        run_iwyu +=
-          cmStrCat(" --cppcheck=",
-                   this->GetLocalGenerator()->EscapeForShell(*cppcheck));
-      }
-      if (cmNonempty(tidy) || cmNonempty(cpplint) || cmNonempty(cppcheck)) {
-        run_iwyu += " --source=$in";
-      }
-      run_iwyu += " -- ";
-      compileCmds.front().insert(0, run_iwyu);
-    }
-  }
-
-  // If compiler launcher was specified and not consumed above, it
-  // goes to the beginning of the command line.
-  if (!compileCmds.empty() && !compilerLauncher.empty()) {
-    std::vector<std::string> args = cmExpandedList(compilerLauncher, true);
-    if (!args.empty()) {
-      args[0] = this->LocalGenerator->ConvertToOutputFormat(
-        args[0], cmOutputConverter::SHELL);
-      for (std::string& i : cmMakeRange(args.begin() + 1, args.end())) {
-        i = this->LocalGenerator->EscapeForShell(i);
-      }
-    }
-    compileCmds.front().insert(0, cmStrCat(cmJoin(args, " "), ' '));
+  if (!compileCmds.empty()) {
+    compileCmds.front().insert(0, "${CODE_CHECK}");
+    compileCmds.front().insert(0, "${LAUNCHER}");
   }
 
   if (!compileCmds.empty()) {
@@ -1008,12 +901,10 @@
   const auto& extraCommands = this->GetMakefile()->GetSafeDefinition(
     cmStrCat("CMAKE_", lang, "_DEPENDS_EXTRA_COMMANDS"));
   if (!extraCommands.empty()) {
-    auto commandList = cmExpandedList(extraCommands);
-    compileCmds.insert(compileCmds.end(), commandList.cbegin(),
-                       commandList.cend());
+    compileCmds.append(extraCommands);
   }
 
-  for (std::string& i : compileCmds) {
+  for (auto& i : compileCmds) {
     i = cmStrCat(launcher, i);
     rulePlaceholderExpander->ExpandRuleVariables(this->GetLocalGenerator(), i,
                                                  vars);
@@ -1139,27 +1030,32 @@
     }
   }
 
-  for (auto const& langDDIFiles : this->Configs[config].DDIFiles) {
-    std::string const& language = langDDIFiles.first;
-    cmNinjaDeps const& ddiFiles = langDDIFiles.second;
+  for (auto const& langScanningFiles : this->Configs[config].ScanningInfo) {
+    std::string const& language = langScanningFiles.first;
+    std::vector<ScanningFiles> const& scanningFiles = langScanningFiles.second;
 
     cmNinjaBuild build(this->LanguageDyndepRule(language, config));
     build.Outputs.push_back(this->GetDyndepFilePath(language, config));
-    build.ExplicitDeps = ddiFiles;
+    build.ImplicitOuts.push_back(
+      cmStrCat(this->Makefile->GetCurrentBinaryDirectory(), '/',
+               this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget),
+               this->GetGlobalGenerator()->ConfigDirectory(config), '/',
+               language, "Modules.json"));
+    for (auto const& scanFiles : scanningFiles) {
+      if (!scanFiles.ScanningOutput.empty()) {
+        build.ExplicitDeps.push_back(scanFiles.ScanningOutput);
+      }
+      if (!scanFiles.ModuleMapFile.empty()) {
+        build.ImplicitOuts.push_back(scanFiles.ModuleMapFile);
+      }
+    }
 
     this->WriteTargetDependInfo(language, config);
 
-    // Make sure dyndep files for all our dependencies have already
-    // been generated so that the '<LANG>Modules.json' files they
-    // produced as side-effects are available for us to read.
-    // Ideally we should depend on the '<LANG>Modules.json' files
-    // from our dependencies directly, but we don't know which of
-    // our dependencies produces them.  Fixing this will require
-    // refactoring the Ninja generator to generate targets in
-    // dependency order so that we can collect the needed information.
-    this->GetLocalGenerator()->AppendTargetDepends(
-      this->GeneratorTarget, build.OrderOnlyDeps, config, fileConfig,
-      DependOnTargetArtifact);
+    for (std::string const& l :
+         this->GetLinkedTargetDirectories(language, config)) {
+      build.ImplicitDeps.push_back(cmStrCat(l, '/', language, "Modules.json"));
+    }
 
     this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig),
                                            build);
@@ -1208,8 +1104,8 @@
 cmNinjaBuild GetScanBuildStatement(const std::string& ruleName,
                                    const std::string& ppFileName,
                                    bool compilePP, bool compilePPWithDefines,
+                                   bool compilationPreprocesses,
                                    cmNinjaBuild& objBuild, cmNinjaVars& vars,
-                                   std::string const& modmapFormat,
                                    const std::string& objectFileName,
                                    cmLocalGenerator* lg)
 {
@@ -1265,6 +1161,13 @@
   } else {
     scanBuild.Outputs.push_back(ddiFile);
     scanBuild.Variables["PREPROCESSED_OUTPUT_FILE"] = ppFileName;
+    if (!compilationPreprocesses) {
+      // Compilation does not preprocess and we are not compiling an
+      // already-preprocessed source.  Make compilation depend on the scan
+      // results to honor implicit dependencies discovered during scanning
+      // (such as Fortran INCLUDE directives).
+      objBuild.ImplicitDeps.emplace_back(ddiFile);
+    }
   }
 
   // Scanning always provides a depfile for preprocessor dependencies. This
@@ -1278,15 +1181,6 @@
     vars.erase("DEP_FILE");
   }
 
-  if (!modmapFormat.empty()) {
-    // XXX(modmap): If changing this path construction, change
-    // `cmGlobalNinjaGenerator::WriteDyndep` to expect the corresponding
-    // file path.
-    std::string const ddModmapFile = cmStrCat(objectFileName, ".modmap");
-    scanBuild.Variables["DYNDEP_MODULE_MAP_FILE"] = ddModmapFile;
-    scanBuild.ImplicitOuts.push_back(ddModmapFile);
-  }
-
   return scanBuild;
 }
 }
@@ -1326,6 +1220,33 @@
   vars["DEFINES"] = this->ComputeDefines(source, language, config);
   vars["INCLUDES"] = this->ComputeIncludes(source, language, config);
 
+  auto compilerLauncher = this->GetCompilerLauncher(language, config);
+
+  cmValue const skipCodeCheck = source->GetProperty("SKIP_LINTING");
+  if (!skipCodeCheck.IsOn()) {
+    auto const cmakeCmd = this->GetLocalGenerator()->ConvertToOutputFormat(
+      cmSystemTools::GetCMakeCommand(), cmLocalGenerator::SHELL);
+    vars["CODE_CHECK"] =
+      this->GenerateCodeCheckRules(*source, compilerLauncher, cmakeCmd, config,
+                                   [this](const std::string& path) {
+                                     return this->ConvertToNinjaPath(path);
+                                   });
+  }
+
+  // If compiler launcher was specified and not consumed above, it
+  // goes to the beginning of the command line.
+  if (!compilerLauncher.empty()) {
+    cmList args{ compilerLauncher, cmList::EmptyElements::Yes };
+    if (!args.empty()) {
+      args[0] = this->LocalGenerator->ConvertToOutputFormat(
+        args[0], cmOutputConverter::SHELL);
+      for (std::string& i : cmMakeRange(args.begin() + 1, args.end())) {
+        i = this->LocalGenerator->EscapeForShell(i);
+      }
+      vars["LAUNCHER"] = args.join(" ") + " ";
+    }
+  }
+
   if (this->GetMakefile()->GetSafeDefinition(
         cmStrCat("CMAKE_", language, "_DEPFILE_FORMAT")) != "msvc"_s) {
     bool replaceExt(false);
@@ -1349,18 +1270,6 @@
     }
   }
 
-  std::string d =
-    this->GeneratorTarget->GetClangTidyExportFixesDirectory(language);
-  if (!d.empty()) {
-    this->GlobalCommonGenerator->AddClangTidyExportFixesDir(d);
-    std::string fixesFile =
-      this->GetClangTidyReplacementsFilePath(d, source, config);
-    this->GlobalCommonGenerator->AddClangTidyExportFixesFile(fixesFile);
-    cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(fixesFile));
-    fixesFile = this->ConvertToNinjaPath(fixesFile);
-    vars["CLANG_TIDY_EXPORT_FIXES"] = fixesFile;
-  }
-
   if (firstForConfig) {
     this->ExportObjectCompileCommand(
       language, sourceFilePath, objectDir, objectFileName, objectFileDir,
@@ -1382,8 +1291,8 @@
   // Add precompile headers dependencies
   std::vector<std::string> depList;
 
-  std::vector<std::string> architectures;
-  this->GeneratorTarget->GetAppleArchs(config, architectures);
+  std::vector<std::string> architectures =
+    this->GeneratorTarget->GetAppleArchs(config, language);
   if (architectures.empty()) {
     architectures.emplace_back();
   }
@@ -1410,7 +1319,7 @@
   }
 
   if (cmValue objectDeps = source->GetProperty("OBJECT_DEPENDS")) {
-    std::vector<std::string> objDepList = cmExpandedList(*objectDeps);
+    cmList objDepList{ *objectDeps };
     std::copy(objDepList.begin(), objDepList.end(),
               std::back_inserter(depList));
   }
@@ -1480,8 +1389,9 @@
     }
 
     cmNinjaBuild ppBuild = GetScanBuildStatement(
-      scanRuleName, ppFileName, compilePP, compilePPWithDefines, objBuild,
-      vars, modmapFormat, objectFileName, this->LocalGenerator);
+      scanRuleName, ppFileName, compilePP, compilePPWithDefines,
+      compilationPreprocesses, objBuild, vars, objectFileName,
+      this->LocalGenerator);
 
     if (compilePP) {
       // In case compilation requires flags that are incompatible with
@@ -1502,9 +1412,10 @@
       vars["INCLUDES"] = cmStrCat(sourceDirectoryFlag, ' ', vars["INCLUDES"]);
     }
 
+    ScanningFiles scanningFiles;
+
     if (firstForConfig) {
-      std::string const ddiFile = cmStrCat(objectFileName, ".ddi");
-      this->Configs[config].DDIFiles[language].push_back(ddiFile);
+      scanningFiles.ScanningOutput = cmStrCat(objectFileName, ".ddi");
     }
 
     this->addPoolNinjaVariable("JOB_POOL_COMPILE", this->GetGeneratorTarget(),
@@ -1518,9 +1429,17 @@
     vars["dyndep"] = dyndep;
 
     if (!modmapFormat.empty()) {
-      std::string const ddModmapFile = cmStrCat(objectFileName, ".modmap");
+      // XXX(modmap): If changing this path construction, change
+      // `cmGlobalNinjaGenerator::WriteDyndep` to expect the corresponding file
+      // path.
+      std::string ddModmapFile = cmStrCat(objectFileName, ".modmap");
       vars["DYNDEP_MODULE_MAP_FILE"] = ddModmapFile;
       objBuild.OrderOnlyDeps.push_back(ddModmapFile);
+      scanningFiles.ModuleMapFile = std::move(ddModmapFile);
+    }
+
+    if (!scanningFiles.IsEmpty()) {
+      this->Configs[config].ScanningInfo[language].emplace_back(scanningFiles);
     }
   }
 
@@ -1617,7 +1536,7 @@
     if (!evaluatedObjectOutputs.empty()) {
       cmNinjaBuild build("phony");
       build.Comment = "Additional output files.";
-      build.Outputs = cmExpandedList(evaluatedObjectOutputs);
+      build.Outputs = cmList{ evaluatedObjectOutputs }.data();
       std::transform(build.Outputs.begin(), build.Outputs.end(),
                      build.Outputs.begin(), this->MapToNinjaPath());
       build.ExplicitDeps = objBuild.Outputs;
@@ -1788,11 +1707,22 @@
         this->Makefile->GetRequiredDefinition("_CMAKE_CUDA_RDC_FLAG");
       cudaCompileMode = cmStrCat(cudaCompileMode, rdcFlag, " ");
     }
-    if (this->GeneratorTarget->GetPropertyAsBool("CUDA_PTX_COMPILATION")) {
-      const std::string& ptxFlag =
-        this->Makefile->GetRequiredDefinition("_CMAKE_CUDA_PTX_FLAG");
-      cudaCompileMode = cmStrCat(cudaCompileMode, ptxFlag);
-    } else {
+    static std::array<cm::string_view, 4> const compileModes{
+      { "PTX"_s, "CUBIN"_s, "FATBIN"_s, "OPTIX"_s }
+    };
+    bool useNormalCompileMode = true;
+    for (cm::string_view mode : compileModes) {
+      auto propName = cmStrCat("CUDA_", mode, "_COMPILATION");
+      auto defName = cmStrCat("_CMAKE_CUDA_", mode, "_FLAG");
+      if (this->GeneratorTarget->GetPropertyAsBool(propName)) {
+        const std::string& flag =
+          this->Makefile->GetRequiredDefinition(defName);
+        cudaCompileMode = cmStrCat(cudaCompileMode, flag);
+        useNormalCompileMode = false;
+        break;
+      }
+    }
+    if (useNormalCompileMode) {
       const std::string& wholeFlag =
         this->Makefile->GetRequiredDefinition("_CMAKE_CUDA_WHOLE_FLAG");
       cudaCompileMode = cmStrCat(cudaCompileMode, wholeFlag);
@@ -1800,16 +1730,15 @@
     compileObjectVars.CudaCompileMode = cudaCompileMode.c_str();
   }
 
-  std::vector<std::string> compileCmds;
   const std::string cmdVar = cmStrCat("CMAKE_", language, "_COMPILE_OBJECT");
   const std::string& compileCmd =
     this->Makefile->GetRequiredDefinition(cmdVar);
-  cmExpandList(compileCmd, compileCmds);
+  cmList compileCmds(compileCmd);
 
-  std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander(
-    this->GetLocalGenerator()->CreateRulePlaceholderExpander());
+  auto rulePlaceholderExpander =
+    this->GetLocalGenerator()->CreateRulePlaceholderExpander();
 
-  for (std::string& i : compileCmds) {
+  for (auto& i : compileCmds) {
     // no launcher for CMAKE_EXPORT_COMPILE_COMMANDS
     rulePlaceholderExpander->ExpandRuleVariables(this->GetLocalGenerator(), i,
                                                  compileObjectVars);
@@ -1827,13 +1756,11 @@
   if (cmValue prop_value =
         this->GeneratorTarget->GetProperty("ADDITIONAL_CLEAN_FILES")) {
     cmLocalNinjaGenerator* lg = this->LocalGenerator;
-    std::vector<std::string> cleanFiles;
-    cmExpandList(cmGeneratorExpression::Evaluate(*prop_value, lg, config,
-                                                 this->GeneratorTarget),
-                 cleanFiles);
+    cmList cleanFiles(cmGeneratorExpression::Evaluate(*prop_value, lg, config,
+                                                      this->GeneratorTarget));
     std::string const& binaryDir = lg->GetCurrentBinaryDirectory();
     cmGlobalNinjaGenerator* gg = lg->GetGlobalNinjaGenerator();
-    for (std::string const& cleanFile : cleanFiles) {
+    for (auto const& cleanFile : cleanFiles) {
       // Support relative paths
       gg->AddAdditionalCleanFile(
         cmSystemTools::CollapseFullPath(cleanFile, binaryDir), config);
diff --git a/Source/cmNinjaTargetGenerator.h b/Source/cmNinjaTargetGenerator.h
index 8bf7986..8c38499 100644
--- a/Source/cmNinjaTargetGenerator.h
+++ b/Source/cmNinjaTargetGenerator.h
@@ -136,8 +136,8 @@
 
   /// @return the clang-tidy replacements file path for the given @a source.
   std::string GetClangTidyReplacementsFilePath(
-    const std::string& directory, cmSourceFile const* source,
-    const std::string& config) const;
+    std::string const& directory, cmSourceFile const& source,
+    std::string const& config) const override;
 
   /// @return the dyndep file path for this target.
   std::string GetDyndepFilePath(std::string const& lang,
@@ -222,12 +222,23 @@
 private:
   cmLocalNinjaGenerator* LocalGenerator;
 
+  struct ScanningFiles
+  {
+    bool IsEmpty() const
+    {
+      return this->ScanningOutput.empty() && this->ModuleMapFile.empty();
+    }
+
+    std::string ScanningOutput;
+    std::string ModuleMapFile;
+  };
+
   struct ByConfig
   {
     /// List of object files for this target.
     cmNinjaDeps Objects;
-    // Fortran Support
-    std::map<std::string, cmNinjaDeps> DDIFiles;
+    // Dyndep Support
+    std::map<std::string, std::vector<ScanningFiles>> ScanningInfo;
     // Swift Support
     Json::Value SwiftOutputMap;
     std::vector<cmCustomCommand const*> CustomCommands;
diff --git a/Source/cmNinjaTypes.h b/Source/cmNinjaTypes.h
index c8a411e..b77e0b5 100644
--- a/Source/cmNinjaTypes.h
+++ b/Source/cmNinjaTypes.h
@@ -5,8 +5,8 @@
 #include "cmConfigure.h" // IWYU pragma: keep
 
 #include <map>
-#include <set>
 #include <string>
+#include <unordered_set>
 #include <utility>
 #include <vector>
 
@@ -17,7 +17,6 @@
 };
 
 using cmNinjaDeps = std::vector<std::string>;
-using cmNinjaOuts = std::set<std::string>;
 using cmNinjaVars = std::map<std::string, std::string>;
 
 class cmNinjaRule
diff --git a/Source/cmOSXBundleGenerator.cxx b/Source/cmOSXBundleGenerator.cxx
index 674735b..4301f05 100644
--- a/Source/cmOSXBundleGenerator.cxx
+++ b/Source/cmOSXBundleGenerator.cxx
@@ -11,6 +11,7 @@
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmTarget.h"
+#include "cmValue.h"
 
 class cmSourceFile;
 
@@ -77,7 +78,7 @@
   std::string frameworkVersion = this->GT->GetFrameworkVersion();
 
   std::string name = cmSystemTools::GetFilenameName(targetName);
-  if (!skipParts.infoPlist) {
+  if (!skipParts.InfoPlist) {
     // Configure the Info.plist file
     std::string plist = newoutpath;
     if (!this->Makefile->PlatformIsAppleEmbedded()) {
@@ -120,6 +121,17 @@
   cmSystemTools::CreateSymlink(oldName, newName);
   this->Makefile->AddCMakeOutputFile(newName);
 
+  if (!skipParts.TextStubs) {
+    // foo.tbd -> Versions/Current/foo.tbd
+    cmValue tbdSuffix =
+      this->Makefile->GetDefinition("CMAKE_APPLE_IMPORT_FILE_SUFFIX");
+    oldName = cmStrCat("Versions/Current/", name, tbdSuffix);
+    newName = cmStrCat(contentdir, name, tbdSuffix);
+    cmSystemTools::RemoveFile(newName);
+    cmSystemTools::CreateSymlink(oldName, newName);
+    this->Makefile->AddCMakeOutputFile(newName);
+  }
+
   // Resources -> Versions/Current/Resources
   if (this->MacContentFolders->find("Resources") !=
       this->MacContentFolders->end()) {
diff --git a/Source/cmOSXBundleGenerator.h b/Source/cmOSXBundleGenerator.h
index c33b087..38453fd 100644
--- a/Source/cmOSXBundleGenerator.h
+++ b/Source/cmOSXBundleGenerator.h
@@ -20,11 +20,10 @@
 
   struct SkipParts
   {
-    SkipParts()
-      : infoPlist(false)
-    {
-    }
-    bool infoPlist; // NOLINT(modernize-use-default-member-init)
+    SkipParts() {} // NOLINT(modernize-use-equals-default)
+
+    bool InfoPlist = false;
+    bool TextStubs = true;
   };
 
   // create an app bundle at a given root, and return
@@ -35,7 +34,7 @@
   // create a framework at a given root
   void CreateFramework(const std::string& targetName, const std::string& root,
                        const std::string& config,
-                       const SkipParts& skipParts = SkipParts());
+                       const SkipParts& skipParts = SkipParts{});
 
   // create a cf bundle at a given root
   void CreateCFBundle(const std::string& targetName, const std::string& root,
diff --git a/Source/cmOptionCommand.cxx b/Source/cmOptionCommand.cxx
index ec54fc5..d589f0e 100644
--- a/Source/cmOptionCommand.cxx
+++ b/Source/cmOptionCommand.cxx
@@ -67,7 +67,7 @@
   }
   bool init = cmIsOn(initialValue);
   status.GetMakefile().AddCacheDefinition(args[0], init ? "ON" : "OFF",
-                                          args[1].c_str(), cmStateEnums::BOOL);
+                                          args[1], cmStateEnums::BOOL);
   if (status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0077) !=
         cmPolicies::NEW &&
       status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0126) ==
diff --git a/Source/cmOutputConverter.cxx b/Source/cmOutputConverter.cxx
index 6c54e01..02981ae 100644
--- a/Source/cmOutputConverter.cxx
+++ b/Source/cmOutputConverter.cxx
@@ -6,16 +6,15 @@
 #include <cassert>
 #include <cctype>
 #include <set>
-#include <vector>
 
 #ifdef _WIN32
 #  include <unordered_map>
 #  include <utility>
 #endif
 
+#include "cmList.h"
 #include "cmState.h"
 #include "cmStateDirectory.h"
-#include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmValue.h"
 
@@ -176,7 +175,12 @@
       }
 
       std::string tmp{};
-      cmSystemTools::GetShortPath(remote, tmp);
+      cmsys::Status status = cmSystemTools::GetShortPath(remote, tmp);
+      if (!status) {
+        // Fallback for cases when Windows refuses to resolve the short path,
+        // like for C:\Program Files\WindowsApps\...
+        tmp = remote;
+      }
       shortPathCache[remote] = tmp;
       return tmp;
     }();
@@ -239,11 +243,6 @@
                                               bool unescapeNinjaConfiguration,
                                               bool forResponse) const
 {
-  // Do not escape shell operators.
-  if (cmOutputConverterIsShellOperator(str)) {
-    return std::string(str);
-  }
-
   // Compute the flags for the target shell environment.
   int flags = 0;
   if (this->GetState()->UseWindowsVSIDE()) {
@@ -279,6 +278,16 @@
     flags |= Shell_Flag_IsUnix;
   }
 
+  return cmOutputConverter::EscapeForShell(str, flags);
+}
+
+std::string cmOutputConverter::EscapeForShell(cm::string_view str, int flags)
+{
+  // Do not escape shell operators.
+  if (cmOutputConverterIsShellOperator(str)) {
+    return std::string(str);
+  }
+
   return Shell_GetArgument(str, flags);
 }
 
@@ -319,7 +328,7 @@
 {
   FortranFormat format = FortranFormatNone;
   if (!value.empty()) {
-    for (std::string const& fi : cmExpandedList(value)) {
+    for (std::string const& fi : cmList(value)) {
       if (fi == "FIXED") {
         format = FortranFormatFixed;
       }
diff --git a/Source/cmOutputConverter.h b/Source/cmOutputConverter.h
index 2717bdd..0ee7afb 100644
--- a/Source/cmOutputConverter.h
+++ b/Source/cmOutputConverter.h
@@ -16,6 +16,7 @@
 {
 public:
   cmOutputConverter(cmStateSnapshot const& snapshot);
+  virtual ~cmOutputConverter() = default;
 
   /**
    * Convert the given remote path to a relative path with respect to
@@ -27,6 +28,15 @@
   std::string MaybeRelativeToTopBinDir(std::string const& path) const;
   std::string MaybeRelativeToCurBinDir(std::string const& path) const;
 
+  /**
+   * The effective working directory can be different for each generator.
+   * By default, equivalent to the current binary directory.
+   */
+  virtual std::string MaybeRelativeToWorkDir(std::string const& path) const
+  {
+    return this->MaybeRelativeToCurBinDir(path);
+  }
+
   std::string const& GetRelativePathTopSource() const;
   std::string const& GetRelativePathTopBinary() const;
   void SetRelativePathTop(std::string const& topSource,
@@ -97,6 +107,7 @@
                              bool forEcho = false, bool useWatcomQuote = false,
                              bool unescapeNinjaConfiguration = false,
                              bool forResponse = false) const;
+  static std::string EscapeForShell(cm::string_view str, int flags);
 
   enum class WrapQuotes
   {
diff --git a/Source/cmOutputRequiredFilesCommand.cxx b/Source/cmOutputRequiredFilesCommand.cxx
index ad276d2..f147ed2 100644
--- a/Source/cmOutputRequiredFilesCommand.cxx
+++ b/Source/cmOutputRequiredFilesCommand.cxx
@@ -14,6 +14,7 @@
 
 #include "cmExecutionStatus.h"
 #include "cmGeneratorExpression.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmSourceFile.h"
 #include "cmStringAlgorithms.h"
@@ -126,9 +127,9 @@
       std::string incDirs = cmGeneratorExpression::Preprocess(
         *incDirProp, cmGeneratorExpression::StripAllGeneratorExpressions);
 
-      std::vector<std::string> includes = cmExpandedList(incDirs);
+      cmList includes{ incDirs };
 
-      for (std::string& path : includes) {
+      for (auto& path : includes) {
         this->Makefile->ExpandVariablesInString(path);
         if (uniqueIncludes.insert(path).second) {
           orderedAndUniqueIncludes.push_back(path);
diff --git a/Source/cmParseArgumentsCommand.cxx b/Source/cmParseArgumentsCommand.cxx
index 7e19566..b0462f0 100644
--- a/Source/cmParseArgumentsCommand.cxx
+++ b/Source/cmParseArgumentsCommand.cxx
@@ -12,6 +12,7 @@
 #include "cmArgumentParser.h"
 #include "cmArgumentParserTypes.h"
 #include "cmExecutionStatus.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
 #include "cmRange.h"
@@ -169,17 +170,15 @@
   };
 
   // the second argument is a (cmake) list of options without argument
-  std::vector<std::string> list = cmExpandedList(*argIter++);
+  cmList list{ *argIter++ };
   parser.Bind(list, options, duplicateKey);
 
   // the third argument is a (cmake) list of single argument options
-  list.clear();
-  cmExpandList(*argIter++, list);
+  list.assign(*argIter++);
   parser.Bind(list, singleValArgs, duplicateKey);
 
   // the fourth argument is a (cmake) list of multi argument options
-  list.clear();
-  cmExpandList(*argIter++, list);
+  list.assign(*argIter++);
   parser.Bind(list, multiValArgs, duplicateKey);
 
   list.clear();
@@ -187,7 +186,7 @@
     // Flatten ;-lists in the arguments into a single list as was done
     // by the original function(CMAKE_PARSE_ARGUMENTS).
     for (; argIter != argEnd; ++argIter) {
-      cmExpandList(*argIter, list);
+      list.append(*argIter);
     }
   } else {
     // in the PARSE_ARGV move read the arguments from ARGC and ARGV#
diff --git a/Source/cmPolicies.cxx b/Source/cmPolicies.cxx
index da5f5e5..d5e5725 100644
--- a/Source/cmPolicies.cxx
+++ b/Source/cmPolicies.cxx
@@ -259,8 +259,7 @@
 {
   // Warn about policy versions for which support will be removed.
   if (warnCompat == WarnCompat::On &&
-      (majorVer < 2 || (majorVer == 2 && minorVer < 8) ||
-       (majorVer == 2 && minorVer == 8 && patchVer < 12)) &&
+      (majorVer < 3 || (majorVer == 3 && minorVer < 5)) &&
       // Avoid warning on calls generated by install(EXPORT)
       // in CMake versions prior to 3.18.
       !(majorVer == 2 && minorVer == 6 && patchVer == 0 &&
@@ -269,7 +268,7 @@
                                "cmake_policy") == 0)) {
     mf->IssueMessage(
       MessageType::DEPRECATION_WARNING,
-      "Compatibility with CMake < 2.8.12 will be removed from "
+      "Compatibility with CMake < 3.5 will be removed from "
       "a future version of CMake.\n"
       "Update the VERSION argument <min> value or use a ...<max> suffix "
       "to tell CMake that the project does not need compatibility with "
diff --git a/Source/cmPolicies.h b/Source/cmPolicies.h
index fa24f57..a0030d3 100644
--- a/Source/cmPolicies.h
+++ b/Source/cmPolicies.h
@@ -434,7 +434,32 @@
          3, 25, 0, cmPolicies::WARN)                                          \
   SELECT(POLICY, CMP0143,                                                     \
          "Global property USE_FOLDERS treated as ON by default", 3, 26, 0,    \
-         cmPolicies::WARN)
+         cmPolicies::WARN)                                                    \
+  SELECT(POLICY, CMP0144,                                                     \
+         "find_package uses upper-case <PACKAGENAME>_ROOT variables.", 3, 27, \
+         0, cmPolicies::WARN)                                                 \
+  SELECT(POLICY, CMP0145, "The Dart and FindDart modules are removed.", 3,    \
+         27, 0, cmPolicies::WARN)                                             \
+  SELECT(POLICY, CMP0146, "The FindCUDA module is removed.", 3, 27, 0,        \
+         cmPolicies::WARN)                                                    \
+  SELECT(POLICY, CMP0147,                                                     \
+         "Visual Studio generators build custom commands in parallel.", 3,    \
+         27, 0, cmPolicies::WARN)                                             \
+  SELECT(POLICY, CMP0148,                                                     \
+         "The FindPythonInterp and FindPythonLibs modules are removed.", 3,   \
+         27, 0, cmPolicies::WARN)                                             \
+  SELECT(POLICY, CMP0149,                                                     \
+         "Visual Studio generators select latest Windows SDK by default.", 3, \
+         27, 0, cmPolicies::WARN)                                             \
+  SELECT(POLICY, CMP0150,                                                     \
+         "ExternalProject_Add and FetchContent_Declare commands "             \
+         "treat relative GIT_REPOSITORY paths as being relative "             \
+         "to the parent project's remote.",                                   \
+         3, 27, 0, cmPolicies::WARN)                                          \
+  SELECT(POLICY, CMP0151,                                                     \
+         "AUTOMOC include directory is a system include directory by "        \
+         "default.",                                                          \
+         3, 27, 0, cmPolicies::WARN)
 
 #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
 #define CM_FOR_EACH_POLICY_ID(POLICY)                                         \
@@ -474,6 +499,10 @@
   F(CMP0131)                                                                  \
   F(CMP0142)
 
+#define CM_FOR_EACH_CUSTOM_COMMAND_POLICY(F)                                  \
+  F(CMP0116)                                                                  \
+  F(CMP0147)
+
 /** \class cmPolicies
  * \brief Handles changes in CMake behavior and policies
  *
diff --git a/Source/cmProcessOutput.cxx b/Source/cmProcessOutput.cxx
index 10c4215..e1df661 100644
--- a/Source/cmProcessOutput.cxx
+++ b/Source/cmProcessOutput.cxx
@@ -85,7 +85,7 @@
           rawparts[id - 1] += *(raw.end() - 1);
           raw.resize(raw.size() - 1);
         }
-        success = DoDecodeText(raw, decoded, NULL);
+        success = DoDecodeText(raw, decoded, nullptr);
       } else {
         bool restoreDecoded = false;
         std::string firstDecoded = decoded;
@@ -114,7 +114,7 @@
         }
       }
     } else {
-      success = DoDecodeText(raw, decoded, NULL);
+      success = DoDecodeText(raw, decoded, nullptr);
     }
   }
   return success;
@@ -143,7 +143,7 @@
 {
   bool success = false;
   const int wlength =
-    MultiByteToWideChar(codepage, 0, raw.c_str(), int(raw.size()), NULL, 0);
+    MultiByteToWideChar(codepage, 0, raw.c_str(), int(raw.size()), nullptr, 0);
   auto wdata = cm::make_unique<wchar_t[]>(wlength);
   int r = MultiByteToWideChar(codepage, 0, raw.c_str(), int(raw.size()),
                               wdata.get(), wlength);
@@ -156,10 +156,10 @@
       }
     }
     int length = WideCharToMultiByte(defaultCodepage, 0, wdata.get(), wlength,
-                                     NULL, 0, NULL, NULL);
+                                     nullptr, 0, nullptr, nullptr);
     auto data = cm::make_unique<char[]>(length);
     r = WideCharToMultiByte(defaultCodepage, 0, wdata.get(), wlength,
-                            data.get(), length, NULL, NULL);
+                            data.get(), length, nullptr, nullptr);
     if (r > 0) {
       decoded = std::string(data.get(), length);
       success = true;
diff --git a/Source/cmPropertyMap.cxx b/Source/cmPropertyMap.cxx
index b15000f..568a3d2 100644
--- a/Source/cmPropertyMap.cxx
+++ b/Source/cmPropertyMap.cxx
@@ -10,14 +10,9 @@
   this->Map_.clear();
 }
 
-void cmPropertyMap::SetProperty(const std::string& name, const char* value)
+void cmPropertyMap::SetProperty(const std::string& name, std::nullptr_t)
 {
-  if (!value) {
-    this->Map_.erase(name);
-    return;
-  }
-
-  this->Map_[name] = value;
+  this->Map_.erase(name);
 }
 void cmPropertyMap::SetProperty(const std::string& name, cmValue value)
 {
diff --git a/Source/cmPropertyMap.h b/Source/cmPropertyMap.h
index f50b65e..23b50a5 100644
--- a/Source/cmPropertyMap.h
+++ b/Source/cmPropertyMap.h
@@ -4,6 +4,7 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
+#include <cstddef>
 #include <string>
 #include <unordered_map>
 #include <utility>
@@ -25,7 +26,7 @@
   // -- Properties
 
   //! Set the property value
-  void SetProperty(const std::string& name, const char* value);
+  void SetProperty(const std::string& name, std::nullptr_t);
   void SetProperty(const std::string& name, cmValue value);
   void SetProperty(const std::string& name, const std::string& value)
   {
diff --git a/Source/cmQtAutoGenGlobalInitializer.cxx b/Source/cmQtAutoGenGlobalInitializer.cxx
index b7ea7d6..1da8847 100644
--- a/Source/cmQtAutoGenGlobalInitializer.cxx
+++ b/Source/cmQtAutoGenGlobalInitializer.cxx
@@ -13,7 +13,6 @@
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
-#include "cmPolicies.h"
 #include "cmProcessOutput.h"
 #include "cmQtAutoGen.h"
 #include "cmQtAutoGenInitializer.h"
@@ -173,7 +172,6 @@
     // Create utility target
     auto cc = cm::make_unique<cmCustomCommand>();
     cc->SetWorkingDirectory(makefile->GetHomeOutputDirectory().c_str());
-    cc->SetCMP0116Status(cmPolicies::NEW);
     cc->SetEscapeOldStyle(false);
     cc->SetComment(comment.c_str());
     cmTarget* target = localGen->AddUtilityCommand(name, true, std::move(cc));
@@ -266,11 +264,6 @@
   return res;
 }
 
-bool cmQtAutoGenGlobalInitializer::generate()
-{
-  return (this->InitializeCustomTargets() && this->SetupCustomTargets());
-}
-
 bool cmQtAutoGenGlobalInitializer::InitializeCustomTargets()
 {
   // Initialize global autogen targets
diff --git a/Source/cmQtAutoGenGlobalInitializer.h b/Source/cmQtAutoGenGlobalInitializer.h
index 3de5c1a..e8569a5 100644
--- a/Source/cmQtAutoGenGlobalInitializer.h
+++ b/Source/cmQtAutoGenGlobalInitializer.h
@@ -51,14 +51,12 @@
 
   Keywords const& kw() const { return this->Keywords_; }
 
-  bool generate();
+  bool InitializeCustomTargets();
+  bool SetupCustomTargets();
 
 private:
   friend class cmQtAutoGenInitializer;
 
-  bool InitializeCustomTargets();
-  bool SetupCustomTargets();
-
   void GetOrCreateGlobalTarget(cmLocalGenerator* localGen,
                                std::string const& name,
                                std::string const& comment);
diff --git a/Source/cmQtAutoGenInitializer.cxx b/Source/cmQtAutoGenInitializer.cxx
index 66e591e..2c48e78 100644
--- a/Source/cmQtAutoGenInitializer.cxx
+++ b/Source/cmQtAutoGenInitializer.cxx
@@ -5,6 +5,7 @@
 #include <cstddef>
 #include <deque>
 #include <initializer_list>
+#include <limits>
 #include <map>
 #include <set>
 #include <sstream> // for basic_ios, istringstream
@@ -27,11 +28,14 @@
 #include "cmAlgorithms.h"
 #include "cmCustomCommand.h"
 #include "cmCustomCommandLines.h"
+#include "cmEvaluatedTargetProperty.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratorExpression.h"
+#include "cmGeneratorExpressionDAGChecker.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
 #include "cmLinkItem.h"
+#include "cmList.h"
 #include "cmListFileCache.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
@@ -42,6 +46,7 @@
 #include "cmSourceFile.h"
 #include "cmSourceFileLocationKind.h"
 #include "cmSourceGroup.h"
+#include "cmStandardLevelResolver.h"
 #include "cmState.h"
 #include "cmStateTypes.h"
 #include "cmStringAlgorithms.h"
@@ -339,7 +344,7 @@
 
   // Verbosity
   {
-    std::string def =
+    std::string const def =
       this->Makefile->GetSafeDefinition("CMAKE_AUTOGEN_VERBOSE");
     if (!def.empty()) {
       unsigned long iVerb = 0;
@@ -457,12 +462,23 @@
 
     // Autogen target parallel processing
     {
+      using ParallelType = decltype(this->AutogenTarget.Parallel);
+      unsigned long propInt = 0;
       std::string const& prop =
         this->GenTarget->GetSafeProperty("AUTOGEN_PARALLEL");
       if (prop.empty() || (prop == "AUTO")) {
         // Autodetect number of CPUs
         this->AutogenTarget.Parallel = GetParallelCPUCount();
+      } else if (cmStrToULong(prop, &propInt) && propInt > 0 &&
+                 propInt <= std::numeric_limits<ParallelType>::max()) {
+        this->AutogenTarget.Parallel = static_cast<ParallelType>(propInt);
       } else {
+        // Warn the project author that AUTOGEN_PARALLEL is not valid.
+        this->Makefile->IssueMessage(
+          MessageType::AUTHOR_WARNING,
+          cmStrCat("AUTOGEN_PARALLEL=\"", prop, "\" for target \"",
+                   this->GenTarget->GetName(),
+                   "\" is not valid. Using AUTOGEN_PARALLEL=1"));
         this->AutogenTarget.Parallel = 1;
       }
     }
@@ -492,7 +508,7 @@
       std::string const& deps =
         this->GenTarget->GetSafeProperty("AUTOGEN_TARGET_DEPENDS");
       if (!deps.empty()) {
-        for (std::string const& depName : cmExpandedList(deps)) {
+        for (auto const& depName : cmList{ deps }) {
           // Allow target and file dependencies
           auto* depTarget = this->Makefile->FindTargetToUse(depName);
           if (depTarget) {
@@ -530,8 +546,8 @@
       this->Moc.MacroNames.erase(cmRemoveDuplicates(this->Moc.MacroNames),
                                  this->Moc.MacroNames.end());
       {
-        auto filterList = cmExpandedList(
-          this->GenTarget->GetSafeProperty("AUTOMOC_DEPEND_FILTERS"));
+        cmList const filterList = { this->GenTarget->GetSafeProperty(
+          "AUTOMOC_DEPEND_FILTERS") };
         if ((filterList.size() % 2) != 0) {
           cmSystemTools::Error(
             cmStrCat("AutoMoc: AUTOMOC_DEPEND_FILTERS predefs size ",
@@ -543,7 +559,7 @@
           "Q_PLUGIN_METADATA",
           "[\n][ \t]*Q_PLUGIN_METADATA[ \t]*\\("
           "[^\\)]*FILE[ \t]*\"([^\"]+)\"");
-        for (std::size_t ii = 0; ii != filterList.size(); ii += 2) {
+        for (cmList::size_type ii = 0; ii != filterList.size(); ii += 2) {
           this->Moc.DependFilters.emplace_back(filterList[ii],
                                                filterList[ii + 1]);
         }
@@ -558,7 +574,31 @@
 
   // Add autogen include directory to the origin target INCLUDE_DIRECTORIES
   if (this->MocOrUicEnabled() || (this->Rcc.Enabled && this->MultiConfig)) {
-    this->GenTarget->AddIncludeDirectory(this->Dir.IncludeGenExp, true);
+    auto addBefore = false;
+    auto const& value =
+      this->GenTarget->GetProperty("AUTOGEN_USE_SYSTEM_INCLUDE");
+    if (value.IsSet()) {
+      if (cmIsOn(value)) {
+        this->GenTarget->AddSystemIncludeDirectory(this->Dir.IncludeGenExp,
+                                                   "CXX");
+      } else {
+        addBefore = true;
+      }
+    } else {
+      switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0151)) {
+        case cmPolicies::WARN:
+        case cmPolicies::OLD:
+          addBefore = true;
+          break;
+        case cmPolicies::REQUIRED_IF_USED:
+        case cmPolicies::REQUIRED_ALWAYS:
+        case cmPolicies::NEW:
+          this->GenTarget->AddSystemIncludeDirectory(this->Dir.IncludeGenExp,
+                                                     "CXX");
+          break;
+      }
+    }
+    this->GenTarget->AddIncludeDirectory(this->Dir.IncludeGenExp, addBefore);
   }
 
   // Scan files
@@ -598,8 +638,9 @@
   if (this->GenTarget->GetPropertyAsBool("AUTOMOC_COMPILER_PREDEFINES") &&
       (this->QtVersion >= IntegerVersion(5, 8))) {
     // Command
-    this->Makefile->GetDefExpandList("CMAKE_CXX_COMPILER_PREDEFINES_COMMAND",
-                                     this->Moc.PredefsCmd);
+    cmList::assign(
+      this->Moc.PredefsCmd,
+      this->Makefile->GetDefinition("CMAKE_CXX_COMPILER_PREDEFINES_COMMAND"));
     // Header
     if (!this->Moc.PredefsCmd.empty()) {
       this->ConfigFileNames(this->Moc.PredefsFile,
@@ -609,7 +650,7 @@
 
   // Moc includes
   {
-    SearchPathSanitizer sanitizer(this->Makefile);
+    SearchPathSanitizer const sanitizer(this->Makefile);
     auto getDirs =
       [this, &sanitizer](std::string const& cfg) -> std::vector<std::string> {
       // Get the include dirs for this target, without stripping the implicit
@@ -621,8 +662,6 @@
       return sanitizer(dirs);
     };
 
-    // Default configuration include directories
-    this->Moc.Includes.Default = getDirs(this->ConfigDefault);
     // Other configuration settings
     if (this->MultiConfig) {
       for (std::string const& cfg : this->ConfigsList) {
@@ -632,6 +671,9 @@
         }
         this->Moc.Includes.Config[cfg] = std::move(dirs);
       }
+    } else {
+      // Default configuration include directories
+      this->Moc.Includes.Default = getDirs(this->ConfigDefault);
     }
   }
 
@@ -649,8 +691,6 @@
       return defines;
     };
 
-    // Default configuration defines
-    this->Moc.Defines.Default = getDefs(this->ConfigDefault);
     // Other configuration defines
     if (this->MultiConfig) {
       for (std::string const& cfg : this->ConfigsList) {
@@ -660,6 +700,9 @@
         }
         this->Moc.Defines.Config[cfg] = std::move(defines);
       }
+    } else {
+      // Default configuration defines
+      this->Moc.Defines.Default = getDefs(this->ConfigDefault);
     }
   }
 
@@ -686,7 +729,7 @@
       this->GenTarget->GetSafeProperty("AUTOUIC_SEARCH_PATHS");
     if (!usp.empty()) {
       this->Uic.SearchPaths =
-        SearchPathSanitizer(this->Makefile)(cmExpandedList(usp));
+        SearchPathSanitizer(this->Makefile)(cmList{ usp });
     }
   }
   // Uic target options
@@ -946,8 +989,8 @@
           if (uicOpts.empty()) {
             this->Uic.UiFilesNoOptions.emplace_back(fullPath);
           } else {
-            this->Uic.UiFilesWithOptions.emplace_back(fullPath,
-                                                      cmExpandedList(uicOpts));
+            this->Uic.UiFilesWithOptions.emplace_back(
+              fullPath, std::move(cmList{ uicOpts }.data()));
           }
 
           auto uiHeaderRelativePath = cmSystemTools::RelativePath(
@@ -983,8 +1026,24 @@
   if (this->MocOrUicEnabled() && !this->AutogenTarget.FilesGenerated.empty()) {
     if (this->CMP0071Accept) {
       // Let the autogen target depend on the GENERATED files
-      for (MUFile* muf : this->AutogenTarget.FilesGenerated) {
-        this->AutogenTarget.DependFiles.insert(muf->FullPath);
+      if (this->MultiConfig &&
+          this->Makefile->GetSafeDefinition("CMAKE_CROSS_CONFIGS").empty()) {
+        for (MUFile const* muf : this->AutogenTarget.FilesGenerated) {
+          if (muf->Configs.empty()) {
+            this->AutogenTarget.DependFiles.insert(muf->FullPath);
+          } else {
+            for (size_t ci : muf->Configs) {
+              std::string const& config = this->ConfigsList[ci];
+              std::string const& pathWithConfig =
+                cmStrCat("$<$<CONFIG:", config, ">:", muf->FullPath, '>');
+              this->AutogenTarget.DependFiles.insert(pathWithConfig);
+            }
+          }
+        }
+      } else {
+        for (MUFile const* muf : this->AutogenTarget.FilesGenerated) {
+          this->AutogenTarget.DependFiles.insert(muf->FullPath);
+        }
       }
     } else if (this->CMP0071Warn) {
       cm::string_view property;
@@ -996,7 +1055,7 @@
         property = "SKIP_AUTOUIC";
       }
       std::string files;
-      for (MUFile* muf : this->AutogenTarget.FilesGenerated) {
+      for (MUFile const* muf : this->AutogenTarget.FilesGenerated) {
         files += cmStrCat("  ", Quoted(muf->FullPath), '\n');
       }
       this->Makefile->IssueMessage(
@@ -1027,7 +1086,7 @@
       property = "SKIP_AUTOUIC";
     }
     std::string files;
-    for (cmSourceFile* sf : this->AutogenTarget.CMP0100HeadersWarn) {
+    for (cmSourceFile const* sf : this->AutogenTarget.CMP0100HeadersWarn) {
       files += cmStrCat("  ", Quoted(sf->GetFullPath()), '\n');
     }
     this->Makefile->IssueMessage(
@@ -1048,8 +1107,8 @@
   if (!this->Rcc.Qrcs.empty()) {
     const bool modernQt = (this->QtVersion.Major >= 5);
     // Target rcc options
-    std::vector<std::string> optionsTarget =
-      cmExpandedList(this->GenTarget->GetSafeProperty(kw.AUTORCC_OPTIONS));
+    cmList const optionsTarget{ this->GenTarget->GetSafeProperty(
+      kw.AUTORCC_OPTIONS) };
 
     // Check if file name is unique
     for (Qrc& qrc : this->Rcc.Qrcs) {
@@ -1127,7 +1186,8 @@
   if (this->Moc.Enabled) {
     this->AddGeneratedSource(this->Moc.CompilationFile, this->Moc, true);
     if (useNinjaDepfile) {
-      if (this->MultiConfig) {
+      if (this->MultiConfig &&
+          !this->Makefile->GetSafeDefinition("CMAKE_CROSS_CONFIGS").empty()) {
         // Make all mocs_compilation_<CONFIG>.cpp files byproducts of the
         // ${target}_autogen/timestamp custom command.
         // We cannot just use Moc.CompilationFileGenex here, because that
@@ -1172,12 +1232,25 @@
   // instead of fiddling with the include directories
   std::vector<std::string> configs;
   this->GlobalGen->GetQtAutoGenConfigs(configs);
-  bool stdPipesUTF8 = true;
+  bool constexpr stdPipesUTF8 = true;
   cmCustomCommandLines commandLines;
-  for (auto const& config : configs) {
+  if (this->Makefile->GetSafeDefinition("CMAKE_CROSS_CONFIGS").empty()) {
+    std::string autugenInfoFileconfig;
+    if (this->MultiConfig) {
+      autugenInfoFileconfig = "$<CONFIG>";
+    } else {
+      autugenInfoFileconfig = configs[0];
+    }
     commandLines.push_back(cmMakeCommandLine(
       { cmSystemTools::GetCMakeCommand(), "-E", "cmake_autogen",
-        this->AutogenTarget.InfoFile, config }));
+        this->AutogenTarget.InfoFile, autugenInfoFileconfig }));
+
+  } else {
+    for (auto const& config : configs) {
+      commandLines.push_back(cmMakeCommandLine(
+        { cmSystemTools::GetCMakeCommand(), "-E", "cmake_autogen",
+          this->AutogenTarget.InfoFile, config }));
+    }
   }
 
   // Use PRE_BUILD on demand
@@ -1203,7 +1276,7 @@
   // Create the autogen target/command
   if (usePRE_BUILD) {
     // Add additional autogen target dependencies to origin target
-    for (cmTarget* depTarget : this->AutogenTarget.DependTargets) {
+    for (cmTarget const* depTarget : this->AutogenTarget.DependTargets) {
       this->GenTarget->Target->AddUtility(depTarget->GetName(), false,
                                           this->Makefile);
     }
@@ -1238,7 +1311,6 @@
       cc->SetDepends(uicDependencies);
       cc->SetComment("");
       cc->SetWorkingDirectory(this->Dir.Work.c_str());
-      cc->SetCMP0116Status(cmPolicies::NEW);
       cc->SetEscapeOldStyle(false);
       cc->SetStdPipesUTF8(stdPipesUTF8);
       this->LocalGen->AddCustomCommandToOutput(std::move(cc));
@@ -1332,7 +1404,6 @@
       cc->SetByproducts(timestampTargetProvides);
       cc->SetDepends(dependencies);
       cc->SetCommandLines(timestampTargetCommandLines);
-      cc->SetCMP0116Status(cmPolicies::NEW);
       cc->SetEscapeOldStyle(false);
       cmTarget* timestampTarget = this->LocalGen->AddUtilityCommand(
         timestampTargetName, true, std::move(cc));
@@ -1371,7 +1442,6 @@
       cc->SetCommandLines(commandLines);
       cc->SetComment(autogenComment.c_str());
       cc->SetWorkingDirectory(this->Dir.Work.c_str());
-      cc->SetCMP0116Status(cmPolicies::NEW);
       cc->SetEscapeOldStyle(false);
       cc->SetDepfile(this->AutogenTarget.DepFile);
       cc->SetStdPipesUTF8(stdPipesUTF8);
@@ -1391,7 +1461,6 @@
     cc->SetByproducts(autogenByproducts);
     cc->SetDepends(dependencies);
     cc->SetCommandLines(commandLines);
-    cc->SetCMP0116Status(cmPolicies::NEW);
     cc->SetEscapeOldStyle(false);
     cc->SetComment(autogenComment.c_str());
     cmTarget* autogenTarget = this->LocalGen->AddUtilityCommand(
@@ -1472,7 +1541,6 @@
     auto cc = cm::make_unique<cmCustomCommand>();
     cc->SetWorkingDirectory(this->Dir.Work.c_str());
     cc->SetCommandLines(commandLines);
-    cc->SetCMP0116Status(cmPolicies::NEW);
     cc->SetComment(ccComment.c_str());
     cc->SetStdPipesUTF8(true);
 
@@ -1683,6 +1751,34 @@
     info.SetArray("MOC_OPTIONS", this->Moc.Options);
     info.SetBool("MOC_RELAXED_MODE", this->Moc.RelaxedMode);
     info.SetBool("MOC_PATH_PREFIX", this->Moc.PathPrefix);
+
+    cmGeneratorExpressionDAGChecker dagChecker(
+      this->GenTarget, "AUTOMOC_MACRO_NAMES", nullptr, nullptr);
+    EvaluatedTargetPropertyEntries InterfaceAutoMocMacroNamesEntries;
+
+    if (this->MultiConfig) {
+      for (auto const& cfg : this->ConfigsList) {
+        if (!cfg.empty()) {
+          AddInterfaceEntries(this->GenTarget, cfg,
+                              "INTERFACE_AUTOMOC_MACRO_NAMES", "CXX",
+                              &dagChecker, InterfaceAutoMocMacroNamesEntries,
+                              IncludeRuntimeInterface::Yes);
+        }
+      }
+    } else {
+      AddInterfaceEntries(this->GenTarget, this->ConfigDefault,
+                          "INTERFACE_AUTOMOC_MACRO_NAMES", "CXX", &dagChecker,
+                          InterfaceAutoMocMacroNamesEntries,
+                          IncludeRuntimeInterface::Yes);
+    }
+
+    for (auto const& entry : InterfaceAutoMocMacroNamesEntries.Entries) {
+      this->Moc.MacroNames.insert(this->Moc.MacroNames.end(),
+                                  entry.Values.begin(), entry.Values.end());
+    }
+    this->Moc.MacroNames.erase(cmRemoveDuplicates(this->Moc.MacroNames),
+                               this->Moc.MacroNames.end());
+
     info.SetArray("MOC_MACRO_NAMES", this->Moc.MacroNames);
     info.SetArrayArray(
       "MOC_DEPEND_FILTERS", this->Moc.DependFilters,
@@ -1692,8 +1788,22 @@
         jval[1u] = pair.second;
       });
     info.SetConfig("MOC_COMPILATION_FILE", this->Moc.CompilationFile);
-    info.SetArray("MOC_PREDEFS_CMD", this->Moc.PredefsCmd);
     info.SetConfig("MOC_PREDEFS_FILE", this->Moc.PredefsFile);
+
+    cmStandardLevelResolver const resolver{ this->Makefile };
+    auto const CompileOptionFlag =
+      resolver.GetCompileOptionDef(this->GenTarget, "CXX", "");
+
+    auto const CompileOptionValue =
+      this->GenTarget->Makefile->GetSafeDefinition(CompileOptionFlag);
+
+    if (!CompileOptionValue.empty()) {
+      if (this->Moc.PredefsCmd.size() >= 3) {
+        this->Moc.PredefsCmd.insert(this->Moc.PredefsCmd.begin() + 1,
+                                    CompileOptionValue);
+      }
+    }
+    info.SetArray("MOC_PREDEFS_CMD", this->Moc.PredefsCmd);
   }
 
   // Write uic settings
@@ -1767,6 +1877,7 @@
   cmSourceFile* gFile = this->Makefile->GetOrCreateSource(filename, true);
   gFile->MarkAsGenerated();
   gFile->SetProperty("SKIP_AUTOGEN", "1");
+  gFile->SetProperty("SKIP_LINTING", "ON");
   return gFile;
 }
 
@@ -1927,13 +2038,13 @@
   return parseMocVersion(capturedStdOut);
 }
 
-static std::string FindMocExecutableFromMocTarget(cmMakefile* makefile,
+static std::string FindMocExecutableFromMocTarget(cmMakefile const* makefile,
                                                   unsigned int qtMajorVersion)
 {
   std::string result;
   const std::string mocTargetName =
     "Qt" + std::to_string(qtMajorVersion) + "::moc";
-  cmTarget* mocTarget = makefile->FindTargetToUse(mocTargetName);
+  cmTarget const* mocTarget = makefile->FindTargetToUse(mocTargetName);
   if (mocTarget) {
     result = mocTarget->GetSafeProperty("IMPORTED_LOCATION");
   }
diff --git a/Source/cmQtAutoMocUic.cxx b/Source/cmQtAutoMocUic.cxx
index b7af859..a101a81 100644
--- a/Source/cmQtAutoMocUic.cxx
+++ b/Source/cmQtAutoMocUic.cxx
@@ -2272,10 +2272,9 @@
 void cmQtAutoMocUicT::JobDepFilesMergeT::Process()
 {
   if (this->Log().Verbose()) {
-    this->Log().Info(
-      GenT::MOC,
-      cmStrCat("Merging MOC dependencies into ",
-               this->MessagePath(this->BaseConst().DepFile.c_str())));
+    this->Log().Info(GenT::MOC,
+                     cmStrCat("Merging MOC dependencies into ",
+                              this->MessagePath(this->BaseConst().DepFile)));
   }
   auto processDepFile =
     [this](const std::string& mocOutputFile) -> std::vector<std::string> {
diff --git a/Source/cmRST.cxx b/Source/cmRST.cxx
index fa9d4cc..f48330d 100644
--- a/Source/cmRST.cxx
+++ b/Source/cmRST.cxx
@@ -20,8 +20,8 @@
   : OS(os)
   , DocRoot(std::move(docroot))
   , CMakeDirective("^.. (cmake:)?("
-                   "command|envvar|genex|variable"
-                   ")::[ \t]+([^ \t\n]+)$")
+                   "command|envvar|genex|signature|variable"
+                   ")::")
   , CMakeModuleDirective("^.. cmake-module::[ \t]+([^ \t\n]+)$")
   , ParsedLiteralDirective("^.. parsed-literal::[ \t]*(.*)$")
   , CodeBlockDirective("^.. code-block::[ \t]*(.*)$")
@@ -33,6 +33,7 @@
   , VersionDirective("^.. version(added|changed)::[ \t]*(.*)$")
   , ModuleRST(R"(^#\[(=*)\[\.rst:$)")
   , CMakeRole("(:cmake)?:("
+              "cref|"
               "command|cpack_gen|generator|genex|"
               "variable|envvar|module|policy|"
               "prop_cache|prop_dir|prop_gbl|prop_inst|prop_sf|"
@@ -126,27 +127,27 @@
   if (!this->MarkupLines.empty()) {
     cmRST::UnindentLines(this->MarkupLines);
   }
-  switch (this->Directive) {
-    case DirectiveNone:
+  switch (this->DirectiveType) {
+    case Directive::None:
       break;
-    case DirectiveParsedLiteral:
+    case Directive::ParsedLiteral:
       this->ProcessDirectiveParsedLiteral();
       break;
-    case DirectiveLiteralBlock:
+    case Directive::LiteralBlock:
       this->ProcessDirectiveLiteralBlock();
       break;
-    case DirectiveCodeBlock:
+    case Directive::CodeBlock:
       this->ProcessDirectiveCodeBlock();
       break;
-    case DirectiveReplace:
+    case Directive::Replace:
       this->ProcessDirectiveReplace();
       break;
-    case DirectiveTocTree:
+    case Directive::TocTree:
       this->ProcessDirectiveTocTree();
       break;
   }
-  this->Markup = MarkupNone;
-  this->Directive = DirectiveNone;
+  this->MarkupType = Markup::None;
+  this->DirectiveType = Directive::None;
   this->MarkupLines.clear();
 }
 
@@ -160,9 +161,9 @@
       (line.size() >= 3 && line[0] == '.' && line[1] == '.' &&
        isspace(line[2]))) {
     this->Reset();
-    this->Markup =
-      (line.find_first_not_of(" \t", 2) == std::string::npos ? MarkupEmpty
-                                                             : MarkupNormal);
+    this->MarkupType =
+      (line.find_first_not_of(" \t", 2) == std::string::npos ? Markup::Empty
+                                                             : Markup::Normal);
     // XXX(clang-tidy): https://bugs.llvm.org/show_bug.cgi?id=44165
     // NOLINTNEXTLINE(bugprone-branch-clone)
     if (this->CMakeDirective.find(line)) {
@@ -171,33 +172,33 @@
     } else if (this->CMakeModuleDirective.find(line)) {
       // Process cmake-module directive: scan .cmake file comments.
       std::string file = this->CMakeModuleDirective.match(1);
-      if (file.empty() || !this->ProcessInclude(file, IncludeModule)) {
+      if (file.empty() || !this->ProcessInclude(file, Include::Module)) {
         this->NormalLine(line);
       }
     } else if (this->ParsedLiteralDirective.find(line)) {
       // Record the literal lines to output after whole block.
-      this->Directive = DirectiveParsedLiteral;
+      this->DirectiveType = Directive::ParsedLiteral;
       this->MarkupLines.push_back(this->ParsedLiteralDirective.match(1));
     } else if (this->CodeBlockDirective.find(line)) {
       // Record the literal lines to output after whole block.
       // Ignore the language spec and record the opening line as blank.
-      this->Directive = DirectiveCodeBlock;
+      this->DirectiveType = Directive::CodeBlock;
       this->MarkupLines.emplace_back();
     } else if (this->ReplaceDirective.find(line)) {
       // Record the replace directive content.
-      this->Directive = DirectiveReplace;
+      this->DirectiveType = Directive::Replace;
       this->ReplaceName = this->ReplaceDirective.match(1);
       this->MarkupLines.push_back(this->ReplaceDirective.match(2));
     } else if (this->IncludeDirective.find(line)) {
       // Process the include directive or output the directive and its
       // content normally if it fails.
       std::string file = this->IncludeDirective.match(1);
-      if (file.empty() || !this->ProcessInclude(file, IncludeNormal)) {
+      if (file.empty() || !this->ProcessInclude(file, Include::Normal)) {
         this->NormalLine(line);
       }
     } else if (this->TocTreeDirective.find(line)) {
       // Record the toctree entries to process after whole block.
-      this->Directive = DirectiveTocTree;
+      this->DirectiveType = Directive::TocTree;
       this->MarkupLines.push_back(this->TocTreeDirective.match(1));
     } else if (this->ProductionListDirective.find(line)) {
       // Output productionlist directives and their content normally.
@@ -211,14 +212,15 @@
       this->NormalLine(line);
     }
   }
-  // An explicit markup start followed nothing but whitespace and a
+  // An explicit markup start followed by nothing but whitespace and a
   // blank line does not consume any indented text following.
-  else if (this->Markup == MarkupEmpty && line.empty()) {
+  else if (this->MarkupType == Markup::Empty && line.empty()) {
     this->NormalLine(line);
   }
   // Indented lines following an explicit markup start are explicit markup.
-  else if (this->Markup && (line.empty() || isspace(line[0]))) {
-    this->Markup = MarkupNormal;
+  else if (this->MarkupType != Markup::None &&
+           (line.empty() || isspace(line[0]))) {
+    this->MarkupType = Markup::Normal;
     // Record markup lines if the start line was recorded.
     if (!this->MarkupLines.empty()) {
       this->MarkupLines.push_back(line);
@@ -227,8 +229,8 @@
   // A blank line following a paragraph ending in "::" starts a literal block.
   else if (lastLineEndedInColonColon && line.empty()) {
     // Record the literal lines to output after whole block.
-    this->Markup = MarkupNormal;
-    this->Directive = DirectiveLiteralBlock;
+    this->MarkupType = Markup::Normal;
+    this->DirectiveType = Directive::LiteralBlock;
     this->MarkupLines.emplace_back();
     this->OutputLine("", false);
   }
@@ -354,14 +356,14 @@
   this->OutputLinePending = true;
 }
 
-bool cmRST::ProcessInclude(std::string file, IncludeType type)
+bool cmRST::ProcessInclude(std::string file, Include type)
 {
   bool found = false;
   if (this->IncludeDepth < 10) {
     cmRST r(this->OS, this->DocRoot);
     r.IncludeDepth = this->IncludeDepth + 1;
     r.OutputLinePending = this->OutputLinePending;
-    if (type != IncludeTocTree) {
+    if (type != Include::TocTree) {
       r.Replace = this->Replace;
     }
     if (file[0] == '/') {
@@ -369,8 +371,8 @@
     } else {
       file = this->DocDir + "/" + file;
     }
-    found = r.ProcessFile(file, type == IncludeModule);
-    if (type != IncludeTocTree) {
+    found = r.ProcessFile(file, type == Include::Module);
+    if (type != Include::TocTree) {
       this->Replace = r.Replace;
     }
     this->OutputLinePending = r.OutputLinePending;
@@ -408,9 +410,9 @@
     if (!line.empty() && line[0] != ':') {
       if (this->TocTreeLink.find(line)) {
         std::string const& link = this->TocTreeLink.match(1);
-        this->ProcessInclude(link + ".rst", IncludeTocTree);
+        this->ProcessInclude(link + ".rst", Include::TocTree);
       } else {
-        this->ProcessInclude(line + ".rst", IncludeTocTree);
+        this->ProcessInclude(line + ".rst", Include::TocTree);
       }
     }
   }
diff --git a/Source/cmRST.h b/Source/cmRST.h
index ea4ef22..65a8a71 100644
--- a/Source/cmRST.h
+++ b/Source/cmRST.h
@@ -29,26 +29,26 @@
   bool ProcessFile(std::string const& fname, bool isModule = false);
 
 private:
-  enum IncludeType
+  enum class Include
   {
-    IncludeNormal,
-    IncludeModule,
-    IncludeTocTree
+    Normal,
+    Module,
+    TocTree
   };
-  enum MarkupType
+  enum class Markup
   {
-    MarkupNone,
-    MarkupNormal,
-    MarkupEmpty
+    None,
+    Normal,
+    Empty
   };
-  enum DirectiveType
+  enum class Directive
   {
-    DirectiveNone,
-    DirectiveParsedLiteral,
-    DirectiveLiteralBlock,
-    DirectiveCodeBlock,
-    DirectiveReplace,
-    DirectiveTocTree
+    None,
+    ParsedLiteral,
+    LiteralBlock,
+    CodeBlock,
+    Replace,
+    TocTree
   };
 
   void ProcessRST(std::istream& is);
@@ -59,7 +59,7 @@
   void OutputLine(std::string const& line, bool inlineMarkup);
   std::string ReplaceSubstitutions(std::string const& line);
   void OutputMarkupLines(bool inlineMarkup);
-  bool ProcessInclude(std::string file, IncludeType type);
+  bool ProcessInclude(std::string file, Include type);
   void ProcessDirectiveParsedLiteral();
   void ProcessDirectiveLiteralBlock();
   void ProcessDirectiveCodeBlock();
@@ -72,8 +72,8 @@
   int IncludeDepth = 0;
   bool OutputLinePending = false;
   bool LastLineEndedInColonColon = false;
-  MarkupType Markup = MarkupNone;
-  DirectiveType Directive = DirectiveNone;
+  Markup MarkupType = Markup::None;
+  Directive DirectiveType = Directive::None;
   cmsys::RegularExpression CMakeDirective;
   cmsys::RegularExpression CMakeModuleDirective;
   cmsys::RegularExpression ParsedLiteralDirective;
diff --git a/Source/cmRemoveCommand.cxx b/Source/cmRemoveCommand.cxx
index 8af13ae..65a2268 100644
--- a/Source/cmRemoveCommand.cxx
+++ b/Source/cmRemoveCommand.cxx
@@ -3,8 +3,8 @@
 #include "cmRemoveCommand.h"
 
 #include "cmExecutionStatus.h"
+#include "cmList.h"
 #include "cmMakefile.h"
-#include "cmStringAlgorithms.h"
 #include "cmValue.h"
 
 // cmRemoveCommand
@@ -25,12 +25,11 @@
   }
 
   // expand the variable
-  std::vector<std::string> const varArgsExpanded = cmExpandedList(*cacheValue);
+  cmList const varArgsExpanded{ *cacheValue };
 
   // expand the args
   // check for REMOVE(VAR v1 v2 ... vn)
-  std::vector<std::string> const argsExpanded =
-    cmExpandedLists(args.begin() + 1, args.end());
+  cmList const argsExpanded{ args.begin() + 1, args.end() };
 
   // now create the new value
   std::string value;
diff --git a/Source/cmRuntimeDependencyArchive.cxx b/Source/cmRuntimeDependencyArchive.cxx
index 4dfdfae..2fbf2fa 100644
--- a/Source/cmRuntimeDependencyArchive.cxx
+++ b/Source/cmRuntimeDependencyArchive.cxx
@@ -3,13 +3,21 @@
 
 #include "cmRuntimeDependencyArchive.h"
 
+#include <algorithm>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <cm/memory>
+
 #include "cmBinUtilsLinuxELFLinker.h"
 #include "cmBinUtilsMacOSMachOLinker.h"
 #include "cmBinUtilsWindowsPELinker.h"
 #include "cmExecutionStatus.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmStateTypes.h"
-#include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 
 #if defined(_WIN32)
@@ -22,14 +30,6 @@
 #  include "cmVSSetupHelper.h"
 #endif
 
-#include <algorithm>
-#include <sstream>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include <cm/memory>
-
 #if defined(_WIN32)
 static void AddVisualStudioPath(std::vector<std::string>& paths,
                                 const std::string& prefix,
diff --git a/Source/cmSearchPath.cxx b/Source/cmSearchPath.cxx
index d5f6f0d..2b992ef 100644
--- a/Source/cmSearchPath.cxx
+++ b/Source/cmSearchPath.cxx
@@ -9,6 +9,7 @@
 #include <cm/optional>
 
 #include "cmFindCommon.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
@@ -71,7 +72,7 @@
 
   // Get a path from a CMake variable.
   if (cmValue value = this->FC->Makefile->GetDefinition(variable)) {
-    std::vector<std::string> expanded = cmExpandedList(*value);
+    cmList expanded{ *value };
 
     for (std::string const& p : expanded) {
       this->AddPathInternal(
@@ -95,7 +96,7 @@
 
   // Get a path from a CMake variable.
   if (cmValue value = this->FC->Makefile->GetDefinition(variable)) {
-    std::vector<std::string> expanded = cmExpandedList(*value);
+    cmList expanded{ *value };
 
     this->AddPrefixPaths(
       expanded, this->FC->Makefile->GetCurrentSourceDirectory().c_str());
diff --git a/Source/cmSetCommand.cxx b/Source/cmSetCommand.cxx
index ce0cb25..040eb08 100644
--- a/Source/cmSetCommand.cxx
+++ b/Source/cmSetCommand.cxx
@@ -79,8 +79,8 @@
   bool force = false; // optional
   bool parentScope = false;
   cmStateEnums::CacheEntryType type =
-    cmStateEnums::STRING;          // required if cache
-  const char* docstring = nullptr; // required if cache
+    cmStateEnums::STRING; // required if cache
+  cmValue docstring;      // required if cache
 
   unsigned int ignoreLastArgs = 0;
   // look for PARENT_SCOPE argument
@@ -131,7 +131,7 @@
       // ensure that the type is actually converting to a string.
       type = cmStateEnums::STRING;
     }
-    docstring = args[cacheStart + 2].c_str();
+    docstring = cmValue{ args[cacheStart + 2] };
   }
 
   // see if this is already in the cache
@@ -150,8 +150,8 @@
 
   // if it is meant to be in the cache then define it in the cache
   if (cache) {
-    status.GetMakefile().AddCacheDefinition(variable, value, docstring, type,
-                                            force);
+    status.GetMakefile().AddCacheDefinition(variable, cmValue{ value },
+                                            docstring, type, force);
   } else {
     // add the definition
     status.GetMakefile().AddDefinition(variable, value);
diff --git a/Source/cmSourceFile.cxx b/Source/cmSourceFile.cxx
index 3fa0051..3403745 100644
--- a/Source/cmSourceFile.cxx
+++ b/Source/cmSourceFile.cxx
@@ -4,6 +4,9 @@
 
 #include <utility>
 
+#include <cm/string_view>
+#include <cmext/string_view>
+
 #include "cmGlobalGenerator.h"
 #include "cmListFileCache.h"
 #include "cmMakefile.h"
@@ -221,6 +224,11 @@
     case cmPolicies::NEW:
       break;
   }
+  if (lPath == "FILE_SET"_s) {
+    err += "\nHint: the FILE_SET keyword may only appear after a visibility "
+           "specifier or another FILE_SET within the target_sources() "
+           "command.";
+  }
   if (error != nullptr) {
     *error = std::move(err);
   } else {
@@ -270,8 +278,7 @@
   return this->Location.Matches(loc);
 }
 
-template <typename ValueType>
-void cmSourceFile::StoreProperty(const std::string& prop, ValueType value)
+void cmSourceFile::SetProperty(const std::string& prop, cmValue value)
 {
   if (prop == propINCLUDE_DIRECTORIES) {
     this->IncludeDirectories.clear();
@@ -296,15 +303,6 @@
   }
 }
 
-void cmSourceFile::SetProperty(const std::string& prop, const char* value)
-{
-  this->StoreProperty(prop, value);
-}
-void cmSourceFile::SetProperty(const std::string& prop, cmValue value)
-{
-  this->StoreProperty(prop, value);
-}
-
 void cmSourceFile::AppendProperty(const std::string& prop,
                                   const std::string& value, bool asString)
 {
diff --git a/Source/cmSourceFile.h b/Source/cmSourceFile.h
index c1c5201..3f070a7 100644
--- a/Source/cmSourceFile.h
+++ b/Source/cmSourceFile.h
@@ -4,6 +4,7 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
+#include <cstddef>
 #include <memory>
 #include <string>
 #include <vector>
@@ -41,8 +42,11 @@
   void SetCustomCommand(std::unique_ptr<cmCustomCommand> cc);
 
   //! Set/Get a property of this source file
-  void SetProperty(const std::string& prop, const char* value);
   void SetProperty(const std::string& prop, cmValue value);
+  void SetProperty(const std::string& prop, std::nullptr_t)
+  {
+    this->SetProperty(prop, cmValue{ nullptr });
+  }
   void SetProperty(const std::string& prop, const std::string& value)
   {
     this->SetProperty(prop, cmValue(value));
@@ -183,8 +187,8 @@
 #define CM_HEADER_REGEX "\\.(h|hh|h\\+\\+|hm|hpp|hxx|in|txx|inl)$"
 
 #define CM_SOURCE_REGEX                                                       \
-  "\\.(C|F|M|c|c\\+\\+|cc|cpp|mpp|cxx|ixx|cppm|cu|f|f90|for|fpp|ftn|m|mm|"    \
-  "rc|def|r|odl|idl|hpj|bat)$"
+  "\\.(C|F|M|c|c\\+\\+|cc|cpp|mpp|cxx|ixx|cppm|ccm|cxxm|c\\+\\+m|cu"          \
+  "|f|f90|for|fpp|ftn|m|mm|rc|def|r|odl|idl|hpj|bat)$"
 
 #define CM_PCH_REGEX "cmake_pch(_[^.]+)?\\.(h|hxx)$"
 
diff --git a/Source/cmStandardLevelResolver.cxx b/Source/cmStandardLevelResolver.cxx
index d2eac0c..f6e8bc6 100644
--- a/Source/cmStandardLevelResolver.cxx
+++ b/Source/cmStandardLevelResolver.cxx
@@ -18,6 +18,7 @@
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
+#include "cmList.h"
 #include "cmListFileCache.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
@@ -349,7 +350,7 @@
     for (size_t i = 0; i < this->Levels.size(); ++i) {
       if (cmValue prop = makefile->GetDefinition(
             cmStrCat(prefix, this->LevelsAsStrings[i], "_COMPILE_FEATURES"))) {
-        std::vector<std::string> props = cmExpandedList(*prop);
+        cmList props{ *prop };
         if (cm::contains(props, feature)) {
           maxLevel = { static_cast<int>(i), this->Levels[i] };
         }
@@ -468,7 +469,7 @@
     return false;
   }
 
-  std::vector<std::string> availableFeatures = cmExpandedList(features);
+  cmList availableFeatures{ features };
   if (!cm::contains(availableFeatures, feature)) {
     std::ostringstream e;
     e << "The compiler feature \"" << feature << "\" is not known to " << lang
diff --git a/Source/cmState.cxx b/Source/cmState.cxx
index f12f91f..2596d8c 100644
--- a/Source/cmState.cxx
+++ b/Source/cmState.cxx
@@ -101,11 +101,13 @@
 bool cmState::StringToCacheEntryType(const std::string& s,
                                      cmStateEnums::CacheEntryType& type)
 {
-  for (size_t i = 0; i < cmCacheEntryTypes.size(); ++i) {
-    if (s == cmCacheEntryTypes[i]) {
-      type = static_cast<cmStateEnums::CacheEntryType>(i);
-      return true;
-    }
+  // NOLINTNEXTLINE(readability-qualified-auto)
+  auto const entry =
+    std::find(cmCacheEntryTypes.begin(), cmCacheEntryTypes.end(), s);
+  if (entry != cmCacheEntryTypes.end()) {
+    type = static_cast<cmStateEnums::CacheEntryType>(
+      entry - cmCacheEntryTypes.begin());
+    return true;
   }
   return false;
 }
@@ -207,7 +209,7 @@
 }
 
 void cmState::AddCacheEntry(const std::string& key, cmValue value,
-                            const char* helpString,
+                            const std::string& helpString,
                             cmStateEnums::CacheEntryType type)
 {
   this->CacheManager->AddCacheEntry(key, value, helpString, type);
@@ -562,7 +564,8 @@
   this->ScriptedCommands.clear();
 }
 
-void cmState::SetGlobalProperty(const std::string& prop, const char* value)
+void cmState::SetGlobalProperty(const std::string& prop,
+                                const std::string& value)
 {
   this->GlobalProperties.SetProperty(prop, value);
 }
@@ -581,10 +584,10 @@
 {
   if (prop == "CACHE_VARIABLES") {
     std::vector<std::string> cacheKeys = this->GetCacheEntryKeys();
-    this->SetGlobalProperty("CACHE_VARIABLES", cmJoin(cacheKeys, ";").c_str());
+    this->SetGlobalProperty("CACHE_VARIABLES", cmJoin(cacheKeys, ";"));
   } else if (prop == "COMMANDS") {
     std::vector<std::string> commands = this->GetCommandNames();
-    this->SetGlobalProperty("COMMANDS", cmJoin(commands, ";").c_str());
+    this->SetGlobalProperty("COMMANDS", cmJoin(commands, ";"));
   } else if (prop == "IN_TRY_COMPILE") {
     this->SetGlobalProperty(
       "IN_TRY_COMPILE",
@@ -595,10 +598,10 @@
   } else if (prop == "ENABLED_LANGUAGES") {
     std::string langs;
     langs = cmJoin(this->EnabledLanguages, ";");
-    this->SetGlobalProperty("ENABLED_LANGUAGES", langs.c_str());
+    this->SetGlobalProperty("ENABLED_LANGUAGES", langs);
   } else if (prop == "CMAKE_ROLE") {
     std::string mode = this->GetModeString();
-    this->SetGlobalProperty("CMAKE_ROLE", mode.c_str());
+    this->SetGlobalProperty("CMAKE_ROLE", mode);
   }
 #define STRING_LIST_ELEMENT(F) ";" #F
   if (prop == "CMAKE_C_KNOWN_FEATURES") {
diff --git a/Source/cmState.h b/Source/cmState.h
index 9a17b22..1ebd79a 100644
--- a/Source/cmState.h
+++ b/Source/cmState.h
@@ -194,7 +194,7 @@
   void RemoveUserDefinedCommands();
   std::vector<std::string> GetCommandNames() const;
 
-  void SetGlobalProperty(const std::string& prop, const char* value);
+  void SetGlobalProperty(const std::string& prop, const std::string& value);
   void SetGlobalProperty(const std::string& prop, cmValue value);
   void AppendGlobalProperty(const std::string& prop, const std::string& value,
                             bool asString = false);
@@ -253,20 +253,8 @@
 
 private:
   friend class cmake;
-  void AddCacheEntry(const std::string& key, const char* value,
-                     const char* helpString, cmStateEnums::CacheEntryType type)
-  {
-    this->AddCacheEntry(key,
-                        value ? cmValue(std::string(value)) : cmValue(nullptr),
-                        helpString, type);
-  }
-  void AddCacheEntry(const std::string& key, const std::string& value,
-                     const char* helpString, cmStateEnums::CacheEntryType type)
-  {
-    this->AddCacheEntry(key, cmValue(value), helpString, type);
-  }
   void AddCacheEntry(const std::string& key, cmValue value,
-                     const char* helpString,
+                     const std::string& helpString,
                      cmStateEnums::CacheEntryType type);
 
   bool DoWriteGlobVerifyTarget() const;
diff --git a/Source/cmStateDirectory.cxx b/Source/cmStateDirectory.cxx
index 20e4604..6e6fcbd 100644
--- a/Source/cmStateDirectory.cxx
+++ b/Source/cmStateDirectory.cxx
@@ -271,9 +271,8 @@
                this->Snapshot_.Position->LinkDirectoriesPosition);
 }
 
-template <typename ValueType>
-void cmStateDirectory::StoreProperty(const std::string& prop, ValueType value,
-                                     cmListFileBacktrace const& lfbt)
+void cmStateDirectory::SetProperty(const std::string& prop, cmValue value,
+                                   cmListFileBacktrace const& lfbt)
 {
   if (prop == "INCLUDE_DIRECTORIES") {
     if (!value) {
@@ -319,17 +318,6 @@
   this->DirectoryState->Properties.SetProperty(prop, value);
 }
 
-void cmStateDirectory::SetProperty(const std::string& prop, const char* value,
-                                   cmListFileBacktrace const& lfbt)
-{
-  this->StoreProperty(prop, value, lfbt);
-}
-void cmStateDirectory::SetProperty(const std::string& prop, cmValue value,
-                                   cmListFileBacktrace const& lfbt)
-{
-  this->StoreProperty(prop, value, lfbt);
-}
-
 void cmStateDirectory::AppendProperty(const std::string& prop,
                                       const std::string& value, bool asString,
                                       cmListFileBacktrace const& lfbt)
diff --git a/Source/cmStateDirectory.h b/Source/cmStateDirectory.h
index 8c6b09d..55cc716 100644
--- a/Source/cmStateDirectory.h
+++ b/Source/cmStateDirectory.h
@@ -5,6 +5,7 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
+#include <cstddef>
 #include <string>
 #include <vector>
 
@@ -57,10 +58,13 @@
   void SetLinkDirectories(BT<std::string> const& vecs);
   void ClearLinkDirectories();
 
-  void SetProperty(const std::string& prop, const char* value,
-                   cmListFileBacktrace const& lfbt);
   void SetProperty(const std::string& prop, cmValue value,
                    cmListFileBacktrace const& lfbt);
+  void SetProperty(const std::string& prop, std::nullptr_t,
+                   cmListFileBacktrace const& lfbt)
+  {
+    this->SetProperty(prop, cmValue{ nullptr }, lfbt);
+  }
   void AppendProperty(const std::string& prop, const std::string& value,
                       bool asString, cmListFileBacktrace const& lfbt);
   cmValue GetProperty(const std::string& prop) const;
diff --git a/Source/cmStateTypes.h b/Source/cmStateTypes.h
index 010d7e3..24b809b 100644
--- a/Source/cmStateTypes.h
+++ b/Source/cmStateTypes.h
@@ -60,3 +60,14 @@
   ImportLibraryArtifact
 };
 }
+
+namespace cmTraceEnums {
+
+/** \brief Define supported trace formats **/
+enum class TraceOutputFormat
+{
+  Undefined,
+  Human,
+  JSONv1
+};
+};
diff --git a/Source/cmString.cxx b/Source/cmString.cxx
index aefaa64..f7f6293 100644
--- a/Source/cmString.cxx
+++ b/Source/cmString.cxx
@@ -9,7 +9,6 @@
 #include <ostream>
 #include <stdexcept>
 #include <string>
-#include <type_traits>
 
 namespace cm {
 
diff --git a/Source/cmStringAlgorithms.cxx b/Source/cmStringAlgorithms.cxx
index 66bf383..e352d8d 100644
--- a/Source/cmStringAlgorithms.cxx
+++ b/Source/cmStringAlgorithms.cxx
@@ -80,77 +80,6 @@
   return tokens;
 }
 
-void cmExpandList(cm::string_view arg, std::vector<std::string>& argsOut,
-                  bool emptyArgs)
-{
-  // If argument is empty, it is an empty list.
-  if (!emptyArgs && arg.empty()) {
-    return;
-  }
-
-  // if there are no ; in the name then just copy the current string
-  if (arg.find(';') == cm::string_view::npos) {
-    argsOut.emplace_back(arg);
-    return;
-  }
-
-  std::string newArg;
-  // Break the string at non-escaped semicolons not nested in [].
-  int squareNesting = 0;
-  cm::string_view::iterator last = arg.begin();
-  cm::string_view::iterator const cend = arg.end();
-  for (cm::string_view::iterator c = last; c != cend; ++c) {
-    switch (*c) {
-      case '\\': {
-        // We only want to allow escaping of semicolons.  Other
-        // escapes should not be processed here.
-        cm::string_view::iterator cnext = c + 1;
-        if ((cnext != cend) && *cnext == ';') {
-          newArg.append(last, c);
-          // Skip over the escape character
-          last = cnext;
-          c = cnext;
-        }
-      } break;
-      case '[': {
-        ++squareNesting;
-      } break;
-      case ']': {
-        --squareNesting;
-      } break;
-      case ';': {
-        // Break the string here if we are not nested inside square
-        // brackets.
-        if (squareNesting == 0) {
-          newArg.append(last, c);
-          // Skip over the semicolon
-          last = c + 1;
-          if (!newArg.empty() || emptyArgs) {
-            // Add the last argument if the string is not empty.
-            argsOut.push_back(newArg);
-            newArg.clear();
-          }
-        }
-      } break;
-      default: {
-        // Just append this character.
-      } break;
-    }
-  }
-  newArg.append(last, cend);
-  if (!newArg.empty() || emptyArgs) {
-    // Add the last argument if the string is not empty.
-    argsOut.push_back(std::move(newArg));
-  }
-}
-
-std::vector<std::string> cmExpandedList(cm::string_view arg, bool emptyArgs)
-{
-  std::vector<std::string> argsOut;
-  cmExpandList(arg, argsOut, emptyArgs);
-  return argsOut;
-}
-
 namespace {
 template <std::size_t N, typename T>
 inline void MakeDigits(cm::string_view& view, char (&digits)[N],
diff --git a/Source/cmStringAlgorithms.h b/Source/cmStringAlgorithms.h
index 9ea7491..4a9840b 100644
--- a/Source/cmStringAlgorithms.h
+++ b/Source/cmStringAlgorithms.h
@@ -88,63 +88,6 @@
 /** Extract tokens that are separated by any of the characters in @a sep.  */
 std::vector<std::string> cmTokenize(cm::string_view str, cm::string_view sep);
 
-/**
- * Expand the ; separated string @a arg into multiple arguments.
- * All found arguments are appended to @a argsOut.
- */
-void cmExpandList(cm::string_view arg, std::vector<std::string>& argsOut,
-                  bool emptyArgs = false);
-inline void cmExpandList(cmValue arg, std::vector<std::string>& argsOut,
-                         bool emptyArgs = false)
-{
-  if (arg) {
-    cmExpandList(*arg, argsOut, emptyArgs);
-  }
-}
-
-/**
- * Expand out any arguments in the string range [@a first, @a last) that have
- * ; separated strings into multiple arguments.  All found arguments are
- * appended to @a argsOut.
- */
-template <class InputIt>
-void cmExpandLists(InputIt first, InputIt last,
-                   std::vector<std::string>& argsOut)
-{
-  for (; first != last; ++first) {
-    cmExpandList(*first, argsOut);
-  }
-}
-
-/**
- * Same as cmExpandList but a new vector is created containing
- * the expanded arguments from the string @a arg.
- */
-std::vector<std::string> cmExpandedList(cm::string_view arg,
-                                        bool emptyArgs = false);
-inline std::vector<std::string> cmExpandedList(cmValue arg,
-                                               bool emptyArgs = false)
-{
-  if (!arg) {
-    return {};
-  }
-  return cmExpandedList(*arg, emptyArgs);
-}
-
-/**
- * Same as cmExpandList but a new vector is created containing the expanded
- * versions of all arguments in the string range [@a first, @a last).
- */
-template <class InputIt>
-std::vector<std::string> cmExpandedLists(InputIt first, InputIt last)
-{
-  std::vector<std::string> argsOut;
-  for (; first != last; ++first) {
-    cmExpandList(*first, argsOut);
-  }
-  return argsOut;
-}
-
 /** Concatenate string pieces into a single string.  */
 std::string cmCatViews(
   std::initializer_list<std::pair<cm::string_view, std::string*>> views);
diff --git a/Source/cmSystemTools.cxx b/Source/cmSystemTools.cxx
index 0b29b0d..1fb0079 100644
--- a/Source/cmSystemTools.cxx
+++ b/Source/cmSystemTools.cxx
@@ -956,7 +956,7 @@
   // 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(NULL, &req, path.c_str(), NULL);
+  int err = uv_fs_realpath(nullptr, &req, path.c_str(), nullptr);
   if (!err) {
     resolved_path = std::string((char*)req.ptr);
     cmSystemTools::ConvertToUnixSlashes(resolved_path);
@@ -967,12 +967,12 @@
   } else if (err == UV_ENOSYS) {
     resolved_path = cmsys::SystemTools::GetRealPath(path, errorMessage);
   } else if (errorMessage) {
-    LPSTR message = NULL;
+    LPSTR message = nullptr;
     DWORD size = FormatMessageA(
       FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
         FORMAT_MESSAGE_IGNORE_INSERTS,
-      NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&message, 0,
-      NULL);
+      nullptr, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&message,
+      0, nullptr);
     *errorMessage = std::string(message, size);
     LocalFree(message);
 
@@ -1329,32 +1329,33 @@
   std::string thumbprint;
 
   CRYPT_INTEGER_BLOB cryptBlob;
-  HCERTSTORE certStore = NULL;
-  PCCERT_CONTEXT certContext = NULL;
+  HCERTSTORE certStore = nullptr;
+  PCCERT_CONTEXT certContext = nullptr;
 
   HANDLE certFile = CreateFileW(
     cmsys::Encoding::ToWide(source.c_str()).c_str(), GENERIC_READ,
-    FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+    FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
 
-  if (certFile != INVALID_HANDLE_VALUE && certFile != NULL) {
-    DWORD fileSize = GetFileSize(certFile, NULL);
+  if (certFile != INVALID_HANDLE_VALUE && certFile != nullptr) {
+    DWORD fileSize = GetFileSize(certFile, nullptr);
     if (fileSize != INVALID_FILE_SIZE) {
       auto certData = cm::make_unique<BYTE[]>(fileSize);
-      if (certData != NULL) {
+      if (certData != nullptr) {
         DWORD dwRead = 0;
-        if (ReadFile(certFile, certData.get(), fileSize, &dwRead, NULL)) {
+        if (ReadFile(certFile, certData.get(), fileSize, &dwRead, nullptr)) {
           cryptBlob.cbData = fileSize;
           cryptBlob.pbData = certData.get();
 
           // Verify that this is a valid cert
           if (PFXIsPFXBlob(&cryptBlob)) {
             // Open the certificate as a store
-            certStore = PFXImportCertStore(&cryptBlob, NULL, CRYPT_EXPORTABLE);
-            if (certStore != NULL) {
+            certStore =
+              PFXImportCertStore(&cryptBlob, nullptr, CRYPT_EXPORTABLE);
+            if (certStore != nullptr) {
               // There should only be 1 cert.
               certContext =
                 CertEnumCertificatesInStore(certStore, certContext);
-              if (certContext != NULL) {
+              if (certContext != nullptr) {
                 // The hash is 20 bytes
                 BYTE hashData[20];
                 DWORD hashLength = 20;
@@ -1647,6 +1648,32 @@
   return out;
 }
 
+cm::optional<std::string> cmSystemTools::GetEnvVar(std::string const& var)
+{
+  cm::optional<std::string> result;
+  {
+    std::string value;
+    if (cmSystemTools::GetEnv(var, value)) {
+      result = std::move(value);
+    }
+  }
+  return result;
+}
+
+std::vector<std::string> cmSystemTools::SplitEnvPath(std::string const& value)
+{
+#if defined(_WIN32) && !defined(__CYGWIN__)
+  static cm::string_view sep = ";"_s;
+#else
+  static cm::string_view sep = ":"_s;
+#endif
+  std::vector<std::string> paths = cmTokenize(value, sep);
+  for (std::string& p : paths) {
+    SystemTools::ConvertToUnixSlashes(p);
+  }
+  return paths;
+}
+
 #ifndef CMAKE_BOOTSTRAP
 bool cmSystemTools::UnsetEnv(const char* value)
 {
@@ -2375,22 +2402,22 @@
   }
   SECURITY_ATTRIBUTES sa;
   sa.nLength = sizeof(sa);
-  sa.lpSecurityDescriptor = NULL;
+  sa.lpSecurityDescriptor = nullptr;
   sa.bInheritHandle = TRUE;
 
   HANDLE h = CreateFileW(
     L"NUL",
     fd == STD_INPUT_HANDLE ? FILE_GENERIC_READ
                            : FILE_GENERIC_WRITE | FILE_READ_ATTRIBUTES,
-    FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, OPEN_EXISTING, 0, NULL);
+    FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, OPEN_EXISTING, 0, nullptr);
 
   if (h == INVALID_HANDLE_VALUE) {
-    LPSTR message = NULL;
+    LPSTR message = nullptr;
     DWORD size = FormatMessageA(
       FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
         FORMAT_MESSAGE_IGNORE_INSERTS,
-      NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
-      (LPSTR)&message, 0, NULL);
+      nullptr, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+      (LPSTR)&message, 0, nullptr);
     std::string msg = std::string(message, size);
     LocalFree(message);
     std::cerr << "failed to open NUL for missing stdio pipe: " << msg;
@@ -2535,10 +2562,10 @@
 #if defined(_WIN32) && !defined(__CYGWIN__)
   (void)argv0; // ignore this on windows
   wchar_t modulepath[_MAX_PATH];
-  ::GetModuleFileNameW(NULL, modulepath, sizeof(modulepath));
+  ::GetModuleFileNameW(nullptr, modulepath, sizeof(modulepath));
   std::string path = cmsys::Encoding::ToNarrow(modulepath);
   std::string realPath =
-    cmSystemTools::GetRealPathResolvingWindowsSubst(path, NULL);
+    cmSystemTools::GetRealPathResolvingWindowsSubst(path, nullptr);
   if (realPath.empty()) {
     realPath = path;
   }
@@ -3196,20 +3223,41 @@
 {
   const char* endl = lhss;
   const char* endr = rhss;
-  unsigned long lhs;
-  unsigned long rhs;
 
   while (((*endl >= '0') && (*endl <= '9')) ||
          ((*endr >= '0') && (*endr <= '9'))) {
-    // Do component-wise comparison.
-    lhs = strtoul(endl, const_cast<char**>(&endl), 10);
-    rhs = strtoul(endr, const_cast<char**>(&endr), 10);
+    // Do component-wise comparison, ignoring leading zeros
+    // (components are treated as integers, not as mantissas)
+    while (*endl == '0') {
+      endl++;
+    }
+    while (*endr == '0') {
+      endr++;
+    }
 
-    if (lhs < rhs) {
+    const char* beginl = endl;
+    const char* beginr = endr;
+
+    // count significant digits
+    while ((*endl >= '0') && (*endl <= '9')) {
+      endl++;
+    }
+    while ((*endr >= '0') && (*endr <= '9')) {
+      endr++;
+    }
+
+    // compare number of digits first
+    ptrdiff_t r = ((endl - beginl) - (endr - beginr));
+    if (r == 0) {
+      // compare the digits if number of digits is equal
+      r = strncmp(beginl, beginr, endl - beginl);
+    }
+
+    if (r < 0) {
       // lhs < rhs, so true if operation is LESS
       return (op & cmSystemTools::OP_LESS) != 0;
     }
-    if (lhs > rhs) {
+    if (r > 0) {
       // lhs > rhs, so true if operation is GREATER
       return (op & cmSystemTools::OP_GREATER) != 0;
     }
diff --git a/Source/cmSystemTools.h b/Source/cmSystemTools.h
index 09f2bf0..7d55d4b 100644
--- a/Source/cmSystemTools.h
+++ b/Source/cmSystemTools.h
@@ -399,6 +399,9 @@
   static std::string RelativeIfUnder(std::string const& top,
                                      std::string const& in);
 
+  static cm::optional<std::string> GetEnvVar(std::string const& var);
+  static std::vector<std::string> SplitEnvPath(std::string const& value);
+
 #ifndef CMAKE_BOOTSTRAP
   /** Remove an environment variable */
   static bool UnsetEnv(const char* value);
diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx
index debf593..b55554d 100644
--- a/Source/cmTarget.cxx
+++ b/Source/cmTarget.cxx
@@ -4,7 +4,6 @@
 
 #include <algorithm>
 #include <cassert>
-#include <initializer_list>
 #include <iterator>
 #include <map>
 #include <set>
@@ -24,6 +23,7 @@
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
+#include "cmList.h"
 #include "cmListFileCache.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
@@ -93,7 +93,7 @@
   std::ostringstream ss;
   const char* sep = "";
   for (auto const& entry : entries) {
-    std::vector<std::string> files = cmExpandedList(entry.Value);
+    cmList files{ entry.Value };
     for (std::string const& file : files) {
       if (cmHasLiteralPrefix(file, "$<TARGET_OBJECTS:") &&
           file.back() == '>') {
@@ -273,6 +273,357 @@
 
   std::vector<BT<std::string>> Entries;
 };
+
+struct TargetProperty
+{
+  enum class InitCondition
+  {
+    // Always initialize the property.
+    Always,
+    // Never initialize the property.
+    Never,
+    // Only initialize if the target can compile sources.
+    CanCompileSources,
+    // Only apply to Xcode generators.
+    NeedsXcode,
+    // Only apply to Xcode generators on targets that can compile sources.
+    NeedsXcodeAndCanCompileSources,
+    // Needs to be a "normal" target (any non-global, non-utility target).
+    NormalTarget,
+    // Any non-imported target.
+    NonImportedTarget,
+    // Needs to be a "normal" target (any non-global, non-utility target) that
+    // is not `IMPORTED`.
+    NormalNonImportedTarget,
+    // Needs to be a "normal" target with an artifact (no `INTERFACE`
+    // libraries).
+    TargetWithArtifact,
+    // Needs to be a "normal" target with an artifact that is not an
+    // executable.
+    NonExecutableWithArtifact,
+    // Needs to be a linkable library target (no `OBJECT` or `MODULE`
+    // libraries).
+    LinkableLibraryTarget,
+    // Needs to be an executable.
+    ExecutableTarget,
+    // Needs to be a shared library (`SHARED`).
+    SharedLibraryTarget,
+    // Needs to be a target with meaningful symbol exports (`SHARED` or
+    // `EXECUTABLE`).
+    TargetWithSymbolExports,
+    // Targets with "commands" associated with them. Basically everything
+    // except global and `INTERFACE` targets.
+    TargetWithCommands,
+  };
+
+  enum class Repetition
+  {
+    Once,
+    PerConfig,
+    PerConfigPrefix,
+  };
+
+  TargetProperty(cm::static_string_view name)
+    : Name(name)
+  {
+  }
+
+  TargetProperty(cm::static_string_view name, cm::static_string_view dflt,
+                 InitCondition init)
+    : Name(name)
+    , Default(dflt)
+    , InitConditional(init)
+  {
+  }
+
+  TargetProperty(cm::static_string_view name, InitCondition init)
+    : Name(name)
+    , InitConditional(init)
+  {
+  }
+
+  TargetProperty(cm::static_string_view name, InitCondition init,
+                 Repetition repeat)
+    : Name(name)
+    , InitConditional(init)
+    , Repeat(repeat)
+  {
+  }
+
+  cm::static_string_view const Name;
+  cm::optional<cm::static_string_view> const Default = {};
+  InitCondition const InitConditional = InitCondition::Always;
+  Repetition const Repeat = Repetition::Once;
+};
+
+#define IC TargetProperty::InitCondition
+#define R TargetProperty::Repetition
+
+/* clang-format off */
+#define COMMON_LANGUAGE_PROPERTIES(lang)                                      \
+  { #lang "_COMPILER_LAUNCHER"_s, IC::CanCompileSources },                    \
+  { #lang "_STANDARD"_s, IC::CanCompileSources },                             \
+  { #lang "_STANDARD_REQUIRED"_s, IC::CanCompileSources },                    \
+  { #lang "_EXTENSIONS"_s, IC::CanCompileSources },                           \
+  { #lang "_VISIBILITY_PRESET"_s, IC::CanCompileSources }
+/* clang-format on */
+
+TargetProperty const StaticTargetProperties[] = {
+  /* clang-format off */
+  // Compilation properties
+  { "COMPILE_WARNING_AS_ERROR"_s, IC::CanCompileSources },
+  { "INTERPROCEDURAL_OPTIMIZATION"_s, IC::CanCompileSources },
+  { "INTERPROCEDURAL_OPTIMIZATION_"_s, IC::TargetWithArtifact, R::PerConfig },
+  { "NO_SYSTEM_FROM_IMPORTED"_s, IC::CanCompileSources },
+  // Set to `True` for `SHARED` and `MODULE` targets.
+  { "POSITION_INDEPENDENT_CODE"_s, IC::CanCompileSources },
+  { "VISIBILITY_INLINES_HIDDEN"_s, IC::CanCompileSources },
+  // -- Features
+  // ---- PCH
+  { "DISABLE_PRECOMPILE_HEADERS"_s, IC::CanCompileSources },
+  { "PCH_WARN_INVALID"_s, "ON"_s, IC::CanCompileSources },
+  { "PCH_INSTANTIATE_TEMPLATES"_s, "ON"_s, IC::CanCompileSources },
+  // -- Platforms
+  // ---- Android
+  { "ANDROID_API"_s, IC::CanCompileSources },
+  { "ANDROID_API_MIN"_s, IC::CanCompileSources },
+  { "ANDROID_ARCH"_s, IC::CanCompileSources },
+  { "ANDROID_ASSETS_DIRECTORIES"_s, IC::CanCompileSources },
+  { "ANDROID_JAVA_SOURCE_DIR"_s, IC::CanCompileSources },
+  { "ANDROID_STL_TYPE"_s, IC::CanCompileSources },
+  // ---- macOS
+  { "OSX_ARCHITECTURES"_s, IC::CanCompileSources },
+  // ---- Windows
+  { "MSVC_DEBUG_INFORMATION_FORMAT"_s, IC::CanCompileSources },
+  { "MSVC_RUNTIME_LIBRARY"_s, IC::CanCompileSources },
+  { "VS_JUST_MY_CODE_DEBUGGING"_s, IC::CanCompileSources },
+  { "VS_DEBUGGER_COMMAND"_s, IC::ExecutableTarget },
+  { "VS_DEBUGGER_COMMAND_ARGUMENTS"_s, IC::ExecutableTarget },
+  { "VS_DEBUGGER_ENVIRONMENT"_s, IC::ExecutableTarget },
+  { "VS_DEBUGGER_WORKING_DIRECTORY"_s, IC::ExecutableTarget },
+  // ---- OpenWatcom
+  { "WATCOM_RUNTIME_LIBRARY"_s, IC::CanCompileSources },
+  // -- Language
+  // ---- C
+  COMMON_LANGUAGE_PROPERTIES(C),
+  // ---- C++
+  COMMON_LANGUAGE_PROPERTIES(CXX),
+  // ---- CSharp
+  { "DOTNET_SDK"_s, IC::NonImportedTarget },
+  { "DOTNET_TARGET_FRAMEWORK"_s, IC::TargetWithCommands },
+  { "DOTNET_TARGET_FRAMEWORK_VERSION"_s, IC::TargetWithCommands },
+  // ---- CUDA
+  COMMON_LANGUAGE_PROPERTIES(CUDA),
+  { "CUDA_SEPARABLE_COMPILATION"_s, IC::CanCompileSources },
+  { "CUDA_ARCHITECTURES"_s, IC::CanCompileSources },
+  // ---- Fortran
+  { "Fortran_FORMAT"_s, IC::CanCompileSources },
+  { "Fortran_MODULE_DIRECTORY"_s, IC::CanCompileSources },
+  { "Fortran_COMPILER_LAUNCHER"_s, IC::CanCompileSources },
+  { "Fortran_PREPRPOCESS"_s, IC::CanCompileSources },
+  { "Fortran_VISIBILITY_PRESET"_s, IC::CanCompileSources },
+  // ---- HIP
+  COMMON_LANGUAGE_PROPERTIES(HIP),
+  { "HIP_ARCHITECTURES"_s, IC::CanCompileSources },
+  // ---- ISPC
+  { "ISPC_COMPILER_LAUNCHER"_s, IC::CanCompileSources },
+  { "ISPC_HEADER_DIRECTORY"_s, IC::CanCompileSources },
+  { "ISPC_HEADER_SUFFIX"_s, "_ispc.h"_s, IC::CanCompileSources },
+  { "ISPC_INSTRUCTION_SETS"_s, IC::CanCompileSources },
+  // ---- Objective C
+  COMMON_LANGUAGE_PROPERTIES(OBJC),
+  // ---- Objective C++
+  COMMON_LANGUAGE_PROPERTIES(OBJCXX),
+  // ---- Swift
+  { "Swift_LANGUAGE_VERSION"_s, IC::CanCompileSources },
+  { "Swift_MODULE_DIRECTORY"_s, IC::CanCompileSources },
+  // ---- moc
+  { "AUTOMOC"_s, IC::CanCompileSources },
+  { "AUTOMOC_COMPILER_PREDEFINES"_s, IC::CanCompileSources },
+  { "AUTOMOC_MACRO_NAMES"_s, IC::CanCompileSources },
+  { "AUTOMOC_MOC_OPTIONS"_s, IC::CanCompileSources },
+  { "AUTOMOC_PATH_PREFIX"_s, IC::CanCompileSources },
+  { "AUTOMOC_EXECUTABLE"_s, IC::CanCompileSources },
+  // ---- uic
+  { "AUTOUIC"_s, IC::CanCompileSources },
+  { "AUTOUIC_OPTIONS"_s, IC::CanCompileSources },
+  { "AUTOUIC_SEARCH_PATHS"_s, IC::CanCompileSources },
+  { "AUTOUIC_EXECUTABLE"_s, IC::CanCompileSources },
+  // ---- rcc
+  { "AUTORCC"_s, IC::CanCompileSources },
+  { "AUTORCC_OPTIONS"_s, IC::CanCompileSources },
+  { "AUTORCC_EXECUTABLE"_s, IC::CanCompileSources },
+
+  // Linking properties
+  { "ENABLE_EXPORTS"_s, IC::TargetWithSymbolExports },
+  { "LINK_LIBRARIES_ONLY_TARGETS"_s, IC::NormalNonImportedTarget },
+  { "LINK_SEARCH_START_STATIC"_s, IC::CanCompileSources },
+  { "LINK_SEARCH_END_STATIC"_s, IC::CanCompileSources },
+  // Initialize per-configuration name postfix property from the variable only
+  // for non-executable targets.  This preserves compatibility with previous
+  // CMake versions in which executables did not support this variable.
+  // Projects may still specify the property directly.
+  { "_POSTFIX"_s, IC::NonExecutableWithArtifact, R::PerConfigPrefix },
+  // -- Dependent library lookup
+  { "MACOSX_RPATH"_s, IC::CanCompileSources },
+  // ---- Build
+  { "BUILD_RPATH"_s, IC::CanCompileSources },
+  { "BUILD_RPATH_USE_ORIGIN"_s, IC::CanCompileSources },
+  { "SKIP_BUILD_RPATH"_s, "OFF"_s, IC::CanCompileSources },
+  { "BUILD_WITH_INSTALL_RPATH"_s, "OFF"_s, IC::CanCompileSources },
+  { "BUILD_WITH_INSTALL_NAME_DIR"_s, IC::CanCompileSources },
+  // ---- Install
+  { "INSTALL_NAME_DIR"_s, IC::CanCompileSources },
+  { "INSTALL_REMOVE_ENVIRONMENT_RPATH"_s, IC::CanCompileSources },
+  { "INSTALL_RPATH"_s, ""_s, IC::CanCompileSources },
+  { "INSTALL_RPATH_USE_LINK_PATH"_s, "OFF"_s, IC::CanCompileSources },
+  // -- Platforms
+  // ---- AIX
+  { "AIX_EXPORT_ALL_SYMBOLS"_s, IC::TargetWithSymbolExports },
+  // ---- Android
+  { "ANDROID_GUI"_s, IC::ExecutableTarget },
+  { "ANDROID_JAR_DIRECTORIES"_s, IC::CanCompileSources },
+  { "ANDROID_JAR_DEPENDENCIES"_s, IC::CanCompileSources },
+  { "ANDROID_NATIVE_LIB_DIRECTORIES"_s, IC::CanCompileSources },
+  { "ANDROID_NATIVE_LIB_DEPENDENCIES"_s, IC::CanCompileSources },
+  { "ANDROID_PROGUARD"_s, IC::CanCompileSources },
+  { "ANDROID_PROGUARD_CONFIG_PATH"_s, IC::CanCompileSources },
+  { "ANDROID_SECURE_PROPS_PATH"_s, IC::CanCompileSources },
+  // ---- iOS
+  { "IOS_INSTALL_COMBINED"_s, IC::CanCompileSources },
+  // ---- macOS
+  { "FRAMEWORK_MULTI_CONFIG_POSTFIX_"_s, IC::LinkableLibraryTarget, R::PerConfig },
+  // ---- Windows
+  { "DLL_NAME_WITH_SOVERSION"_s, IC::SharedLibraryTarget },
+  { "GNUtoMS"_s, IC::CanCompileSources },
+  { "WIN32_EXECUTABLE"_s, IC::CanCompileSources },
+  { "WINDOWS_EXPORT_ALL_SYMBOLS"_s, IC::TargetWithSymbolExports },
+  // -- Languages
+  // ---- C
+  { "C_LINKER_LAUNCHER"_s, IC::CanCompileSources },
+  // ---- C++
+  { "CXX_LINKER_LAUNCHER"_s, IC::CanCompileSources },
+  // ---- CUDA
+  { "CUDA_RESOLVE_DEVICE_SYMBOLS"_s, IC::CanCompileSources },
+  { "CUDA_RUNTIME_LIBRARY"_s, IC::CanCompileSources },
+  // ---- HIP
+  { "HIP_RUNTIME_LIBRARY"_s, IC::CanCompileSources },
+  // ---- Objective C
+  { "OBJC_LINKER_LAUNCHER"_s, IC::CanCompileSources },
+  // ---- Objective C++
+  { "OBJCXX_LINKER_LAUNCHER"_s, IC::CanCompileSources },
+
+  // Static analysis
+  // -- C
+  { "C_CLANG_TIDY"_s, IC::CanCompileSources },
+  { "C_CLANG_TIDY_EXPORT_FIXES_DIR"_s, IC::CanCompileSources },
+  { "C_CPPLINT"_s, IC::CanCompileSources },
+  { "C_CPPCHECK"_s, IC::CanCompileSources },
+  { "C_INCLUDE_WHAT_YOU_USE"_s, IC::CanCompileSources },
+  // -- C++
+  { "CXX_CLANG_TIDY"_s, IC::CanCompileSources },
+  { "CXX_CLANG_TIDY_EXPORT_FIXES_DIR"_s, IC::CanCompileSources },
+  { "CXX_CPPLINT"_s, IC::CanCompileSources },
+  { "CXX_CPPCHECK"_s, IC::CanCompileSources },
+  { "CXX_INCLUDE_WHAT_YOU_USE"_s, IC::CanCompileSources },
+  // -- Objective C
+  { "OBJC_CLANG_TIDY"_s, IC::CanCompileSources },
+  { "OBJC_CLANG_TIDY_EXPORT_FIXES_DIR"_s, IC::CanCompileSources },
+  // -- Objective C++
+  { "OBJCXX_CLANG_TIDY"_s, IC::CanCompileSources },
+  { "OBJCXX_CLANG_TIDY_EXPORT_FIXES_DIR"_s, IC::CanCompileSources },
+  // -- Linking
+  { "LINK_WHAT_YOU_USE"_s, IC::CanCompileSources },
+
+  // Build graph properties
+  { "LINK_DEPENDS_NO_SHARED"_s, IC::CanCompileSources },
+  { "UNITY_BUILD"_s, IC::CanCompileSources },
+  { "UNITY_BUILD_UNIQUE_ID"_s, IC::CanCompileSources },
+  { "UNITY_BUILD_BATCH_SIZE"_s, "8"_s, IC::CanCompileSources },
+  { "UNITY_BUILD_MODE"_s, "BATCH"_s, IC::CanCompileSources },
+  { "OPTIMIZE_DEPENDENCIES"_s, IC::CanCompileSources },
+  { "VERIFY_INTERFACE_HEADER_SETS"_s },
+  // -- Android
+  { "ANDROID_ANT_ADDITIONAL_OPTIONS"_s, IC::CanCompileSources },
+  { "ANDROID_PROCESS_MAX"_s, IC::CanCompileSources },
+  { "ANDROID_SKIP_ANT_STEP"_s, IC::CanCompileSources },
+  // -- Autogen
+  { "AUTOGEN_ORIGIN_DEPENDS"_s, IC::CanCompileSources },
+  { "AUTOGEN_PARALLEL"_s, IC::CanCompileSources },
+  { "AUTOGEN_USE_SYSTEM_INCLUDE"_s, IC::CanCompileSources },
+  // -- moc
+  { "AUTOMOC_DEPEND_FILTERS"_s, IC::CanCompileSources },
+  // -- C++
+  { "CXX_SCAN_FOR_MODULES"_s, IC::CanCompileSources },
+  // -- Ninja
+  { "JOB_POOL_COMPILE"_s, IC::CanCompileSources },
+  { "JOB_POOL_LINK"_s, IC::CanCompileSources },
+  { "JOB_POOL_PRECOMPILE_HEADER"_s, IC::CanCompileSources },
+  // -- Visual Studio
+  { "VS_NO_COMPILE_BATCHING"_s, IC::CanCompileSources },
+  { "VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION"_s, IC::CanCompileSources},
+
+  // Output location properties
+  { "ARCHIVE_OUTPUT_DIRECTORY"_s, IC::CanCompileSources },
+  { "ARCHIVE_OUTPUT_DIRECTORY_"_s, IC::TargetWithArtifact, R::PerConfig },
+  { "COMPILE_PDB_OUTPUT_DIRECTORY"_s, IC::CanCompileSources },
+  { "COMPILE_PDB_OUTPUT_DIRECTORY_"_s, IC::TargetWithArtifact, R::PerConfig },
+  { "LIBRARY_OUTPUT_DIRECTORY"_s, IC::CanCompileSources },
+  { "LIBRARY_OUTPUT_DIRECTORY_"_s, IC::TargetWithArtifact, R::PerConfig },
+  { "PDB_OUTPUT_DIRECTORY"_s, IC::CanCompileSources },
+  { "PDB_OUTPUT_DIRECTORY_"_s, IC::TargetWithArtifact, R::PerConfig },
+  { "RUNTIME_OUTPUT_DIRECTORY"_s, IC::CanCompileSources },
+  { "RUNTIME_OUTPUT_DIRECTORY_"_s, IC::TargetWithArtifact, R::PerConfig },
+
+  // macOS bundle properties
+  { "FRAMEWORK"_s, IC::CanCompileSources },
+  { "FRAMEWORK_MULTI_CONFIG_POSTFIX"_s, IC::CanCompileSources },
+  { "MACOSX_BUNDLE"_s, IC::CanCompileSources },
+
+  // Usage requirement properties
+  { "LINK_INTERFACE_LIBRARIES"_s, IC::CanCompileSources },
+  { "MAP_IMPORTED_CONFIG_"_s, IC::NormalTarget, R::PerConfig },
+
+  // Metadata
+  { "CROSSCOMPILING_EMULATOR"_s, IC::ExecutableTarget },
+  { "EXPORT_COMPILE_COMMANDS"_s, IC::CanCompileSources },
+  { "FOLDER"_s },
+
+  // Xcode properties
+  { "XCODE_GENERATE_SCHEME"_s, IC::NeedsXcode },
+
+#ifdef __APPLE__
+  { "XCODE_SCHEME_ADDRESS_SANITIZER"_s, IC::NeedsXcodeAndCanCompileSources },
+  { "XCODE_SCHEME_ADDRESS_SANITIZER_USE_AFTER_RETURN"_s, IC::NeedsXcodeAndCanCompileSources },
+  { "XCODE_SCHEME_DEBUG_DOCUMENT_VERSIONING"_s, IC::NeedsXcodeAndCanCompileSources },
+  { "XCODE_SCHEME_ENABLE_GPU_FRAME_CAPTURE_MODE"_s, IC::NeedsXcodeAndCanCompileSources },
+  { "XCODE_SCHEME_THREAD_SANITIZER"_s, IC::NeedsXcodeAndCanCompileSources },
+  { "XCODE_SCHEME_THREAD_SANITIZER_STOP"_s, IC::NeedsXcodeAndCanCompileSources },
+  { "XCODE_SCHEME_UNDEFINED_BEHAVIOUR_SANITIZER"_s, IC::NeedsXcodeAndCanCompileSources },
+  { "XCODE_SCHEME_UNDEFINED_BEHAVIOUR_SANITIZER_STOP"_s, IC::NeedsXcodeAndCanCompileSources },
+  { "XCODE_SCHEME_LAUNCH_CONFIGURATION"_s, IC::NeedsXcodeAndCanCompileSources },
+  { "XCODE_SCHEME_ENABLE_GPU_API_VALIDATION"_s, IC::NeedsXcodeAndCanCompileSources },
+  { "XCODE_SCHEME_ENABLE_GPU_SHADER_VALIDATION"_s, IC::NeedsXcodeAndCanCompileSources },
+  { "XCODE_SCHEME_WORKING_DIRECTORY"_s, IC::NeedsXcodeAndCanCompileSources },
+  { "XCODE_SCHEME_DISABLE_MAIN_THREAD_CHECKER"_s, IC::NeedsXcodeAndCanCompileSources },
+  { "XCODE_SCHEME_MAIN_THREAD_CHECKER_STOP"_s, IC::NeedsXcodeAndCanCompileSources },
+  { "XCODE_SCHEME_MALLOC_SCRIBBLE"_s, IC::NeedsXcodeAndCanCompileSources },
+  { "XCODE_SCHEME_MALLOC_GUARD_EDGES"_s, IC::NeedsXcodeAndCanCompileSources },
+  { "XCODE_SCHEME_GUARD_MALLOC"_s, IC::NeedsXcodeAndCanCompileSources },
+  { "XCODE_SCHEME_LAUNCH_MODE"_s, IC::NeedsXcodeAndCanCompileSources },
+  { "XCODE_SCHEME_ZOMBIE_OBJECTS"_s, IC::NeedsXcodeAndCanCompileSources },
+  { "XCODE_SCHEME_MALLOC_STACK"_s, IC::NeedsXcodeAndCanCompileSources },
+  { "XCODE_SCHEME_DYNAMIC_LINKER_API_USAGE"_s, IC::NeedsXcodeAndCanCompileSources },
+  { "XCODE_SCHEME_DYNAMIC_LIBRARY_LOADS"_s, IC::NeedsXcodeAndCanCompileSources },
+  { "XCODE_SCHEME_ENVIRONMENT"_s, IC::NeedsXcodeAndCanCompileSources },
+  { "XCODE_LINK_BUILD_PHASE_MODE"_s, "NONE"_s, IC::NeedsXcodeAndCanCompileSources },
+#endif
+  /* clang-format on */
+};
+
+#undef COMMON_LANGUAGE_PROPERTIES
+#undef IC
+#undef R
 }
 
 class cmTargetInternals
@@ -289,11 +640,11 @@
   bool HaveInstallRule;
   bool IsDLLPlatform;
   bool IsAIX;
+  bool IsApple;
   bool IsAndroid;
-  bool IsImportedTarget;
-  bool ImportedGloballyVisible;
   bool BuildInterfaceIncludesAppended;
   bool PerConfig;
+  cmTarget::Visibility TargetVisibility;
   std::set<BT<std::pair<std::string, bool>>> Utilities;
   std::vector<cmCustomCommand> PreBuildCommands;
   std::vector<cmCustomCommand> PreLinkCommands;
@@ -328,6 +679,8 @@
 
   cmTargetInternals();
 
+  bool IsImported() const;
+
   bool CheckImportedLibName(std::string const& prop,
                             std::string const& value) const;
 
@@ -562,15 +915,6 @@
   return { did_read, value };
 }
 
-namespace {
-#define SETUP_COMMON_LANGUAGE_PROPERTIES(lang)                                \
-  initProp(#lang "_COMPILER_LAUNCHER");                                       \
-  initProp(#lang "_STANDARD");                                                \
-  initProp(#lang "_STANDARD_REQUIRED");                                       \
-  initProp(#lang "_EXTENSIONS");                                              \
-  initProp(#lang "_VISIBILITY_PRESET")
-}
-
 cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
                    Visibility vis, cmMakefile* mf, PerConfig perConfig)
   : impl(cm::make_unique<cmTargetInternals>())
@@ -583,10 +927,9 @@
   this->impl->HaveInstallRule = false;
   this->impl->IsDLLPlatform = false;
   this->impl->IsAIX = false;
+  this->impl->IsApple = false;
   this->impl->IsAndroid = false;
-  this->impl->IsImportedTarget =
-    (vis == VisibilityImported || vis == VisibilityImportedGlobally);
-  this->impl->ImportedGloballyVisible = vis == VisibilityImportedGlobally;
+  this->impl->TargetVisibility = vis;
   this->impl->BuildInterfaceIncludesAppended = false;
   this->impl->PerConfig = (perConfig == PerConfig::Yes);
 
@@ -602,306 +945,17 @@
     this->impl->IsAIX = (systemName == "AIX" || systemName == "OS400");
   }
 
+  // Check whether we are targeting Apple.
+  this->impl->IsApple = this->impl->Makefile->IsOn("APPLE");
+
   // Check whether we are targeting an Android platform.
   this->impl->IsAndroid = (this->impl->Makefile->GetSafeDefinition(
                              "CMAKE_SYSTEM_NAME") == "Android");
 
-  std::string defKey;
-  defKey.reserve(128);
-  defKey += "CMAKE_";
-  auto initProp = [this, mf, &defKey](const std::string& property) {
-    // Replace everything after "CMAKE_"
-    defKey.replace(defKey.begin() + 6, defKey.end(), property);
-    if (cmValue value = mf->GetDefinition(defKey)) {
-      this->SetProperty(property, value);
-    }
-  };
-  auto initPropValue = [this, mf, &defKey](const std::string& property,
-                                           const char* default_value) {
-    // Replace everything after "CMAKE_"
-    defKey.replace(defKey.begin() + 6, defKey.end(), property);
-    if (cmValue value = mf->GetDefinition(defKey)) {
-      this->SetProperty(property, value);
-    } else if (default_value) {
-      this->SetProperty(property, default_value);
-    }
-  };
-
-  // Setup default property values.
-  if (this->CanCompileSources()) {
-
-    // Compilation properties
-    initProp("INTERPROCEDURAL_OPTIMIZATION");
-    // initProp("INTERPROCEDURAL_OPTIMIZATION_<CONFIG>"); (per-config block)
-    initProp("NO_SYSTEM_FROM_IMPORTED");
-    initProp("VISIBILITY_INLINES_HIDDEN");
-    initProp("COMPILE_WARNING_AS_ERROR");
-    // -- Features
-    // ---- PCH
-    initProp("DISABLE_PRECOMPILE_HEADERS");
-    initPropValue("PCH_WARN_INVALID", "ON");
-    initPropValue("PCH_INSTANTIATE_TEMPLATES", "ON");
-    // -- Platforms
-    // ---- Android
-    initProp("ANDROID_API");
-    initProp("ANDROID_API_MIN");
-    initProp("ANDROID_ARCH");
-    initProp("ANDROID_ASSETS_DIRECTORIES");
-    initProp("ANDROID_JAVA_SOURCE_DIR");
-    initProp("ANDROID_STL_TYPE");
-    // ---- macOS
-    initProp("OSX_ARCHITECTURES");
-    // ---- Windows
-    initProp("MSVC_DEBUG_INFORMATION_FORMAT");
-    initProp("MSVC_RUNTIME_LIBRARY");
-    initProp("VS_JUST_MY_CODE_DEBUGGING");
-    // ---- OpenWatcom
-    initProp("WATCOM_RUNTIME_LIBRARY");
-    // -- Language
-    // ---- C
-    SETUP_COMMON_LANGUAGE_PROPERTIES(C);
-    // ---- C++
-    SETUP_COMMON_LANGUAGE_PROPERTIES(CXX);
-    // ---- CUDA
-    SETUP_COMMON_LANGUAGE_PROPERTIES(CUDA);
-    initProp("CUDA_SEPARABLE_COMPILATION");
-    initProp("CUDA_ARCHITECTURES");
-    // ---- Fortran
-    initProp("Fortran_FORMAT");
-    initProp("Fortran_MODULE_DIRECTORY");
-    initProp("Fortran_COMPILER_LAUNCHER");
-    initProp("Fortran_PREPROCESS");
-    initProp("Fortran_VISIBILITY_PRESET");
-    // ---- HIP
-    SETUP_COMMON_LANGUAGE_PROPERTIES(HIP);
-    initProp("HIP_ARCHITECTURES");
-    // ---- ISPC
-    initProp("ISPC_COMPILER_LAUNCHER");
-    initProp("ISPC_HEADER_DIRECTORY");
-    initPropValue("ISPC_HEADER_SUFFIX", "_ispc.h");
-    initProp("ISPC_INSTRUCTION_SETS");
-    // ---- Objective C
-    SETUP_COMMON_LANGUAGE_PROPERTIES(OBJC);
-    // ---- Objective C++
-    SETUP_COMMON_LANGUAGE_PROPERTIES(OBJCXX);
-    // ---- Swift
-    initProp("Swift_LANGUAGE_VERSION");
-    initProp("Swift_MODULE_DIRECTORY");
-    // ---- moc
-    initProp("AUTOMOC");
-    initProp("AUTOMOC_COMPILER_PREDEFINES");
-    initProp("AUTOMOC_MACRO_NAMES");
-    initProp("AUTOMOC_MOC_OPTIONS");
-    initProp("AUTOMOC_PATH_PREFIX");
-    // ---- uic
-    initProp("AUTOUIC");
-    initProp("AUTOUIC_OPTIONS");
-    initProp("AUTOUIC_SEARCH_PATHS");
-    // ---- rcc
-    initProp("AUTORCC");
-    initProp("AUTORCC_OPTIONS");
-
-    // Linking properties
-    initProp("LINK_SEARCH_START_STATIC");
-    initProp("LINK_SEARCH_END_STATIC");
-    // -- Dependent library lookup
-    initProp("MACOSX_RPATH");
-    // ---- Build
-    initProp("BUILD_RPATH");
-    initProp("BUILD_RPATH_USE_ORIGIN");
-    initPropValue("SKIP_BUILD_RPATH", "OFF");
-    initPropValue("BUILD_WITH_INSTALL_RPATH", "OFF");
-    initProp("BUILD_WITH_INSTALL_NAME_DIR");
-    // ---- Install
-    initProp("INSTALL_NAME_DIR");
-    initProp("INSTALL_REMOVE_ENVIRONMENT_RPATH");
-    initPropValue("INSTALL_RPATH", "");
-    initPropValue("INSTALL_RPATH_USE_LINK_PATH", "OFF");
-    // -- Platforms
-    // ---- Android
-    initProp("ANDROID_JAR_DIRECTORIES");
-    initProp("ANDROID_JAR_DEPENDENCIES");
-    initProp("ANDROID_NATIVE_LIB_DIRECTORIES");
-    initProp("ANDROID_NATIVE_LIB_DEPENDENCIES");
-    initProp("ANDROID_PROGUARD");
-    initProp("ANDROID_PROGUARD_CONFIG_PATH");
-    initProp("ANDROID_SECURE_PROPS_PATH");
-    // ---- iOS
-    initProp("IOS_INSTALL_COMBINED");
-    // ---- Windows
-    initProp("GNUtoMS");
-    initProp("WIN32_EXECUTABLE");
-    // -- Languages
-    // ---- C
-    initProp("C_LINKER_LAUNCHER");
-    // ---- C++
-    initProp("CXX_LINKER_LAUNCHER");
-    // ---- CUDA
-    initProp("CUDA_RESOLVE_DEVICE_SYMBOLS");
-    initProp("CUDA_RUNTIME_LIBRARY");
-    // ---- HIP
-    initProp("HIP_RUNTIME_LIBRARY");
-    // ---- Objective C
-    initProp("OBJC_LINKER_LAUNCHER");
-    // ---- Objective C++
-    initProp("OBJCXX_LINKER_LAUNCHER");
-
-    // Static analysis
-    // -- C
-    initProp("C_CLANG_TIDY");
-    initProp("C_CLANG_TIDY_EXPORT_FIXES_DIR");
-    initProp("C_CPPLINT");
-    initProp("C_CPPCHECK");
-    initProp("C_INCLUDE_WHAT_YOU_USE");
-    // -- C++
-    initProp("CXX_CLANG_TIDY");
-    initProp("CXX_CLANG_TIDY_EXPORT_FIXES_DIR");
-    initProp("CXX_CPPLINT");
-    initProp("CXX_CPPCHECK");
-    initProp("CXX_INCLUDE_WHAT_YOU_USE");
-    // -- Objective C
-    initProp("OBJC_CLANG_TIDY");
-    initProp("OBJC_CLANG_TIDY_EXPORT_FIXES_DIR");
-    // -- Objective C++
-    initProp("OBJCXX_CLANG_TIDY");
-    initProp("OBJCXX_CLANG_TIDY_EXPORT_FIXES_DIR");
-    // -- Linking
-    initProp("LINK_WHAT_YOU_USE");
-
-    // Build graph properties
-    initProp("LINK_DEPENDS_NO_SHARED");
-    initProp("UNITY_BUILD");
-    initProp("UNITY_BUILD_UNIQUE_ID");
-    initPropValue("UNITY_BUILD_BATCH_SIZE", "8");
-    initPropValue("UNITY_BUILD_MODE", "BATCH");
-    initProp("OPTIMIZE_DEPENDENCIES");
-    // -- Android
-    initProp("ANDROID_ANT_ADDITIONAL_OPTIONS");
-    initProp("ANDROID_PROCESS_MAX");
-    initProp("ANDROID_SKIP_ANT_STEP");
-    // -- Autogen
-    initProp("AUTOGEN_ORIGIN_DEPENDS");
-    initProp("AUTOGEN_PARALLEL");
-    // -- moc
-    initProp("AUTOMOC_DEPEND_FILTERS");
-    // -- C++
-    initProp("CXX_SCAN_FOR_MODULES");
-    // -- Ninja
-    initProp("JOB_POOL_COMPILE");
-    initProp("JOB_POOL_LINK");
-    initProp("JOB_POOL_PRECOMPILE_HEADER");
-    // -- Visual Studio
-    initProp("VS_NO_COMPILE_BATCHING");
-
-    // Output location properties
-    initProp("ARCHIVE_OUTPUT_DIRECTORY");
-    initProp("LIBRARY_OUTPUT_DIRECTORY");
-    initProp("RUNTIME_OUTPUT_DIRECTORY");
-    initProp("PDB_OUTPUT_DIRECTORY");
-    initProp("COMPILE_PDB_OUTPUT_DIRECTORY");
-
-    // -- macOS bundle properties
-    initProp("FRAMEWORK");
-    initProp("FRAMEWORK_MULTI_CONFIG_POSTFIX");
-    initProp("MACOSX_BUNDLE");
-
-    // Usage requirement properties
-    initProp("LINK_INTERFACE_LIBRARIES");
-
-    // Metadata
-    initProp("EXPORT_COMPILE_COMMANDS");
-
-#ifdef __APPLE__
-    if (this->GetGlobalGenerator()->IsXcode()) {
-      initProp("XCODE_SCHEME_ADDRESS_SANITIZER");
-      initProp("XCODE_SCHEME_ADDRESS_SANITIZER_USE_AFTER_RETURN");
-      initProp("XCODE_SCHEME_DEBUG_DOCUMENT_VERSIONING");
-      initProp("XCODE_SCHEME_ENABLE_GPU_FRAME_CAPTURE_MODE");
-      initProp("XCODE_SCHEME_THREAD_SANITIZER");
-      initProp("XCODE_SCHEME_THREAD_SANITIZER_STOP");
-      initProp("XCODE_SCHEME_UNDEFINED_BEHAVIOUR_SANITIZER");
-      initProp("XCODE_SCHEME_UNDEFINED_BEHAVIOUR_SANITIZER_STOP");
-      initProp("XCODE_SCHEME_LAUNCH_CONFIGURATION");
-      initProp("XCODE_SCHEME_ENABLE_GPU_API_VALIDATION");
-      initProp("XCODE_SCHEME_ENABLE_GPU_SHADER_VALIDATION");
-      initProp("XCODE_SCHEME_WORKING_DIRECTORY");
-      initProp("XCODE_SCHEME_DISABLE_MAIN_THREAD_CHECKER");
-      initProp("XCODE_SCHEME_MAIN_THREAD_CHECKER_STOP");
-      initProp("XCODE_SCHEME_MALLOC_SCRIBBLE");
-      initProp("XCODE_SCHEME_MALLOC_GUARD_EDGES");
-      initProp("XCODE_SCHEME_GUARD_MALLOC");
-      initProp("XCODE_SCHEME_LAUNCH_MODE");
-      initProp("XCODE_SCHEME_ZOMBIE_OBJECTS");
-      initProp("XCODE_SCHEME_MALLOC_STACK");
-      initProp("XCODE_SCHEME_DYNAMIC_LINKER_API_USAGE");
-      initProp("XCODE_SCHEME_DYNAMIC_LIBRARY_LOADS");
-      initProp("XCODE_SCHEME_ENVIRONMENT");
-      initPropValue("XCODE_LINK_BUILD_PHASE_MODE", "NONE");
-    }
-#endif
-  }
-
-  initProp("FOLDER");
-  initProp("VERIFY_INTERFACE_HEADER_SETS");
-
-  if (this->GetGlobalGenerator()->IsXcode()) {
-    initProp("XCODE_GENERATE_SCHEME");
-  }
-
-  // Setup per-configuration property default values.
-  if (this->GetType() != cmStateEnums::UTILITY &&
-      this->GetType() != cmStateEnums::GLOBAL_TARGET) {
-    static const auto configProps = {
-      /* clang-format needs this comment to break after the opening brace */
-      "ARCHIVE_OUTPUT_DIRECTORY_"_s,     "LIBRARY_OUTPUT_DIRECTORY_"_s,
-      "RUNTIME_OUTPUT_DIRECTORY_"_s,     "PDB_OUTPUT_DIRECTORY_"_s,
-      "COMPILE_PDB_OUTPUT_DIRECTORY_"_s, "MAP_IMPORTED_CONFIG_"_s,
-      "INTERPROCEDURAL_OPTIMIZATION_"_s
-    };
-    // Collect the set of configuration types.
-    std::vector<std::string> configNames =
-      mf->GetGeneratorConfigs(cmMakefile::ExcludeEmptyConfig);
-    for (std::string const& configName : configNames) {
-      std::string configUpper = cmSystemTools::UpperCase(configName);
-      for (auto const& prop : configProps) {
-        // Interface libraries have no output locations, so honor only
-        // the configuration map.
-        if (this->impl->TargetType == cmStateEnums::INTERFACE_LIBRARY &&
-            prop != "MAP_IMPORTED_CONFIG_") {
-          continue;
-        }
-        std::string property = cmStrCat(prop, configUpper);
-        initProp(property);
-      }
-
-      // Initialize per-configuration name postfix property from the
-      // variable only for non-executable targets.  This preserves
-      // compatibility with previous CMake versions in which executables
-      // did not support this variable.  Projects may still specify the
-      // property directly.
-      if (this->impl->TargetType != cmStateEnums::EXECUTABLE &&
-          this->impl->TargetType != cmStateEnums::INTERFACE_LIBRARY) {
-        std::string property =
-          cmStrCat(cmSystemTools::UpperCase(configName), "_POSTFIX");
-        initProp(property);
-      }
-
-      if (this->impl->TargetType == cmStateEnums::SHARED_LIBRARY ||
-          this->impl->TargetType == cmStateEnums::STATIC_LIBRARY) {
-        std::string property = cmStrCat("FRAMEWORK_MULTI_CONFIG_POSTFIX_",
-                                        cmSystemTools::UpperCase(configName));
-        initProp(property);
-      }
-    }
-    if (!this->IsImported()) {
-      initProp("LINK_LIBRARIES_ONLY_TARGETS");
-    }
-  }
-
   // Save the backtrace of target construction.
   this->impl->Backtrace = this->impl->Makefile->GetBacktrace();
 
-  if (!this->IsImported()) {
+  if (this->IsNormal()) {
     // Initialize the INCLUDE_DIRECTORIES property based on the current value
     // of the same directory property:
     this->impl->IncludeDirectories.CopyFromDirectory(
@@ -921,23 +975,6 @@
       this->impl->Makefile->GetLinkDirectoriesEntries());
   }
 
-  if (this->impl->TargetType == cmStateEnums::EXECUTABLE) {
-    initProp("ANDROID_GUI");
-    initProp("CROSSCOMPILING_EMULATOR");
-    initProp("ENABLE_EXPORTS");
-  }
-  if (this->impl->TargetType == cmStateEnums::SHARED_LIBRARY ||
-      this->impl->TargetType == cmStateEnums::MODULE_LIBRARY) {
-    this->SetProperty("POSITION_INDEPENDENT_CODE", "True");
-  } else if (this->CanCompileSources()) {
-    initProp("POSITION_INDEPENDENT_CODE");
-  }
-  if (this->impl->TargetType == cmStateEnums::SHARED_LIBRARY ||
-      this->impl->TargetType == cmStateEnums::EXECUTABLE) {
-    initProp("AIX_EXPORT_ALL_SYMBOLS");
-    initProp("WINDOWS_EXPORT_ALL_SYMBOLS");
-  }
-
   // Record current policies for later use.
   this->impl->Makefile->RecordPolicies(this->impl->PolicyMap);
 
@@ -949,13 +986,133 @@
     this->impl->PolicyMap.Set(cmPolicies::CMP0022, cmPolicies::NEW);
   }
 
+  std::set<TargetProperty::InitCondition> metConditions;
+  metConditions.insert(TargetProperty::InitCondition::Always);
+  if (this->CanCompileSources()) {
+    metConditions.insert(TargetProperty::InitCondition::CanCompileSources);
+  }
+  if (this->GetGlobalGenerator()->IsXcode()) {
+    metConditions.insert(TargetProperty::InitCondition::NeedsXcode);
+    if (this->CanCompileSources()) {
+      metConditions.insert(
+        TargetProperty::InitCondition::NeedsXcodeAndCanCompileSources);
+    }
+  }
   if (!this->IsImported()) {
-    initProp("DOTNET_SDK");
+    metConditions.insert(TargetProperty::InitCondition::NonImportedTarget);
+  }
+  if (this->impl->TargetType != cmStateEnums::UTILITY &&
+      this->impl->TargetType != cmStateEnums::GLOBAL_TARGET) {
+    metConditions.insert(TargetProperty::InitCondition::NormalTarget);
+    if (this->IsNormal()) {
+      metConditions.insert(
+        TargetProperty::InitCondition::NormalNonImportedTarget);
+    }
+    if (this->impl->TargetType != cmStateEnums::INTERFACE_LIBRARY) {
+      metConditions.insert(TargetProperty::InitCondition::TargetWithArtifact);
+      if (this->impl->TargetType != cmStateEnums::EXECUTABLE) {
+        metConditions.insert(
+          TargetProperty::InitCondition::NonExecutableWithArtifact);
+      }
+    }
+    if (this->impl->TargetType == cmStateEnums::SHARED_LIBRARY ||
+        this->impl->TargetType == cmStateEnums::STATIC_LIBRARY) {
+      metConditions.insert(
+        TargetProperty::InitCondition::LinkableLibraryTarget);
+    }
+    if (this->impl->TargetType == cmStateEnums::SHARED_LIBRARY) {
+      metConditions.insert(TargetProperty::InitCondition::SharedLibraryTarget);
+    }
+  }
+  if (this->impl->TargetType == cmStateEnums::EXECUTABLE) {
+    metConditions.insert(TargetProperty::InitCondition::ExecutableTarget);
+  }
+  if (this->impl->TargetType == cmStateEnums::SHARED_LIBRARY ||
+      this->impl->TargetType == cmStateEnums::EXECUTABLE) {
+    metConditions.insert(
+      TargetProperty::InitCondition::TargetWithSymbolExports);
+  }
+  if (this->impl->TargetType <= cmStateEnums::GLOBAL_TARGET) {
+    metConditions.insert(TargetProperty::InitCondition::TargetWithCommands);
   }
 
-  if (this->impl->TargetType <= cmStateEnums::GLOBAL_TARGET) {
-    initProp("DOTNET_TARGET_FRAMEWORK");
-    initProp("DOTNET_TARGET_FRAMEWORK_VERSION");
+  std::vector<std::string> configNames =
+    mf->GetGeneratorConfigs(cmMakefile::ExcludeEmptyConfig);
+  for (auto& config : configNames) {
+    config = cmSystemTools::UpperCase(config);
+  }
+
+  std::string defKey;
+  defKey.reserve(128);
+  defKey += "CMAKE_";
+  auto initProperty = [this, mf, &defKey](const std::string& property,
+                                          const char* default_value) {
+    // special init for ENABLE_EXPORTS
+    // For SHARED_LIBRARY, only CMAKE_SHARED_LIBRARY_ENABLE_EXPORTS variable
+    // is used
+    // For EXECUTABLE, CMAKE_EXECUTABLE_ENABLE_EXPORTS or else
+    // CMAKE_ENABLE_EXPORTS variables are used
+    if (property == "ENABLE_EXPORTS"_s) {
+      // Replace everything after "CMAKE_"
+      defKey.replace(
+        defKey.begin() + 6, defKey.end(),
+        cmStrCat(this->impl->TargetType == cmStateEnums::EXECUTABLE
+                   ? "EXECUTABLE"
+                   : "SHARED_LIBRARY",
+                 '_', property));
+      if (cmValue value = mf->GetDefinition(defKey)) {
+        this->SetProperty(property, value);
+        return;
+      }
+      if (this->impl->TargetType == cmStateEnums::SHARED_LIBRARY) {
+        if (default_value) {
+          this->SetProperty(property, default_value);
+        }
+        return;
+      }
+    }
+
+    // Replace everything after "CMAKE_"
+    defKey.replace(defKey.begin() + 6, defKey.end(), property);
+    if (cmValue value = mf->GetDefinition(defKey)) {
+      this->SetProperty(property, value);
+    } else if (default_value) {
+      this->SetProperty(property, default_value);
+    }
+  };
+
+  std::string dflt_storage;
+  for (auto const& tp : StaticTargetProperties) {
+    // Ignore properties that we have not met the condition for.
+    if (!metConditions.count(tp.InitConditional)) {
+      continue;
+    }
+
+    const char* dflt = nullptr;
+    if (tp.Default) {
+      dflt_storage = std::string(*tp.Default);
+      dflt = dflt_storage.c_str();
+    }
+
+    if (tp.Repeat == TargetProperty::Repetition::Once) {
+      initProperty(std::string(tp.Name), dflt);
+    } else {
+      std::string propertyName;
+      for (auto const& configName : configNames) {
+        if (tp.Repeat == TargetProperty::Repetition::PerConfig) {
+          propertyName = cmStrCat(tp.Name, configName);
+        } else if (tp.Repeat == TargetProperty::Repetition::PerConfigPrefix) {
+          propertyName = cmStrCat(configName, tp.Name);
+        }
+        initProperty(propertyName, dflt);
+      }
+    }
+  }
+
+  // Clean up some property defaults.
+  if (this->impl->TargetType == cmStateEnums::SHARED_LIBRARY ||
+      this->impl->TargetType == cmStateEnums::MODULE_LIBRARY) {
+    this->SetProperty("POSITION_INDEPENDENT_CODE", "True");
   }
 
   // check for "CMAKE_VS_GLOBALS" variable and set up target properties
@@ -964,7 +1121,7 @@
   if (globals) {
     const std::string genName = mf->GetGlobalGenerator()->GetName();
     if (cmHasLiteralPrefix(genName, "Visual Studio")) {
-      std::vector<std::string> props = cmExpandedList(*globals);
+      cmList props{ *globals };
       const std::string vsGlobal = "VS_GLOBAL_";
       for (const std::string& i : props) {
         // split NAME=VALUE
@@ -972,13 +1129,13 @@
         if (assignment != std::string::npos) {
           const std::string propName = vsGlobal + i.substr(0, assignment);
           const std::string propValue = i.substr(assignment + 1);
-          initPropValue(propName, propValue.c_str());
+          initProperty(propName, propValue.c_str());
         }
       }
     }
   }
 
-  if (this->IsImported() || mf->GetPropertyAsBool("SYSTEM")) {
+  if (!this->IsNormal() || mf->GetPropertyAsBool("SYSTEM")) {
     this->SetProperty("SYSTEM", "ON");
   }
 
@@ -1089,18 +1246,22 @@
           this->GetPropertyAsBool("ENABLE_EXPORTS"));
 }
 
+bool cmTarget::IsSharedLibraryWithExports() const
+{
+  return (this->GetType() == cmStateEnums::SHARED_LIBRARY &&
+          this->GetPropertyAsBool("ENABLE_EXPORTS"));
+}
+
 bool cmTarget::IsFrameworkOnApple() const
 {
   return ((this->GetType() == cmStateEnums::SHARED_LIBRARY ||
            this->GetType() == cmStateEnums::STATIC_LIBRARY) &&
-          this->impl->Makefile->IsOn("APPLE") &&
-          this->GetPropertyAsBool("FRAMEWORK"));
+          this->IsApple() && this->GetPropertyAsBool("FRAMEWORK"));
 }
 
 bool cmTarget::IsAppBundleOnApple() const
 {
-  return (this->GetType() == cmStateEnums::EXECUTABLE &&
-          this->impl->Makefile->IsOn("APPLE") &&
+  return (this->GetType() == cmStateEnums::EXECUTABLE && this->IsApple() &&
           this->GetPropertyAsBool("MACOSX_BUNDLE"));
 }
 
@@ -1273,7 +1434,7 @@
 
   bool operator()(BT<std::string> const& entry)
   {
-    std::vector<std::string> files = cmExpandedList(entry.Value);
+    cmList files{ entry.Value };
     std::vector<cmSourceFileLocation> locations;
     locations.reserve(files.size());
     std::transform(files.begin(), files.end(), std::back_inserter(locations),
@@ -1624,6 +1785,9 @@
 MAKE_PROP(COMPILE_OPTIONS);
 MAKE_PROP(PRECOMPILE_HEADERS);
 MAKE_PROP(PRECOMPILE_HEADERS_REUSE_FROM);
+MAKE_PROP(CUDA_CUBIN_COMPILATION);
+MAKE_PROP(CUDA_FATBIN_COMPILATION);
+MAKE_PROP(CUDA_OPTIX_COMPILATION);
 MAKE_PROP(CUDA_PTX_COMPILATION);
 MAKE_PROP(EXPORT_NAME);
 MAKE_PROP(IMPORTED);
@@ -1646,27 +1810,7 @@
 #undef MAKE_PROP
 }
 
-namespace {
-// to workaround bug on GCC/AIX
-// Define a template to force conversion to std::string
-template <typename ValueType>
-std::string ConvertToString(ValueType value);
-
-template <>
-std::string ConvertToString<const char*>(const char* value)
-{
-  return std::string(value);
-}
-template <>
-std::string ConvertToString<cmValue>(cmValue value)
-{
-  return std::string(*value);
-}
-
-}
-
-template <typename ValueType>
-void cmTarget::StoreProperty(const std::string& prop, ValueType value)
+void cmTarget::SetProperty(const std::string& prop, cmValue value)
 {
   if (prop == propMANUALLY_ADDED_DEPENDENCIES) {
     this->impl->Makefile->IssueMessage(
@@ -1750,8 +1894,8 @@
       return;
     }
     /* no need to change anything if value does not change */
-    if (!this->impl->ImportedGloballyVisible) {
-      this->impl->ImportedGloballyVisible = true;
+    if (!this->IsImportedGloballyVisible()) {
+      this->impl->TargetVisibility = Visibility::ImportedGlobally;
       this->GetGlobalGenerator()->IndexTarget(this);
     }
   } else if (cmHasLiteralPrefix(prop, "IMPORTED_LIBNAME") &&
@@ -1760,14 +1904,38 @@
                value ? value
                      : std::string{})) { // NOLINT(bugprone-branch-clone)
     /* error was reported by check method */
-  } else if (prop == propCUDA_PTX_COMPILATION &&
-             this->GetType() != cmStateEnums::OBJECT_LIBRARY) {
-    std::ostringstream e;
-    e << "CUDA_PTX_COMPILATION property can only be applied to OBJECT "
-         "targets (\""
-      << this->impl->Name << "\")\n";
-    this->impl->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
-    return;
+  } else if (prop == propCUDA_CUBIN_COMPILATION ||
+             prop == propCUDA_FATBIN_COMPILATION ||
+             prop == propCUDA_OPTIX_COMPILATION ||
+             prop == propCUDA_PTX_COMPILATION) {
+    auto const& compiler =
+      this->impl->Makefile->GetSafeDefinition("CMAKE_CUDA_COMPILER_ID");
+    auto const& compilerVersion =
+      this->impl->Makefile->GetSafeDefinition("CMAKE_CUDA_COMPILER_VERSION");
+    if (this->GetType() != cmStateEnums::OBJECT_LIBRARY) {
+      auto e =
+        cmStrCat(prop, " property can only be applied to OBJECT targets(",
+                 this->impl->Name, ")\n");
+      this->impl->Makefile->IssueMessage(MessageType::FATAL_ERROR, e);
+      return;
+    }
+    const bool flag_found =
+      (prop == propCUDA_PTX_COMPILATION &&
+       this->impl->Makefile->GetDefinition("_CMAKE_CUDA_PTX_FLAG")) ||
+      (prop == propCUDA_CUBIN_COMPILATION &&
+       this->impl->Makefile->GetDefinition("_CMAKE_CUDA_CUBIN_FLAG")) ||
+      (prop == propCUDA_FATBIN_COMPILATION &&
+       this->impl->Makefile->GetDefinition("_CMAKE_CUDA_FATBIN_FLAG")) ||
+      (prop == propCUDA_OPTIX_COMPILATION &&
+       this->impl->Makefile->GetDefinition("_CMAKE_CUDA_OPTIX_FLAG"));
+    if (flag_found) {
+      this->impl->Properties.SetProperty(prop, value);
+    } else {
+      auto e = cmStrCat(prop, " property is not supported by ", compiler,
+                        "  compiler version ", compilerVersion, ".");
+      this->impl->Makefile->IssueMessage(MessageType::FATAL_ERROR, e);
+      return;
+    }
   } else if (prop == propPRECOMPILE_HEADERS_REUSE_FROM) {
     if (this->GetProperty("PRECOMPILE_HEADERS")) {
       std::ostringstream e;
@@ -1788,7 +1956,7 @@
 
     std::string reusedFrom = reusedTarget->GetSafeProperty(prop);
     if (reusedFrom.empty()) {
-      reusedFrom = ConvertToString(value);
+      reusedFrom = *value;
     }
 
     this->impl->Properties.SetProperty(prop, reusedFrom);
@@ -1904,15 +2072,6 @@
   }
 }
 
-void cmTarget::SetProperty(const std::string& prop, const char* value)
-{
-  this->StoreProperty(prop, value);
-}
-void cmTarget::SetProperty(const std::string& prop, cmValue value)
-{
-  this->StoreProperty(prop, value);
-}
-
 template <typename ValueType>
 void cmTargetInternals::AddDirectoryToFileSet(cmTarget* self,
                                               std::string const& fileSetName,
@@ -2441,15 +2600,70 @@
 {
   return this->impl->IsAIX;
 }
+bool cmTarget::IsApple() const
+{
+  return this->impl->IsApple;
+}
+
+bool cmTarget::IsNormal() const
+{
+  switch (this->impl->TargetVisibility) {
+    case Visibility::Normal:
+      return true;
+    case Visibility::Generated:
+    case Visibility::Imported:
+    case Visibility::ImportedGlobally:
+      return false;
+  }
+  assert(false && "unknown visibility (IsNormal)");
+  return false;
+}
+
+bool cmTarget::IsSynthetic() const
+{
+  switch (this->impl->TargetVisibility) {
+    case Visibility::Generated:
+      return true;
+    case Visibility::Normal:
+    case Visibility::Imported:
+    case Visibility::ImportedGlobally:
+      return false;
+  }
+  assert(false && "unknown visibility (IsSynthetic)");
+  return false;
+}
+
+bool cmTargetInternals::IsImported() const
+{
+  switch (this->TargetVisibility) {
+    case cmTarget::Visibility::Imported:
+    case cmTarget::Visibility::ImportedGlobally:
+      return true;
+    case cmTarget::Visibility::Normal:
+    case cmTarget::Visibility::Generated:
+      return false;
+  }
+  assert(false && "unknown visibility (IsImported)");
+  return false;
+}
 
 bool cmTarget::IsImported() const
 {
-  return this->impl->IsImportedTarget;
+  return this->impl->IsImported();
 }
 
 bool cmTarget::IsImportedGloballyVisible() const
 {
-  return this->impl->ImportedGloballyVisible;
+  switch (this->impl->TargetVisibility) {
+    case Visibility::ImportedGlobally:
+      return true;
+    case Visibility::Normal:
+    case Visibility::Generated:
+    case Visibility::Imported:
+      return false;
+  }
+  assert(false && "unknown visibility (IsImportedGloballyVisible)");
+  return false;
 }
 
 bool cmTarget::IsPerConfig() const
@@ -2489,7 +2703,8 @@
         case cmStateEnums::RuntimeBinaryArtifact:
           return "CMAKE_SHARED_LIBRARY_SUFFIX";
         case cmStateEnums::ImportLibraryArtifact:
-          return "CMAKE_IMPORT_LIBRARY_SUFFIX";
+          return this->IsApple() ? "CMAKE_APPLE_IMPORT_FILE_SUFFIX"
+                                 : "CMAKE_IMPORT_LIBRARY_SUFFIX";
       }
       break;
     case cmStateEnums::MODULE_LIBRARY:
@@ -2530,7 +2745,8 @@
         case cmStateEnums::RuntimeBinaryArtifact:
           return "CMAKE_SHARED_LIBRARY_PREFIX";
         case cmStateEnums::ImportLibraryArtifact:
-          return "CMAKE_IMPORT_LIBRARY_PREFIX";
+          return this->IsApple() ? "CMAKE_APPLE_IMPORT_FILE_PREFIX"
+                                 : "CMAKE_IMPORT_LIBRARY_PREFIX";
       }
       break;
     case cmStateEnums::MODULE_LIBRARY:
@@ -2729,7 +2945,7 @@
 
   auto appendEntries = [=](const std::vector<BT<std::string>>& entries) {
     for (auto const& entry : entries) {
-      auto expanded = cmExpandedList(entry.Value);
+      cmList expanded{ entry.Value };
       std::copy(expanded.begin(), expanded.end(), inserter);
     }
   };
@@ -2745,7 +2961,7 @@
                                              std::string const& value) const
 {
   if (this->TargetType != cmStateEnums::INTERFACE_LIBRARY ||
-      !this->IsImportedTarget) {
+      !this->IsImported()) {
     this->Makefile->IssueMessage(
       MessageType::FATAL_ERROR,
       prop +
@@ -2791,11 +3007,11 @@
   // Track the configuration-specific property suffix.
   suffix = cmStrCat('_', config_upper);
 
-  std::vector<std::string> mappedConfigs;
+  cmList mappedConfigs;
   {
     std::string mapProp = cmStrCat("MAP_IMPORTED_CONFIG_", config_upper);
     if (cmValue mapValue = this->GetProperty(mapProp)) {
-      cmExpandList(*mapValue, mappedConfigs, true);
+      mappedConfigs.assign(*mapValue, cmList::EmptyElements::Yes);
     }
   }
 
@@ -2805,7 +3021,9 @@
   bool allowImp = (this->IsDLLPlatform() &&
                    (this->GetType() == cmStateEnums::SHARED_LIBRARY ||
                     this->IsExecutableWithExports())) ||
-    (this->IsAIX() && this->IsExecutableWithExports());
+    (this->IsAIX() && this->IsExecutableWithExports()) ||
+    (this->GetMakefile()->PlatformSupportsAppleTextStubs() &&
+     this->IsSharedLibraryWithExports());
 
   // If a mapping was found, check its configurations.
   for (auto mci = mappedConfigs.begin();
@@ -2875,9 +3093,9 @@
   // If we have not yet found it then the project is willing to try
   // any available configuration.
   if (!loc && !imp) {
-    std::vector<std::string> availableConfigs;
+    cmList availableConfigs;
     if (cmValue iconfigs = this->GetProperty("IMPORTED_CONFIGURATIONS")) {
-      cmExpandList(*iconfigs, availableConfigs);
+      availableConfigs.assign(*iconfigs);
     }
     for (auto aci = availableConfigs.begin();
          !loc && !imp && aci != availableConfigs.end(); ++aci) {
diff --git a/Source/cmTarget.h b/Source/cmTarget.h
index 38bd036..5fe5a28 100644
--- a/Source/cmTarget.h
+++ b/Source/cmTarget.h
@@ -46,11 +46,12 @@
 class cmTarget
 {
 public:
-  enum Visibility
+  enum class Visibility
   {
-    VisibilityNormal,
-    VisibilityImported,
-    VisibilityImportedGlobally
+    Normal,
+    Generated,
+    Imported,
+    ImportedGlobally,
   };
 
   enum class PerConfig
@@ -179,8 +180,11 @@
   std::set<BT<std::pair<std::string, bool>>> const& GetUtilities() const;
 
   //! Set/Get a property of this target file
-  void SetProperty(const std::string& prop, const char* value);
   void SetProperty(const std::string& prop, cmValue value);
+  void SetProperty(const std::string& prop, std::nullptr_t)
+  {
+    this->SetProperty(prop, cmValue{ nullptr });
+  }
   void SetProperty(const std::string& prop, const std::string& value)
   {
     this->SetProperty(prop, cmValue(value));
@@ -204,7 +208,11 @@
 
   //! Return whether or not we are targeting AIX.
   bool IsAIX() const;
+  //! Return whether or not we are targeting Apple.
+  bool IsApple() const;
 
+  bool IsNormal() const;
+  bool IsSynthetic() const;
   bool IsImported() const;
   bool IsImportedGloballyVisible() const;
   bool IsPerConfig() const;
@@ -216,6 +224,10 @@
   //! Return whether this target is an executable with symbol exports enabled.
   bool IsExecutableWithExports() const;
 
+  //! Return whether this target is a shared library with symbol exports
+  //! enabled.
+  bool IsSharedLibraryWithExports() const;
+
   //! Return whether this target is a shared library Framework on Apple.
   bool IsFrameworkOnApple() const;
 
diff --git a/Source/cmTargetLinkLibrariesCommand.cxx b/Source/cmTargetLinkLibrariesCommand.cxx
index 0b123b2..03d7c9f 100644
--- a/Source/cmTargetLinkLibrariesCommand.cxx
+++ b/Source/cmTargetLinkLibrariesCommand.cxx
@@ -552,6 +552,7 @@
       currentProcessingState == ProcessingPlainPrivateInterface) {
     if (this->Target->GetType() == cmStateEnums::STATIC_LIBRARY ||
         this->Target->GetType() == cmStateEnums::OBJECT_LIBRARY) {
+      // TODO: Detect and no-op `$<COMPILE_ONLY>` genexes here.
       std::string configLib =
         this->Target->GetDebugGeneratorExpressions(lib, llt);
       if (cmGeneratorExpression::IsValidTargetName(lib) ||
diff --git a/Source/cmTargetSourcesCommand.cxx b/Source/cmTargetSourcesCommand.cxx
index 53e25b5..cd7ff74 100644
--- a/Source/cmTargetSourcesCommand.cxx
+++ b/Source/cmTargetSourcesCommand.cxx
@@ -13,6 +13,7 @@
 #include "cmExperimental.h"
 #include "cmFileSet.h"
 #include "cmGeneratorExpression.h"
+#include "cmList.h"
 #include "cmListFileCache.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
@@ -320,7 +321,7 @@
     fileSet.first->AddDirectoryEntry(
       BT<std::string>(baseDirectories, this->Makefile->GetBacktrace()));
     if (type == "HEADERS"_s || type == "CXX_MODULE_HEADER_UNITS"_s) {
-      for (auto const& dir : cmExpandedList(baseDirectories)) {
+      for (auto const& dir : cmList{ baseDirectories }) {
         auto interfaceDirectoriesGenex =
           cmStrCat("$<BUILD_INTERFACE:", dir, ">");
         if (cmFileSetVisibilityIsForSelf(visibility)) {
diff --git a/Source/cmTest.cxx b/Source/cmTest.cxx
index e6ed01b..b0d9c2d 100644
--- a/Source/cmTest.cxx
+++ b/Source/cmTest.cxx
@@ -52,10 +52,6 @@
   return cmIsOn(this->GetProperty(prop));
 }
 
-void cmTest::SetProperty(const std::string& prop, const char* value)
-{
-  this->Properties.SetProperty(prop, value);
-}
 void cmTest::SetProperty(const std::string& prop, cmValue value)
 {
   this->Properties.SetProperty(prop, value);
diff --git a/Source/cmTest.h b/Source/cmTest.h
index 1c14310..8b50b87 100644
--- a/Source/cmTest.h
+++ b/Source/cmTest.h
@@ -4,6 +4,7 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
+#include <cstddef>
 #include <string>
 #include <vector>
 
@@ -34,8 +35,11 @@
   std::vector<std::string> const& GetCommand() const { return this->Command; }
 
   //! Set/Get a property of this source file
-  void SetProperty(const std::string& prop, const char* value);
   void SetProperty(const std::string& prop, cmValue value);
+  void SetProperty(const std::string& prop, std::nullptr_t)
+  {
+    this->SetProperty(prop, cmValue{ nullptr });
+  }
   void SetProperty(const std::string& prop, const std::string& value)
   {
     this->SetProperty(prop, cmValue(value));
diff --git a/Source/cmTestGenerator.cxx b/Source/cmTestGenerator.cxx
index 5e325dd..c4a2bc2 100644
--- a/Source/cmTestGenerator.cxx
+++ b/Source/cmTestGenerator.cxx
@@ -13,6 +13,7 @@
 
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorTarget.h"
+#include "cmList.h"
 #include "cmListFileCache.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
@@ -147,16 +148,15 @@
   }
 
   // Evaluate command line arguments
-  std::vector<std::string> argv =
-    this->EvaluateCommandLineArguments(this->Test->GetCommand(), ge, config);
-
-  // Expand arguments if COMMAND_EXPAND_LISTS is set
-  if (this->Test->GetCommandExpandLists()) {
-    argv = cmExpandedLists(argv.begin(), argv.end());
-    // Expanding lists on an empty command may have left it empty
-    if (argv.empty()) {
-      argv.emplace_back();
-    }
+  cmList argv{
+    this->EvaluateCommandLineArguments(this->Test->GetCommand(), ge, config),
+    // Expand arguments if COMMAND_EXPAND_LISTS is set
+    this->Test->GetCommandExpandLists() ? cmList::ExpandElements::Yes
+                                        : cmList::ExpandElements::No
+  };
+  // Expanding lists on an empty command may have left it empty
+  if (argv.empty()) {
+    argv.emplace_back();
   }
 
   // Check whether the command executable is a target whose name is to
@@ -170,7 +170,7 @@
     // Prepend with the emulator when cross compiling if required.
     cmValue emulator = target->GetProperty("CROSSCOMPILING_EMULATOR");
     if (cmNonempty(emulator)) {
-      std::vector<std::string> emulatorWithArgs = cmExpandedList(*emulator);
+      cmList emulatorWithArgs{ *emulator };
       std::string emulatorExe(emulatorWithArgs[0]);
       cmSystemTools::ConvertToUnixSlashes(emulatorExe);
       os << cmOutputConverter::EscapeForCMake(emulatorExe) << " ";
diff --git a/Source/cmTryRunCommand.cxx b/Source/cmTryRunCommand.cxx
index b648d9b..c24c418 100644
--- a/Source/cmTryRunCommand.cxx
+++ b/Source/cmTryRunCommand.cxx
@@ -2,7 +2,6 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmTryRunCommand.h"
 
-#include <cstdio>
 #include <stdexcept>
 
 #include <cm/optional>
@@ -15,6 +14,7 @@
 #include "cmCoreTryCompile.h"
 #include "cmDuration.h"
 #include "cmExecutionStatus.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
 #include "cmRange.h"
@@ -271,7 +271,7 @@
   const std::string& emulator =
     this->Makefile->GetSafeDefinition("CMAKE_CROSSCOMPILING_EMULATOR");
   if (!emulator.empty()) {
-    std::vector<std::string> emulatorWithArgs = cmExpandedList(emulator);
+    cmList emulatorWithArgs{ emulator };
     finalCommand +=
       cmSystemTools::ConvertToRunCommandPath(emulatorWithArgs[0]);
     finalCommand += " ";
@@ -292,11 +292,9 @@
     workDir ? workDir->c_str() : nullptr, cmSystemTools::OUTPUT_NONE,
     cmDuration::zero());
   // set the run var
-  char retChar[16];
-  const char* retStr;
+  std::string retStr;
   if (worked) {
-    snprintf(retChar, sizeof(retChar), "%i", retVal);
-    retStr = retChar;
+    retStr = std::to_string(retVal);
   } else {
     retStr = "FAILED_TO_RUN";
   }
@@ -350,7 +348,7 @@
                detailsString);
     this->Makefile->AddCacheDefinition(this->RunResultVariable,
                                        "PLEASE_FILL_OUT-FAILED_TO_RUN",
-                                       comment.c_str(), cmStateEnums::STRING);
+                                       comment, cmStateEnums::STRING);
 
     cmState* state = this->Makefile->GetState();
     cmValue existingValue = state->GetCacheEntryValue(this->RunResultVariable);
@@ -371,9 +369,9 @@
         "would have printed on stdout on its target platform.\n",
         detailsString);
 
-      this->Makefile->AddCacheDefinition(
-        internalRunOutputStdOutName, "PLEASE_FILL_OUT-NOTFOUND",
-        comment.c_str(), cmStateEnums::STRING);
+      this->Makefile->AddCacheDefinition(internalRunOutputStdOutName,
+                                         "PLEASE_FILL_OUT-NOTFOUND", comment,
+                                         cmStateEnums::STRING);
       cmState* state = this->Makefile->GetState();
       cmValue existing =
         state->GetCacheEntryValue(internalRunOutputStdOutName);
@@ -393,9 +391,9 @@
         "would have printed on stderr on its target platform.\n",
         detailsString);
 
-      this->Makefile->AddCacheDefinition(
-        internalRunOutputStdErrName, "PLEASE_FILL_OUT-NOTFOUND",
-        comment.c_str(), cmStateEnums::STRING);
+      this->Makefile->AddCacheDefinition(internalRunOutputStdErrName,
+                                         "PLEASE_FILL_OUT-NOTFOUND", comment,
+                                         cmStateEnums::STRING);
       cmState* state = this->Makefile->GetState();
       cmValue existing =
         state->GetCacheEntryValue(internalRunOutputStdErrName);
@@ -415,9 +413,9 @@
         "would have printed on stdout and stderr on its target platform.\n",
         detailsString);
 
-      this->Makefile->AddCacheDefinition(
-        internalRunOutputName, "PLEASE_FILL_OUT-NOTFOUND", comment.c_str(),
-        cmStateEnums::STRING);
+      this->Makefile->AddCacheDefinition(internalRunOutputName,
+                                         "PLEASE_FILL_OUT-NOTFOUND", comment,
+                                         cmStateEnums::STRING);
       cmState* state = this->Makefile->GetState();
       cmValue existing = state->GetCacheEntryValue(internalRunOutputName);
       if (existing) {
diff --git a/Source/cmUVProcessChain.cxx b/Source/cmUVProcessChain.cxx
index 3faf2f6..257c054 100644
--- a/Source/cmUVProcessChain.cxx
+++ b/Source/cmUVProcessChain.cxx
@@ -140,6 +140,19 @@
   return *this;
 }
 
+cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetMergedBuiltinStreams()
+{
+  this->MergedBuiltinStreams = true;
+  return this->SetBuiltinStream(Stream_OUTPUT).SetBuiltinStream(Stream_ERROR);
+}
+
+cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetWorkingDirectory(
+  std::string dir)
+{
+  this->WorkingDirectory = std::move(dir);
+  return *this;
+}
+
 cmUVProcessChain cmUVProcessChainBuilder::Start() const
 {
   cmUVProcessChain chain;
@@ -174,27 +187,6 @@
 {
   this->Builder = builder;
 
-  auto const& output =
-    this->Builder->Stdio[cmUVProcessChainBuilder::Stream_OUTPUT];
-  auto& outputData = this->OutputStreamData;
-  switch (output.Type) {
-    case cmUVProcessChainBuilder::None:
-      outputData.Stdio.flags = UV_IGNORE;
-      break;
-
-    case cmUVProcessChainBuilder::Builtin:
-      outputData.BuiltinStream.init(*this->Loop, 0);
-      outputData.Stdio.flags =
-        static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
-      outputData.Stdio.data.stream = outputData.BuiltinStream;
-      break;
-
-    case cmUVProcessChainBuilder::External:
-      outputData.Stdio.flags = UV_INHERIT_FD;
-      outputData.Stdio.data.fd = output.FileDescriptor;
-      break;
-  }
-
   auto const& error =
     this->Builder->Stdio[cmUVProcessChainBuilder::Stream_ERROR];
   auto& errorData = this->ErrorStreamData;
@@ -224,6 +216,32 @@
       break;
   }
 
+  auto const& output =
+    this->Builder->Stdio[cmUVProcessChainBuilder::Stream_OUTPUT];
+  auto& outputData = this->OutputStreamData;
+  switch (output.Type) {
+    case cmUVProcessChainBuilder::None:
+      outputData.Stdio.flags = UV_IGNORE;
+      break;
+
+    case cmUVProcessChainBuilder::Builtin:
+      if (this->Builder->MergedBuiltinStreams) {
+        outputData.Stdio.flags = UV_INHERIT_FD;
+        outputData.Stdio.data.fd = errorData.Stdio.data.fd;
+      } else {
+        outputData.BuiltinStream.init(*this->Loop, 0);
+        outputData.Stdio.flags =
+          static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
+        outputData.Stdio.data.stream = outputData.BuiltinStream;
+      }
+      break;
+
+    case cmUVProcessChainBuilder::External:
+      outputData.Stdio.flags = UV_INHERIT_FD;
+      outputData.Stdio.data.fd = output.FileDescriptor;
+      break;
+  }
+
   return true;
 }
 
@@ -248,6 +266,9 @@
   arguments.push_back(nullptr);
   options.args = const_cast<char**>(arguments.data());
   options.flags = UV_PROCESS_WINDOWS_HIDE;
+  if (!this->Builder->WorkingDirectory.empty()) {
+    options.cwd = this->Builder->WorkingDirectory.c_str();
+  }
 
   std::array<uv_stdio_container_t, 3> stdio;
   stdio[0] = uv_stdio_container_t();
@@ -289,7 +310,8 @@
 bool cmUVProcessChain::InternalData::Finish()
 {
   if (this->Builder->Stdio[cmUVProcessChainBuilder::Stream_OUTPUT].Type ==
-      cmUVProcessChainBuilder::Builtin) {
+        cmUVProcessChainBuilder::Builtin &&
+      !this->Builder->MergedBuiltinStreams) {
     this->OutputStreamData.Streambuf.open(
       this->OutputStreamData.BuiltinStream);
   }
@@ -339,6 +361,9 @@
 
 std::istream* cmUVProcessChain::OutputStream()
 {
+  if (this->Data->Builder->MergedBuiltinStreams) {
+    return this->Data->ErrorStreamData.GetBuiltinStream();
+  }
   return this->Data->OutputStreamData.GetBuiltinStream();
 }
 
diff --git a/Source/cmUVProcessChain.h b/Source/cmUVProcessChain.h
index 5e8e7e6..3ade3fd 100644
--- a/Source/cmUVProcessChain.h
+++ b/Source/cmUVProcessChain.h
@@ -30,7 +30,9 @@
     const std::vector<std::string>& arguments);
   cmUVProcessChainBuilder& SetNoStream(Stream stdio);
   cmUVProcessChainBuilder& SetBuiltinStream(Stream stdio);
+  cmUVProcessChainBuilder& SetMergedBuiltinStreams();
   cmUVProcessChainBuilder& SetExternalStream(Stream stdio, int fd);
+  cmUVProcessChainBuilder& SetWorkingDirectory(std::string dir);
 
   cmUVProcessChain Start() const;
 
@@ -57,6 +59,8 @@
 
   std::array<StdioConfiguration, 3> Stdio;
   std::vector<ProcessConfiguration> Processes;
+  std::string WorkingDirectory;
+  bool MergedBuiltinStreams = false;
 };
 
 class cmUVProcessChain
diff --git a/Source/cmVisualStudio10TargetGenerator.cxx b/Source/cmVisualStudio10TargetGenerator.cxx
index 52de6ff..6d62aff 100644
--- a/Source/cmVisualStudio10TargetGenerator.cxx
+++ b/Source/cmVisualStudio10TargetGenerator.cxx
@@ -9,6 +9,7 @@
 #include <set>
 #include <sstream>
 
+#include <cm/filesystem>
 #include <cm/memory>
 #include <cm/optional>
 #include <cm/string_view>
@@ -31,6 +32,7 @@
 #include "cmGlobalVisualStudio7Generator.h"
 #include "cmGlobalVisualStudioGenerator.h"
 #include "cmLinkLineDeviceComputer.h"
+#include "cmList.h"
 #include "cmListFileCache.h"
 #include "cmLocalGenerator.h"
 #include "cmLocalVisualStudio10Generator.h"
@@ -50,6 +52,8 @@
 #include "cmValue.h"
 #include "cmVisualStudioGeneratorOptions.h"
 
+const std::string kBuildSystemSources = "Buildsystem Input Files";
+
 struct cmIDEFlagTable;
 
 static void ConvertToWindowsSlash(std::string& s);
@@ -693,12 +697,12 @@
 
     switch (this->ProjectType) {
       case VsProjectType::vcxproj: {
+        Elem(e0, "Import").Attribute("Project", VS10_CXX_DEFAULT_PROPS);
         std::string const& props =
           this->GlobalGenerator->GetPlatformToolsetVersionProps();
         if (!props.empty()) {
           Elem(e0, "Import").Attribute("Project", props);
         }
-        Elem(e0, "Import").Attribute("Project", VS10_CXX_DEFAULT_PROPS);
       } break;
       case VsProjectType::csproj:
         Elem(e0, "Import")
@@ -955,7 +959,11 @@
                      "executables."));
           return;
         }
-        outputType = "Exe";
+        if (cmIsOn(win32)) {
+          outputType = "WinExe";
+        } else {
+          outputType = "Exe";
+        }
       } break;
       case cmStateEnums::UTILITY:
       case cmStateEnums::INTERFACE_LIBRARY:
@@ -1086,10 +1094,10 @@
 
 void cmVisualStudio10TargetGenerator::WriteDotNetReferences(Elem& e0)
 {
-  std::vector<std::string> references;
+  cmList references;
   if (cmValue vsDotNetReferences =
         this->GeneratorTarget->GetProperty("VS_DOTNET_REFERENCES")) {
-    cmExpandList(*vsDotNetReferences, references);
+    references.assign(*vsDotNetReferences);
   }
   cmPropertyMap const& props = this->GeneratorTarget->Target->GetProperties();
   for (auto const& i : props.GetList()) {
@@ -1106,7 +1114,7 @@
   }
   if (!references.empty() || !this->DotNetHintReferences.empty()) {
     Elem e1(e0, "ItemGroup");
-    for (std::string const& ri : references) {
+    for (auto const& ri : references) {
       // if the entry from VS_DOTNET_REFERENCES is an existing file, generate
       // a new hint-reference and name it from the filename
       if (cmsys::SystemTools::FileExists(ri, true)) {
@@ -1163,7 +1171,7 @@
   cmValue imports =
     this->GeneratorTarget->Target->GetProperty("VS_PROJECT_IMPORT");
   if (imports) {
-    std::vector<std::string> argsSplit = cmExpandedList(*imports, false);
+    cmList argsSplit{ *imports };
     for (auto& path : argsSplit) {
       if (!cmsys::SystemTools::FileIsFullPath(path)) {
         path = this->Makefile->GetCurrentSourceDirectory() + "/" + path;
@@ -1361,20 +1369,20 @@
 
 void cmVisualStudio10TargetGenerator::WriteWinRTReferences(Elem& e0)
 {
-  std::vector<std::string> references;
+  cmList references;
   if (cmValue vsWinRTReferences =
         this->GeneratorTarget->GetProperty("VS_WINRT_REFERENCES")) {
-    cmExpandList(*vsWinRTReferences, references);
+    references.assign(*vsWinRTReferences);
   }
 
   if (this->GlobalGenerator->TargetsWindowsPhone() &&
       this->GlobalGenerator->GetSystemVersion() == "8.0" &&
       references.empty()) {
-    references.push_back("platform.winmd");
+    references.push_back(std::string{ "platform.winmd" });
   }
   if (!references.empty()) {
     Elem e1(e0, "ItemGroup");
-    for (std::string const& ri : references) {
+    for (auto const& ri : references) {
       Elem e2(e1, "Reference");
       e2.Attribute("Include", ri);
       e2.Element("IsWinMDFile", "true");
@@ -1806,10 +1814,15 @@
       this->WriteCustomRuleCSharp(e0, c, name, script, additional_inputs.str(),
                                   outputs.str(), comment, ccg);
     } else {
-      // FIXME(#18405): Enable BuildInParallel::Yes via an option or policy.
+      BuildInParallel buildInParallel = BuildInParallel::No;
+      if (command.GetCMP0147Status() == cmPolicies::NEW &&
+          !command.GetUsesTerminal() &&
+          !(command.HasMainDependency() && source->GetIsGenerated())) {
+        buildInParallel = BuildInParallel::Yes;
+      }
       this->WriteCustomRuleCpp(*spe2, c, script, additional_inputs.str(),
                                outputs.str(), comment, ccg, symbolic,
-                               BuildInParallel::No);
+                               buildInParallel);
     }
   }
 }
@@ -1940,7 +1953,13 @@
                  "http://schemas.microsoft.com/developer/msbuild/2003");
 
     for (auto const& ti : this->Tools) {
-      this->WriteGroupSources(e0, ti.first, ti.second, sourceGroups);
+      if ((this->GeneratorTarget->GetName() ==
+           CMAKE_CHECK_BUILD_SYSTEM_TARGET) &&
+          (ti.first == "None")) {
+        this->WriteBuildSystemSources(e0, ti.first, ti.second);
+      } else {
+        this->WriteGroupSources(e0, ti.first, ti.second, sourceGroups);
+      }
     }
 
     // Added files are images and the manifest.
@@ -2011,6 +2030,18 @@
                    "rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;"
                    "gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms");
       }
+
+      if (this->GeneratorTarget->GetName() ==
+          CMAKE_CHECK_BUILD_SYSTEM_TARGET) {
+        for (const std::string& filter : this->BuildSystemSourcesFilters) {
+          std::string guidName = "SG_Filter_";
+          guidName += filter;
+          std::string guid = this->GlobalGenerator->GetGUID(guidName);
+          Elem e2(e1, "Filter");
+          e2.Attribute("Include", filter);
+          e2.Element("UniqueIdentifier", "{" + guid + "}");
+        }
+      }
     }
   }
   fout << '\n';
@@ -2077,6 +2108,39 @@
   }
 }
 
+void cmVisualStudio10TargetGenerator::WriteBuildSystemSources(
+  Elem& e0, std::string const& name, ToolSources const& sources)
+{
+  const std::string srcDir = this->Makefile->GetCurrentSourceDirectory();
+  const std::string::size_type srcDirLength = srcDir.length();
+
+  Elem e1(e0, "ItemGroup");
+  e1.SetHasElements();
+  for (ToolSource const& s : sources) {
+    cmSourceFile const* sf = s.SourceFile;
+    std::string const& source = sf->GetFullPath();
+
+    cm::filesystem::path sourcePath(source);
+    bool isInSrcDir = cmHasPrefix(source, srcDir);
+
+    std::string filter = kBuildSystemSources;
+    if (isInSrcDir) {
+      std::string parentPath = sourcePath.parent_path().string();
+      if (srcDir != parentPath) {
+        filter += parentPath.substr(srcDirLength);
+      }
+      ConvertToWindowsSlash(filter);
+      this->BuildSystemSourcesFilters.insert(filter);
+    }
+
+    std::string path = this->ConvertPath(source, s.RelativePath);
+    ConvertToWindowsSlash(path);
+    Elem e2(e1, name);
+    e2.Attribute("Include", path);
+    e2.Element("Filter", filter);
+  }
+}
+
 void cmVisualStudio10TargetGenerator::WriteHeaderSource(
   Elem& e1, cmSourceFile const* sf, ConfigToSettings const& toolSettings)
 {
@@ -2104,8 +2168,8 @@
     for (const std::string& config : this->Configurations) {
       std::string evaluated = cge->Evaluate(this->LocalGenerator, config);
 
-      std::vector<std::string> settings = cmExpandedList(evaluated);
-      for (const std::string& setting : settings) {
+      cmList settings{ evaluated };
+      for (const auto& setting : settings) {
         const std::string::size_type assignment = setting.find('=');
         if (assignment != std::string::npos) {
           const std::string propName = setting.substr(0, assignment);
@@ -2705,16 +2769,11 @@
 
   if (lang == "ASM_NASM") {
     if (cmValue objectDeps = sf.GetProperty("OBJECT_DEPENDS")) {
-      std::string dependencies;
-      std::vector<std::string> depends = cmExpandedList(*objectDeps);
-      const char* sep = "";
-      for (std::string& d : depends) {
+      cmList depends{ *objectDeps };
+      for (auto& d : depends) {
         ConvertToWindowsSlash(d);
-        dependencies += sep;
-        dependencies += d;
-        sep = ";";
       }
-      e2.Element("AdditionalDependencies", dependencies);
+      e2.Element("AdditionalDependencies", depends.join(";"));
     }
   }
 
@@ -2743,7 +2802,14 @@
          fs->GetType() == "CXX_MODULE_HEADER_UNITS"_s)) {
       if (lang == "CXX"_s) {
         if (fs->GetType() == "CXX_MODULES"_s) {
-          compileAsPerConfig = "CompileAsCppModule";
+          if (shouldScanForModules &&
+              this->GlobalGenerator->IsScanDependenciesSupported()) {
+            // ScanSourceforModuleDependencies uses 'cl /scanDependencies' and
+            // can distinguish module interface units and internal partitions.
+            compileAsPerConfig = "CompileAsCpp";
+          } else {
+            compileAsPerConfig = "CompileAsCppModule";
+          }
         } else {
           compileAsPerConfig = "CompileAsHeaderUnit";
         }
@@ -3105,6 +3171,17 @@
   return includes;
 }
 
+std::string cmVisualStudio10TargetGenerator::GetTargetOutputName() const
+{
+  std::string config;
+  if (!this->Configurations.empty()) {
+    config = this->Configurations[0];
+  }
+  const auto& nameComponents =
+    this->GeneratorTarget->GetFullNameComponents(config);
+  return nameComponents.prefix + nameComponents.base;
+}
+
 bool cmVisualStudio10TargetGenerator::ComputeClOptions()
 {
   return std::all_of(
@@ -3577,13 +3654,13 @@
   if (this->GeneratorTarget->GetPropertyAsBool("CUDA_SEPARABLE_COMPILATION")) {
     cudaOptions.AddFlag("GenerateRelocatableDeviceCode", "true");
   }
-  bool notPtx = true;
+  bool notPtxLike = true;
   if (this->GeneratorTarget->GetPropertyAsBool("CUDA_PTX_COMPILATION")) {
     cudaOptions.AddFlag("NvccCompilation", "ptx");
     // We drop the %(Extension) component as CMake expects all PTX files
     // to not have the source file extension at all
     cudaOptions.AddFlag("CompileOut", "$(IntDir)%(Filename).ptx");
-    notPtx = false;
+    notPtxLike = false;
 
     if (cmSystemTools::VersionCompare(cmSystemTools::OP_GREATER_EQUAL,
                                       cudaVersion, "9.0") &&
@@ -3598,9 +3675,24 @@
                           "%(BaseCommandLineTemplate) [CompileOut] [FastMath] "
                           "[Defines] \"%(FullPath)\"");
     }
+  } else if (this->GeneratorTarget->GetPropertyAsBool(
+               "CUDA_CUBIN_COMPILATION")) {
+    cudaOptions.AddFlag("NvccCompilation", "cubin");
+    cudaOptions.AddFlag("CompileOut", "$(IntDir)%(Filename).cubin");
+    notPtxLike = false;
+  } else if (this->GeneratorTarget->GetPropertyAsBool(
+               "CUDA_FATBIN_COMPILATION")) {
+    cudaOptions.AddFlag("NvccCompilation", "fatbin");
+    cudaOptions.AddFlag("CompileOut", "$(IntDir)%(Filename).fatbin");
+    notPtxLike = false;
+  } else if (this->GeneratorTarget->GetPropertyAsBool(
+               "CUDA_OPTIX_COMPILATION")) {
+    cudaOptions.AddFlag("NvccCompilation", "optix-ir");
+    cudaOptions.AddFlag("CompileOut", "$(IntDir)%(Filename).optixir");
+    notPtxLike = false;
   }
 
-  if (notPtx &&
+  if (notPtxLike &&
       cmSystemTools::VersionCompareGreaterEq(
         "8.0", this->GlobalGenerator->GetPlatformToolsetCudaString())) {
     // Explicitly state that we want this file to be treated as a
@@ -4732,13 +4824,13 @@
 
 void cmVisualStudio10TargetGenerator::WriteSDKReferences(Elem& e0)
 {
-  std::vector<std::string> sdkReferences;
+  cmList sdkReferences;
   std::unique_ptr<Elem> spe1;
   if (cmValue vsSDKReferences =
         this->GeneratorTarget->GetProperty("VS_SDK_REFERENCES")) {
-    cmExpandList(*vsSDKReferences, sdkReferences);
+    sdkReferences.assign(*vsSDKReferences);
     spe1 = cm::make_unique<Elem>(e0, "ItemGroup");
-    for (std::string const& ri : sdkReferences) {
+    for (auto const& ri : sdkReferences) {
       Elem(*spe1, "SDKReference").Attribute("Include", ri);
     }
   }
@@ -5054,8 +5146,7 @@
     this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget);
   ConvertToWindowsSlash(artifactDir);
   std::string artifactDirXML = cmVS10EscapeXML(artifactDir);
-  std::string targetNameXML =
-    cmVS10EscapeXML(this->GeneratorTarget->GetName());
+  const std::string& targetNameXML = cmVS10EscapeXML(GetTargetOutputName());
 
   cmGeneratedFileStream fout(manifestFile);
   fout.SetCopyIfDifferent(true);
@@ -5137,8 +5228,7 @@
     this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget);
   ConvertToWindowsSlash(artifactDir);
   std::string artifactDirXML = cmVS10EscapeXML(artifactDir);
-  std::string targetNameXML =
-    cmVS10EscapeXML(this->GeneratorTarget->GetName());
+  const std::string& targetNameXML = cmVS10EscapeXML(GetTargetOutputName());
 
   cmGeneratedFileStream fout(manifestFile);
   fout.SetCopyIfDifferent(true);
@@ -5200,8 +5290,7 @@
     this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget);
   ConvertToWindowsSlash(artifactDir);
   std::string artifactDirXML = cmVS10EscapeXML(artifactDir);
-  std::string targetNameXML =
-    cmVS10EscapeXML(this->GeneratorTarget->GetName());
+  const std::string& targetNameXML = cmVS10EscapeXML(GetTargetOutputName());
 
   cmGeneratedFileStream fout(manifestFile);
   fout.SetCopyIfDifferent(true);
@@ -5255,8 +5344,7 @@
     this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget);
   ConvertToWindowsSlash(artifactDir);
   std::string artifactDirXML = cmVS10EscapeXML(artifactDir);
-  std::string targetNameXML =
-    cmVS10EscapeXML(this->GeneratorTarget->GetName());
+  const std::string& targetNameXML = cmVS10EscapeXML(GetTargetOutputName());
 
   cmGeneratedFileStream fout(manifestFile);
   fout.SetCopyIfDifferent(true);
@@ -5315,8 +5403,7 @@
     this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget);
   ConvertToWindowsSlash(artifactDir);
   std::string artifactDirXML = cmVS10EscapeXML(artifactDir);
-  std::string targetNameXML =
-    cmVS10EscapeXML(this->GeneratorTarget->GetName());
+  const std::string& targetNameXML = cmVS10EscapeXML(GetTargetOutputName());
 
   cmGeneratedFileStream fout(manifestFile);
   fout.SetCopyIfDifferent(true);
diff --git a/Source/cmVisualStudio10TargetGenerator.h b/Source/cmVisualStudio10TargetGenerator.h
index e00f692..a87cb01 100644
--- a/Source/cmVisualStudio10TargetGenerator.h
+++ b/Source/cmVisualStudio10TargetGenerator.h
@@ -117,6 +117,7 @@
 
   std::vector<std::string> GetIncludes(std::string const& config,
                                        std::string const& lang) const;
+  std::string GetTargetOutputName() const;
 
   bool ComputeClOptions();
   bool ComputeClOptions(std::string const& configName);
@@ -192,6 +193,9 @@
   void WriteGroupSources(Elem& e0, std::string const& name,
                          ToolSources const& sources,
                          std::vector<cmSourceGroup>&);
+  void WriteBuildSystemSources(Elem& e0, std::string const& name,
+                               ToolSources const& sources);
+
   void AddMissingSourceGroups(std::set<cmSourceGroup const*>& groupsUsed,
                               const std::vector<cmSourceGroup>& allGroups);
   bool IsResxHeader(const std::string& headerFile);
@@ -242,6 +246,7 @@
   std::set<std::string> ASanEnabledConfigurations;
   std::set<std::string> FuzzerEnabledConfigurations;
   std::map<std::string, std::string> SpectreMitigation;
+  std::set<std::string> BuildSystemSourcesFilters;
   cmGlobalVisualStudio10Generator* const GlobalGenerator;
   cmLocalVisualStudio10Generator* const LocalGenerator;
   std::set<std::string> CSharpCustomCommandNames;
diff --git a/Source/cmXCodeScheme.cxx b/Source/cmXCodeScheme.cxx
index e727d22..7f26fd8 100644
--- a/Source/cmXCodeScheme.cxx
+++ b/Source/cmXCodeScheme.cxx
@@ -13,6 +13,7 @@
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorTarget.h"
+#include "cmList.h"
 #include "cmStateTypes.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
@@ -270,7 +271,7 @@
 
   if (cmValue argList =
         this->Target->GetTarget()->GetProperty("XCODE_SCHEME_ARGUMENTS")) {
-    std::vector<std::string> arguments = cmExpandedList(*argList);
+    cmList arguments{ *argList };
     if (!arguments.empty()) {
       xout.StartElement("CommandLineArguments");
 
@@ -290,7 +291,7 @@
 
   if (cmValue envList =
         this->Target->GetTarget()->GetProperty("XCODE_SCHEME_ENVIRONMENT")) {
-    std::vector<std::string> envs = cmExpandedList(*envList);
+    cmList envs{ *envList };
     if (!envs.empty()) {
       xout.StartElement("EnvironmentVariables");
 
diff --git a/Source/cm_codecvt.cxx b/Source/cm_codecvt.cxx
index 2d2a377..12877b8 100644
--- a/Source/cm_codecvt.cxx
+++ b/Source/cm_codecvt.cxx
@@ -171,7 +171,7 @@
   }
 
   int tlen = WideCharToMultiByte(m_codepage, 0, wbuf, wlen, to_next,
-                                 to_end - to_next, NULL, NULL);
+                                 to_end - to_next, nullptr, nullptr);
   if (tlen <= 0) {
     if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
       return std::codecvt_base::partial;
@@ -206,7 +206,7 @@
   }
 
   int tlen = WideCharToMultiByte(m_codepage, 0, wbuf, wlen, to_next,
-                                 to_end - to_next, NULL, NULL);
+                                 to_end - to_next, nullptr, nullptr);
   if (tlen <= 0) {
     if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
       return std::codecvt_base::partial;
diff --git a/Source/cmake.cxx b/Source/cmake.cxx
index 468ff73..f30d4d3 100644
--- a/Source/cmake.cxx
+++ b/Source/cmake.cxx
@@ -38,6 +38,10 @@
 #include "cmCMakePresetsGraph.h"
 #include "cmCommandLineArgument.h"
 #include "cmCommands.h"
+#ifdef CMake_ENABLE_DEBUGGER
+#  include "cmDebuggerAdapter.h"
+#  include "cmDebuggerPipeConnection.h"
+#endif
 #include "cmDocumentation.h"
 #include "cmDocumentationEntry.h"
 #include "cmDuration.h"
@@ -52,6 +56,8 @@
 #if !defined(CMAKE_BOOTSTRAP)
 #  include "cmMakefileProfilingData.h"
 #endif
+#include "cmJSONState.h"
+#include "cmList.h"
 #include "cmMessenger.h"
 #include "cmState.h"
 #include "cmStateDirectory.h"
@@ -335,7 +341,7 @@
     // The "c" extension MUST precede the "C" extension.
     setupExts(this->CLikeSourceFileExtensions,
               { "c", "C", "c++", "cc", "cpp", "cxx", "cu", "mpp", "m", "M",
-                "mm", "ixx", "cppm" });
+                "mm", "ixx", "cppm", "ccm", "cxxm", "c++m" });
     setupExts(this->HeaderFileExtensions,
               { "h", "hh", "h++", "hm", "hpp", "hxx", "in", "txx" });
     setupExts(this->CudaFileExtensions, { "cu" });
@@ -409,6 +415,11 @@
   obj["fileApi"] = cmFileAPI::ReportCapabilities();
   obj["serverMode"] = false;
   obj["tls"] = static_cast<bool>(curlVersion->features & CURL_VERSION_SSL);
+#  ifdef CMake_ENABLE_DEBUGGER
+  obj["debugger"] = true;
+#  else
+  obj["debugger"] = false;
+#  endif
 
   return obj;
 }
@@ -615,6 +626,13 @@
   };
 
   auto ScriptLambda = [&](std::string const& path, cmake* state) -> bool {
+#ifdef CMake_ENABLE_DEBUGGER
+    // Script mode doesn't hit the usual code path in cmake::Run() that starts
+    // the debugger, so start it manually here instead.
+    if (!this->StartDebuggerIfEnabled()) {
+      return false;
+    }
+#endif
     // Register fake project commands that hint misuse in script mode.
     GetProjectCommandsInScriptMode(state->GetState());
     // Documented behavior of CMAKE{,_CURRENT}_{SOURCE,BINARY}_DIR is to be
@@ -812,7 +830,7 @@
     }
   } else if (mode == "COMPILE"_s) {
     std::string includes = mf->GetSafeDefinition("PACKAGE_INCLUDE_DIRS");
-    std::vector<std::string> includeDirs = cmExpandedList(includes);
+    cmList includeDirs{ includes };
 
     this->GlobalGenerator->CreateGenerationObjects();
     const auto& lg = this->GlobalGenerator->LocalGenerators[0];
@@ -828,7 +846,7 @@
     tgt->SetProperty("LINKER_LANGUAGE", language);
 
     std::string libs = mf->GetSafeDefinition("PACKAGE_LIBRARIES");
-    std::vector<std::string> libList = cmExpandedList(libs);
+    cmList libList{ libs };
     for (std::string const& lib : libList) {
       tgt->AddLinkLibrary(*mf, lib, GENERAL_LibraryType);
     }
@@ -964,7 +982,7 @@
     return true;
   };
 
-  auto ToolsetLamda = [&](std::string const& value, cmake* state) -> bool {
+  auto ToolsetLambda = [&](std::string const& value, cmake* state) -> bool {
     if (haveToolset) {
       cmSystemTools::Error("Multiple -T options not allowed");
       return false;
@@ -1016,7 +1034,7 @@
                      CommandArgument::RequiresSeparator::No, PlatformLambda },
     CommandArgument{ "-T", "No toolset specified for -T",
                      CommandArgument::Values::One,
-                     CommandArgument::RequiresSeparator::No, ToolsetLamda },
+                     CommandArgument::RequiresSeparator::No, ToolsetLambda },
     CommandArgument{ "--toolchain", "No file specified for --toolchain",
                      CommandArgument::Values::One, IgnoreAndTrueLambda },
     CommandArgument{ "--install-prefix",
@@ -1025,7 +1043,7 @@
 
     CommandArgument{ "--check-build-system", CommandArgument::Values::Two,
                      [](std::string const& value, cmake* state) -> bool {
-                       std::vector<std::string> values = cmExpandedList(value);
+                       cmList values{ value };
                        state->CheckBuildSystemArgument = values[0];
                        state->ClearBuildSystem = (atoi(values[1].c_str()) > 0);
                        return true;
@@ -1142,49 +1160,64 @@
         std::cout << ".\n";
         return true;
       } },
+    CommandArgument{ "--trace", CommandArgument::Values::Zero,
+                     [](std::string const&, cmake* state) -> bool {
+                       std::cout << "Put cmake in trace mode.\n";
+                       state->SetTrace(true);
+                       state->SetTraceExpand(false);
+                       return true;
+                     } },
     CommandArgument{ "--trace-expand", CommandArgument::Values::Zero,
                      [](std::string const&, cmake* state) -> bool {
-                       std::cout << "Running with expanded trace output on.\n";
+                       std::cout << "Put cmake in trace mode, but with "
+                                    "variables expanded.\n";
                        state->SetTrace(true);
                        state->SetTraceExpand(true);
                        return true;
                      } },
-    CommandArgument{ "--trace-format", CommandArgument::Values::One,
-                     [](std::string const& value, cmake* state) -> bool {
-                       state->SetTrace(true);
-                       const auto traceFormat = StringToTraceFormat(value);
-                       if (traceFormat == TraceFormat::TRACE_UNDEFINED) {
-                         cmSystemTools::Error(
-                           "Invalid format specified for --trace-format. "
-                           "Valid formats are human, json-v1.");
-                         return false;
+    CommandArgument{
+      "--trace-format", "Invalid format specified for --trace-format",
+      CommandArgument::Values::One,
+      [](std::string const& value, cmake* state) -> bool {
+        std::cout << "Put cmake in trace mode and sets the "
+                     "trace output format.\n";
+        state->SetTrace(true);
+        const auto traceFormat = StringToTraceFormat(value);
+        if (traceFormat == TraceFormat::Undefined) {
+          cmSystemTools::Error("Invalid format specified for --trace-format. "
+                               "Valid formats are human, json-v1.");
+          return false;
+        }
+        state->SetTraceFormat(traceFormat);
+        return true;
+      } },
+    CommandArgument{ "--trace-source", "No file specified for --trace-source",
+                     CommandArgument::Values::OneOrMore,
+                     [](std::string const& values, cmake* state) -> bool {
+                       std::cout << "Put cmake in trace mode, but output only "
+                                    "lines of a specified file. Multiple "
+                                    "options are allowed.\n";
+                       for (auto file :
+                            cmSystemTools::SplitString(values, ';')) {
+                         cmSystemTools::ConvertToUnixSlashes(file);
+                         state->AddTraceSource(file);
                        }
-                       state->SetTraceFormat(traceFormat);
-                       return true;
-                     } },
-    CommandArgument{ "--trace-source", CommandArgument::Values::One,
-                     [](std::string const& value, cmake* state) -> bool {
-                       std::string file(value);
-                       cmSystemTools::ConvertToUnixSlashes(file);
-                       state->AddTraceSource(file);
                        state->SetTrace(true);
                        return true;
                      } },
-    CommandArgument{ "--trace-redirect", CommandArgument::Values::One,
+    CommandArgument{ "--trace-redirect",
+                     "No file specified for --trace-redirect",
+                     CommandArgument::Values::One,
                      [](std::string const& value, cmake* state) -> bool {
+                       std::cout
+                         << "Put cmake in trace mode and redirect trace "
+                            "output to a file instead of stderr.\n";
                        std::string file(value);
                        cmSystemTools::ConvertToUnixSlashes(file);
                        state->SetTraceFile(file);
                        state->SetTrace(true);
                        return true;
                      } },
-    CommandArgument{ "--trace", CommandArgument::Values::Zero,
-                     [](std::string const&, cmake* state) -> bool {
-                       std::cout << "Running with trace output on.\n";
-                       state->SetTrace(true);
-                       state->SetTraceExpand(false);
-                       return true;
-                     } },
     CommandArgument{ "--warn-uninitialized", CommandArgument::Values::Zero,
                      [](std::string const&, cmake* state) -> bool {
                        std::cout << "Warn about uninitialized values.\n";
@@ -1216,7 +1249,52 @@
                      "CMAKE_COMPILE_WARNING_AS_ERROR variable.\n";
         state->SetIgnoreWarningAsError(true);
         return true;
-      } }
+      } },
+    CommandArgument{ "--debugger", CommandArgument::Values::Zero,
+                     [](std::string const&, cmake* state) -> bool {
+#ifdef CMake_ENABLE_DEBUGGER
+                       std::cout << "Running with debugger on.\n";
+                       state->SetDebuggerOn(true);
+                       return true;
+#else
+                       static_cast<void>(state);
+                       cmSystemTools::Error(
+                         "CMake was not built with support for --debugger");
+                       return false;
+#endif
+                     } },
+    CommandArgument{ "--debugger-pipe",
+                     "No path specified for --debugger-pipe",
+                     CommandArgument::Values::One,
+                     [](std::string const& value, cmake* state) -> bool {
+#ifdef CMake_ENABLE_DEBUGGER
+                       state->DebuggerPipe = value;
+                       return true;
+#else
+                       static_cast<void>(value);
+                       static_cast<void>(state);
+                       cmSystemTools::Error("CMake was not built with support "
+                                            "for --debugger-pipe");
+                       return false;
+#endif
+                     } },
+    CommandArgument{
+      "--debugger-dap-log", "No file specified for --debugger-dap-log",
+      CommandArgument::Values::One,
+      [](std::string const& value, cmake* state) -> bool {
+#ifdef CMake_ENABLE_DEBUGGER
+        std::string path = cmSystemTools::CollapseFullPath(value);
+        cmSystemTools::ConvertToUnixSlashes(path);
+        state->DebuggerDapLogFile = path;
+        return true;
+#else
+        static_cast<void>(value);
+        static_cast<void>(state);
+        cmSystemTools::Error(
+          "CMake was not built with support for --debugger-dap-log");
+        return false;
+#endif
+      } },
   };
 
 #if defined(CMAKE_HAVE_VS_GENERATORS)
@@ -1411,13 +1489,10 @@
   if (listPresets != ListPresets::None || !presetName.empty()) {
     cmCMakePresetsGraph presetsGraph;
     auto result = presetsGraph.ReadProjectPresets(this->GetHomeDirectory());
-    if (result != cmCMakePresetsGraph::ReadFileResult::READ_OK) {
+    if (result != true) {
       std::string errorMsg =
-        cmStrCat("Could not read presets from ", this->GetHomeDirectory(),
-                 ": ", cmCMakePresetsGraph::ResultToString(result));
-      if (!presetsGraph.errors.empty()) {
-        errorMsg = cmStrCat(errorMsg, "\nErrors:\n", presetsGraph.errors);
-      }
+        cmStrCat("Could not read presets from ", this->GetHomeDirectory(), ":",
+                 presetsGraph.parseState.GetErrorMessage());
       cmSystemTools::Error(errorMsg);
       return;
     }
@@ -1532,6 +1607,29 @@
     if (expandedPreset->DebugFind == true) {
       this->SetDebugFindOutput(true);
     }
+    if (expandedPreset->TraceMode &&
+        expandedPreset->TraceMode !=
+          cmCMakePresetsGraph::TraceEnableMode::Disable) {
+      this->SetTrace(true);
+      if (expandedPreset->TraceMode ==
+          cmCMakePresetsGraph::TraceEnableMode::Expand) {
+        this->SetTraceExpand(true);
+      }
+    }
+    if (expandedPreset->TraceFormat) {
+      this->SetTrace(true);
+      this->SetTraceFormat(*expandedPreset->TraceFormat);
+    }
+    if (!expandedPreset->TraceSource.empty()) {
+      this->SetTrace(true);
+      for (std::string const& filePaths : expandedPreset->TraceSource) {
+        this->AddTraceSource(filePaths);
+      }
+    }
+    if (!expandedPreset->TraceRedirect.empty()) {
+      this->SetTrace(true);
+      this->SetTraceFile(expandedPreset->TraceRedirect);
+    }
   }
 #endif
 }
@@ -1588,8 +1686,8 @@
 {
   using TracePair = std::pair<std::string, TraceFormat>;
   static const std::vector<TracePair> levels = {
-    { "human", TraceFormat::TRACE_HUMAN },
-    { "json-v1", TraceFormat::TRACE_JSON_V1 },
+    { "human", TraceFormat::Human },
+    { "json-v1", TraceFormat::JSONv1 },
   };
 
   const auto traceStrLowCase = cmSystemTools::LowerCase(traceStr);
@@ -1598,7 +1696,7 @@
                                [&traceStrLowCase](const TracePair& p) {
                                  return p.first == traceStrLowCase;
                                });
-  return (it != levels.cend()) ? it->second : TraceFormat::TRACE_UNDEFINED;
+  return (it != levels.cend()) ? it->second : TraceFormat::Undefined;
 }
 
 void cmake::SetTraceFile(const std::string& file)
@@ -1624,7 +1722,7 @@
   std::string msg;
 
   switch (this->GetTraceFormat()) {
-    case TraceFormat::TRACE_JSON_V1: {
+    case TraceFormat::JSONv1: {
 #ifndef CMAKE_BOOTSTRAP
       Json::Value val;
       Json::Value version;
@@ -1637,11 +1735,11 @@
 #endif
       break;
     }
-    case TraceFormat::TRACE_HUMAN:
+    case TraceFormat::Human:
       msg = "";
       break;
-    case TraceFormat::TRACE_UNDEFINED:
-      msg = "INTERNAL ERROR: Trace format is TRACE_UNDEFINED";
+    case TraceFormat::Undefined:
+      msg = "INTERNAL ERROR: Trace format is Undefined";
       break;
   }
 
@@ -2101,12 +2199,10 @@
     std::string cacheStart =
       cmStrCat(*this->State->GetInitializedCacheValue("CMAKE_HOME_DIRECTORY"),
                "/CMakeLists.txt");
-    std::string currentStart =
-      cmStrCat(this->GetHomeDirectory(), "/CMakeLists.txt");
-    if (!cmSystemTools::SameFile(cacheStart, currentStart)) {
+    if (!cmSystemTools::SameFile(cacheStart, srcList)) {
       std::string message =
-        cmStrCat("The source \"", currentStart,
-                 "\" does not match the source \"", cacheStart,
+        cmStrCat("The source \"", srcList, "\" does not match the source \"",
+                 cacheStart,
                  "\" used to generate cache.  Re-run cmake with a different "
                  "source directory.");
       cmSystemTools::Error(message);
@@ -2127,7 +2223,7 @@
 
 int cmake::HandleDeleteCacheVariables(const std::string& var)
 {
-  std::vector<std::string> argsSplit = cmExpandedList(var, true);
+  cmList argsSplit{ var, cmList::EmptyElements::Yes };
   // erase the property to avoid infinite recursion
   this->State->SetGlobalProperty("__CMAKE_DELETE_CACHE_CHANGE_VARS_", "");
   if (this->GetIsInTryCompile()) {
@@ -2170,7 +2266,7 @@
   this->LoadCache();
   // restore the changed compilers
   for (SaveCacheEntry const& i : saved) {
-    this->AddCacheEntry(i.key, i.value, i.help.c_str(), i.type);
+    this->AddCacheEntry(i.key, i.value, i.help, i.type);
   }
   cmSystemTools::Message(warning.str());
   // avoid reconfigure if there were errors
@@ -2334,16 +2430,16 @@
   cmValue genName = this->State->GetInitializedCacheValue("CMAKE_GENERATOR");
   if (genName) {
     if (!this->GlobalGenerator->MatchesGeneratorName(*genName)) {
-      std::string message =
-        cmStrCat("Error: generator : ", this->GlobalGenerator->GetName(),
-                 "\nDoes not match the generator used previously: ", *genName,
-                 "\nEither remove the CMakeCache.txt file and CMakeFiles "
-                 "directory or choose a different binary directory.");
+      std::string message = cmStrCat(
+        "Error: generator : ", this->GlobalGenerator->GetName(), '\n',
+        "Does not match the generator used previously: ", *genName, '\n',
+        "Either remove the CMakeCache.txt file and CMakeFiles "
+        "directory or choose a different binary directory.");
       cmSystemTools::Error(message);
       return -2;
     }
   }
-  if (!this->State->GetInitializedCacheValue("CMAKE_GENERATOR")) {
+  if (!genName) {
     this->AddCacheEntry("CMAKE_GENERATOR", this->GlobalGenerator->GetName(),
                         "Name of generator.", cmStateEnums::INTERNAL);
     this->AddCacheEntry(
@@ -2364,11 +2460,11 @@
   if (cmValue instance =
         this->State->GetInitializedCacheValue("CMAKE_GENERATOR_INSTANCE")) {
     if (this->GeneratorInstanceSet && this->GeneratorInstance != *instance) {
-      std::string message =
-        cmStrCat("Error: generator instance: ", this->GeneratorInstance,
-                 "\nDoes not match the instance used previously: ", *instance,
-                 "\nEither remove the CMakeCache.txt file and CMakeFiles "
-                 "directory or choose a different binary directory.");
+      std::string message = cmStrCat(
+        "Error: generator instance: ", this->GeneratorInstance, '\n',
+        "Does not match the instance used previously: ", *instance, '\n',
+        "Either remove the CMakeCache.txt file and CMakeFiles "
+        "directory or choose a different binary directory.");
       cmSystemTools::Error(message);
       return -2;
     }
@@ -2383,9 +2479,9 @@
     if (this->GeneratorPlatformSet &&
         this->GeneratorPlatform != *platformName) {
       std::string message = cmStrCat(
-        "Error: generator platform: ", this->GeneratorPlatform,
-        "\nDoes not match the platform used previously: ", *platformName,
-        "\nEither remove the CMakeCache.txt file and CMakeFiles "
+        "Error: generator platform: ", this->GeneratorPlatform, '\n',
+        "Does not match the platform used previously: ", *platformName, '\n',
+        "Either remove the CMakeCache.txt file and CMakeFiles "
         "directory or choose a different binary directory.");
       cmSystemTools::Error(message);
       return -2;
@@ -2399,9 +2495,9 @@
         this->State->GetInitializedCacheValue("CMAKE_GENERATOR_TOOLSET")) {
     if (this->GeneratorToolsetSet && this->GeneratorToolset != *tsName) {
       std::string message =
-        cmStrCat("Error: generator toolset: ", this->GeneratorToolset,
-                 "\nDoes not match the toolset used previously: ", *tsName,
-                 "\nEither remove the CMakeCache.txt file and CMakeFiles "
+        cmStrCat("Error: generator toolset: ", this->GeneratorToolset, '\n',
+                 "Does not match the toolset used previously: ", *tsName, '\n',
+                 "Either remove the CMakeCache.txt file and CMakeFiles "
                  "directory or choose a different binary directory.");
       cmSystemTools::Error(message);
       return -2;
@@ -2583,6 +2679,52 @@
   }
 }
 
+#ifdef CMake_ENABLE_DEBUGGER
+
+bool cmake::StartDebuggerIfEnabled()
+{
+  if (!this->GetDebuggerOn()) {
+    return true;
+  }
+
+  if (DebugAdapter == nullptr) {
+    if (this->GetDebuggerPipe().empty()) {
+      std::cerr
+        << "Error: --debugger-pipe must be set when debugging is enabled.\n";
+      return false;
+    }
+
+    try {
+      DebugAdapter = std::make_shared<cmDebugger::cmDebuggerAdapter>(
+        std::make_shared<cmDebugger::cmDebuggerPipeConnection>(
+          this->GetDebuggerPipe()),
+        this->GetDebuggerDapLogFile());
+    } catch (const std::runtime_error& error) {
+      std::cerr << "Error: Failed to create debugger adapter.\n";
+      std::cerr << error.what() << "\n";
+      return false;
+    }
+    Messenger->SetDebuggerAdapter(DebugAdapter);
+  }
+
+  return true;
+}
+
+void cmake::StopDebuggerIfNeeded(int exitCode)
+{
+  if (!this->GetDebuggerOn()) {
+    return;
+  }
+
+  // The debug adapter may have failed to start (e.g. invalid pipe path).
+  if (DebugAdapter != nullptr) {
+    DebugAdapter->ReportExitCode(exitCode);
+    DebugAdapter.reset();
+  }
+}
+
+#endif
+
 // handle a command line invocation
 int cmake::Run(const std::vector<std::string>& args, bool noconfigure)
 {
@@ -2672,6 +2814,12 @@
     return 0;
   }
 
+#ifdef CMake_ENABLE_DEBUGGER
+  if (!this->StartDebuggerIfEnabled()) {
+    return -1;
+  }
+#endif
+
   int ret = this->Configure();
   if (ret) {
 #if defined(CMAKE_HAVE_VS_GENERATORS)
@@ -2740,7 +2888,7 @@
 }
 
 void cmake::AddCacheEntry(const std::string& key, cmValue value,
-                          const char* helpString, int type)
+                          cmValue helpString, int type)
 {
   this->State->AddCacheEntry(key, value, helpString,
                              static_cast<cmStateEnums::CacheEntryType>(type));
@@ -3104,9 +3252,8 @@
   }
 
   // If any byproduct of makefile generation is missing we must re-run.
-  std::vector<std::string> products;
-  mf.GetDefExpandList("CMAKE_MAKEFILE_PRODUCTS", products);
-  for (std::string const& p : products) {
+  cmList products{ mf.GetDefinition("CMAKE_MAKEFILE_PRODUCTS") };
+  for (auto const& p : products) {
     if (!(cmSystemTools::FileExists(p) || cmSystemTools::FileIsSymlink(p))) {
       if (verbose) {
         cmSystemTools::Stdout(
@@ -3117,10 +3264,10 @@
   }
 
   // Get the set of dependencies and outputs.
-  std::vector<std::string> depends;
-  std::vector<std::string> outputs;
-  if (mf.GetDefExpandList("CMAKE_MAKEFILE_DEPENDS", depends)) {
-    mf.GetDefExpandList("CMAKE_MAKEFILE_OUTPUTS", outputs);
+  cmList depends{ mf.GetDefinition("CMAKE_MAKEFILE_DEPENDS") };
+  cmList outputs;
+  if (!depends.empty()) {
+    outputs.assign(mf.GetDefinition("CMAKE_MAKEFILE_OUTPUTS"));
   }
   if (depends.empty() || outputs.empty()) {
     // Not enough information was provided to do the test.  Just rerun.
@@ -3227,10 +3374,6 @@
 #endif
 }
 
-void cmake::SetProperty(const std::string& prop, const char* value)
-{
-  this->State->SetGlobalProperty(prop, value);
-}
 void cmake::SetProperty(const std::string& prop, cmValue value)
 {
   this->State->SetGlobalProperty(prop, value);
@@ -3396,19 +3539,18 @@
 
 std::vector<std::string> cmake::GetDebugConfigs()
 {
-  std::vector<std::string> configs;
+  cmList configs;
   if (cmValue config_list =
         this->State->GetGlobalProperty("DEBUG_CONFIGURATIONS")) {
     // Expand the specified list and convert to upper-case.
-    cmExpandList(*config_list, configs);
-    std::transform(configs.begin(), configs.end(), configs.begin(),
-                   cmSystemTools::UpperCase);
+    configs.assign(*config_list);
+    configs.transform(cmList::TransformAction::TOUPPER);
   }
   // If no configurations were specified, use a default list.
   if (configs.empty()) {
     configs.emplace_back("DEBUG");
   }
-  return configs;
+  return std::move(configs.data());
 }
 
 int cmake::Build(int jobs, std::string dir, std::vector<std::string> targets,
@@ -3426,10 +3568,10 @@
 
     cmCMakePresetsGraph settingsFile;
     auto result = settingsFile.ReadProjectPresets(this->GetHomeDirectory());
-    if (result != cmCMakePresetsGraph::ReadFileResult::READ_OK) {
+    if (result != true) {
       cmSystemTools::Error(
-        cmStrCat("Could not read presets from ", this->GetHomeDirectory(),
-                 ": ", cmCMakePresetsGraph::ResultToString(result)));
+        cmStrCat("Could not read presets from ", this->GetHomeDirectory(), ":",
+                 settingsFile.parseState.GetErrorMessage()));
       return 1;
     }
 
@@ -3590,7 +3732,6 @@
       return 1;
     }
   }
-  std::string output;
   std::string projName;
   cmValue cachedProjectName =
     this->State->GetCacheEntryValue("CMAKE_PROJECT_NAME");
@@ -3664,10 +3805,17 @@
   }
 
   this->GlobalGenerator->PrintBuildCommandAdvice(std::cerr, jobs);
-  return this->GlobalGenerator->Build(
-    jobs, "", dir, projName, targets, output, "", config, buildOptions,
+  std::stringstream ostr;
+  // `cmGlobalGenerator::Build` logs metadata about what directory and commands
+  // are being executed to the `output` parameter. If CMake is verbose, print
+  // this out.
+  std::ostream& verbose_ostr = verbose ? std::cout : ostr;
+  int buildresult = this->GlobalGenerator->Build(
+    jobs, "", dir, projName, targets, verbose_ostr, "", config, buildOptions,
     verbose, cmDuration::zero(), cmSystemTools::OUTPUT_PASSTHROUGH,
     nativeOptions);
+
+  return buildresult;
 }
 
 bool cmake::Open(const std::string& dir, bool dryRun)
@@ -3782,10 +3930,10 @@
 
   cmCMakePresetsGraph settingsFile;
   auto result = settingsFile.ReadProjectPresets(this->GetHomeDirectory());
-  if (result != cmCMakePresetsGraph::ReadFileResult::READ_OK) {
-    cmSystemTools::Error(
-      cmStrCat("Could not read presets from ", this->GetHomeDirectory(), ": ",
-               cmCMakePresetsGraph::ResultToString(result)));
+  if (result != true) {
+    cmSystemTools::Error(cmStrCat("Could not read presets from ",
+                                  this->GetHomeDirectory(), ":",
+                                  settingsFile.parseState.GetErrorMessage()));
     return 1;
   }
 
diff --git a/Source/cmake.h b/Source/cmake.h
index d1f388a..2f5ea24 100644
--- a/Source/cmake.h
+++ b/Source/cmake.h
@@ -37,6 +37,13 @@
 #endif
 
 class cmConfigureLog;
+
+#ifdef CMake_ENABLE_DEBUGGER
+namespace cmDebugger {
+class cmDebuggerAdapter;
+}
+#endif
+
 class cmExternalMakefileProjectGeneratorFactory;
 class cmFileAPI;
 class cmFileTimeCache;
@@ -118,13 +125,7 @@
     FIND_PACKAGE_MODE
   };
 
-  /** \brief Define supported trace formats **/
-  enum TraceFormat
-  {
-    TRACE_UNDEFINED,
-    TRACE_HUMAN,
-    TRACE_JSON_V1,
-  };
+  using TraceFormat = cmTraceEnums::TraceOutputFormat;
 
   struct GeneratorInfo
   {
@@ -334,20 +335,18 @@
    */
   cmValue GetCacheDefinition(const std::string&) const;
   //! Add an entry into the cache
-  void AddCacheEntry(const std::string& key, const char* value,
-                     const char* helpString, int type)
-  {
-    this->AddCacheEntry(key,
-                        value ? cmValue(std::string(value)) : cmValue(nullptr),
-                        helpString, type);
-  }
   void AddCacheEntry(const std::string& key, const std::string& value,
-                     const char* helpString, int type)
+                     const std::string& helpString, int type)
   {
-    this->AddCacheEntry(key, cmValue(value), helpString, type);
+    this->AddCacheEntry(key, cmValue{ value }, cmValue{ helpString }, type);
   }
   void AddCacheEntry(const std::string& key, cmValue value,
-                     const char* helpString, int type);
+                     const std::string& helpString, int type)
+  {
+    this->AddCacheEntry(key, value, cmValue{ helpString }, type);
+  }
+  void AddCacheEntry(const std::string& key, cmValue value, cmValue helpString,
+                     int type);
 
   bool DoWriteGlobVerifyTarget() const;
   std::string const& GetGlobVerifyScript() const;
@@ -410,8 +409,11 @@
   std::vector<cmDocumentationEntry> GetGeneratorsDocumentation();
 
   //! Set/Get a property of this target file
-  void SetProperty(const std::string& prop, const char* value);
   void SetProperty(const std::string& prop, cmValue value);
+  void SetProperty(const std::string& prop, std::nullptr_t)
+  {
+    this->SetProperty(prop, cmValue{ nullptr });
+  }
   void SetProperty(const std::string& prop, const std::string& value)
   {
     this->SetProperty(prop, cmValue(value));
@@ -665,6 +667,23 @@
   }
 #endif
 
+#ifdef CMake_ENABLE_DEBUGGER
+  bool GetDebuggerOn() const { return this->DebuggerOn; }
+  std::string GetDebuggerPipe() const { return this->DebuggerPipe; }
+  std::string GetDebuggerDapLogFile() const
+  {
+    return this->DebuggerDapLogFile;
+  }
+  void SetDebuggerOn(bool b) { this->DebuggerOn = b; }
+  bool StartDebuggerIfEnabled();
+  void StopDebuggerIfNeeded(int exitCode);
+  std::shared_ptr<cmDebugger::cmDebuggerAdapter> GetDebugAdapter()
+    const noexcept
+  {
+    return this->DebugAdapter;
+  }
+#endif
+
 protected:
   void RunCheckForUnusedVariables();
   int HandleDeleteCacheVariables(const std::string& var);
@@ -719,7 +738,7 @@
   bool DebugFindOutput = false;
   bool Trace = false;
   bool TraceExpand = false;
-  TraceFormat TraceFormatVar = TRACE_HUMAN;
+  TraceFormat TraceFormatVar = TraceFormat::Human;
   cmGeneratedFileStream TraceFile;
   cmake* TraceRedirect = nullptr;
 #ifndef CMAKE_BOOTSTRAP
@@ -805,6 +824,13 @@
   std::unique_ptr<cmMakefileProfilingData> ProfilingOutput;
 #endif
 
+#ifdef CMake_ENABLE_DEBUGGER
+  std::shared_ptr<cmDebugger::cmDebuggerAdapter> DebugAdapter;
+  bool DebuggerOn = false;
+  std::string DebuggerPipe;
+  std::string DebuggerDapLogFile;
+#endif
+
 public:
   static cmDocumentationEntry CMAKE_STANDARD_OPTIONS_TABLE[18];
 };
diff --git a/Source/cmakemain.cxx b/Source/cmakemain.cxx
index f4e602b..ced83dc 100644
--- a/Source/cmakemain.cxx
+++ b/Source/cmakemain.cxx
@@ -26,6 +26,7 @@
 #include "cmConsoleBuf.h"
 #include "cmDocumentationEntry.h"
 #include "cmGlobalGenerator.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmMessageMetadata.h"
 #include "cmState.h"
@@ -391,8 +392,14 @@
   // Always return a non-negative value.  Windows tools do not always
   // interpret negative return values as errors.
   if (res != 0) {
+#ifdef CMake_ENABLE_DEBUGGER
+    cm.StopDebuggerIfNeeded(1);
+#endif
     return 1;
   }
+#ifdef CMake_ENABLE_DEBUGGER
+  cm.StopDebuggerIfNeeded(0);
+#endif
   return 0;
 }
 
@@ -457,7 +464,7 @@
   };
   auto targetLambda = [&](std::string const& value) -> bool {
     if (!value.empty()) {
-      std::vector<std::string> values = cmExpandedList(value);
+      cmList values{ value };
       for (auto const& v : values) {
         targets.emplace_back(v);
         if (v == "clean") {
diff --git a/Source/cmcmd.cxx b/Source/cmcmd.cxx
index 21d0cc9..9929e85 100644
--- a/Source/cmcmd.cxx
+++ b/Source/cmcmd.cxx
@@ -3,6 +3,7 @@
 #include "cmcmd.h"
 
 #include <functional>
+#include <iterator>
 
 #include <cm/optional>
 #include <cmext/algorithm>
@@ -14,6 +15,7 @@
 #include "cmConsoleBuf.h"
 #include "cmDuration.h"
 #include "cmGlobalGenerator.h"
+#include "cmList.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
 #include "cmQtAutoMocUic.h"
@@ -50,10 +52,10 @@
 #endif
 
 #include <array>
+#include <chrono>
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
-#include <ctime>
 #include <iostream>
 #include <memory>
 #include <sstream>
@@ -342,10 +344,9 @@
 {
   // Construct the iwyu command line by taking what was given
   // and adding all the arguments we give to the compiler.
-  std::vector<std::string> iwyu_cmd = cmExpandedList(runCmd, true);
+  cmList iwyu_cmd{ runCmd, cmList::EmptyElements::Yes };
   cm::append(iwyu_cmd, orig_cmd.begin() + 1, orig_cmd.end());
   // Run the iwyu command line.  Capture its stderr and hide its stdout.
-  // Ignore its return code because the tool always returns non-zero.
   std::string stdErr;
   int ret;
   if (!cmSystemTools::RunSingleCommand(iwyu_cmd, nullptr, &stdErr, &ret,
@@ -359,14 +360,21 @@
     std::cerr << "Warning: include-what-you-use reported diagnostics:\n"
               << stdErr << "\n";
   }
-  // always return 0 we don't want to break the compile
-  return 0;
+  // Older versions of iwyu always returned a non-zero exit code,
+  // so ignore it unless the user has enabled errors.
+  auto has_error_opt = std::find_if(
+    iwyu_cmd.cbegin(), iwyu_cmd.cend(),
+    [](std::string const& opt) { return cmHasLiteralPrefix(opt, "--error"); });
+  bool errors_enabled = has_error_opt != iwyu_cmd.cend() &&
+    has_error_opt != iwyu_cmd.cbegin() &&
+    *std::prev(has_error_opt) == "-Xiwyu";
+  return errors_enabled ? ret : 0;
 }
 
 int HandleTidy(const std::string& runCmd, const std::string& sourceFile,
                const std::vector<std::string>& orig_cmd)
 {
-  std::vector<std::string> tidy_cmd = cmExpandedList(runCmd, true);
+  cmList tidy_cmd{ runCmd, cmList::EmptyElements::Yes };
   tidy_cmd.push_back(sourceFile);
 
   for (auto const& arg : tidy_cmd) {
@@ -416,7 +424,7 @@
 {
   // Construct the ldd -r -u (link what you use lwyu) command line
   // ldd -u -r lwuy target
-  std::vector<std::string> lwyu_cmd = cmExpandedList(runCmd, true);
+  cmList lwyu_cmd{ runCmd, cmList::EmptyElements::Yes };
   lwyu_cmd.push_back(sourceFile);
 
   // Run the lwyu check command line,  currently ldd is expected.
@@ -444,7 +452,7 @@
                   const std::vector<std::string>&)
 {
   // Construct the cpplint command line.
-  std::vector<std::string> cpplint_cmd = cmExpandedList(runCmd, true);
+  cmList cpplint_cmd{ runCmd, cmList::EmptyElements::Yes };
   cpplint_cmd.push_back(sourceFile);
 
   // Run the cpplint command line.  Capture its output.
@@ -471,7 +479,7 @@
                    const std::vector<std::string>& orig_cmd)
 {
   // Construct the cpplint command line.
-  std::vector<std::string> cppcheck_cmd = cmExpandedList(runCmd, true);
+  cmList cppcheck_cmd{ runCmd, cmList::EmptyElements::Yes };
   // extract all the -D, -U, and -I options from the compile line
   for (auto const& opt : orig_cmd) {
     if (opt.size() > 2) {
@@ -551,8 +559,8 @@
 int cmcmd::HandleCoCompileCommands(std::vector<std::string> const& args)
 {
   std::vector<CoCompileJob> jobs;
-  std::string sourceFile;             // store --source=
-  std::vector<std::string> launchers; // store --launcher=
+  std::string sourceFile; // store --source=
+  cmList launchers;       // store --launcher=
 
   // Default is to run the original command found after -- if the option
   // does not need to do that, it should be specified here, currently only
@@ -585,7 +593,7 @@
         if (cmHasLiteralPrefix(arg, "--source=")) {
           sourceFile = arg.substr(9);
         } else if (cmHasLiteralPrefix(arg, "--launcher=")) {
-          cmExpandList(arg.substr(11), launchers, true);
+          launchers.append(arg.substr(11), cmList::EmptyElements::Yes);
         } else {
           // if it was not a co-compiler or --source/--launcher then error
           std::cerr << "__run_co_compile given unknown argument: " << arg
@@ -1104,27 +1112,13 @@
     if (args[1] == "time" && args.size() > 2) {
       std::vector<std::string> command(args.begin() + 2, args.end());
 
-      clock_t clock_start;
-      clock_t clock_finish;
-      time_t time_start;
-      time_t time_finish;
-
-      time(&time_start);
-      clock_start = clock();
       int ret = 0;
+      auto time_start = std::chrono::steady_clock::now();
       cmSystemTools::RunSingleCommand(command, nullptr, nullptr, &ret);
+      auto time_finish = std::chrono::steady_clock::now();
 
-      clock_finish = clock();
-      time(&time_finish);
-
-      double clocks_per_sec = static_cast<double>(CLOCKS_PER_SEC);
-      std::cout << "Elapsed time: "
-                << static_cast<long>(time_finish - time_start) << " s. (time)"
-                << ", "
-                << static_cast<double>(clock_finish - clock_start) /
-          clocks_per_sec
-                << " s. (clock)"
-                << "\n";
+      std::chrono::duration<double> time_elapsed = time_finish - time_start;
+      std::cout << "Elapsed time (seconds): " << time_elapsed.count() << "\n";
       return ret;
     }
 
@@ -2343,6 +2337,9 @@
         cmSystemTools::Strucmp(arg->c_str(), "/INCREMENTAL") == 0 ||
         cmSystemTools::Strucmp(arg->c_str(), "-INCREMENTAL") == 0) {
       this->Incremental = true;
+    } else if (cmSystemTools::Strucmp(arg->c_str(), "/INCREMENTAL:NO") == 0 ||
+               cmSystemTools::Strucmp(arg->c_str(), "-INCREMENTAL:NO") == 0) {
+      this->Incremental = false;
     } else if (cmSystemTools::Strucmp(arg->c_str(), "/MANIFEST:NO") == 0 ||
                cmSystemTools::Strucmp(arg->c_str(), "-MANIFEST:NO") == 0) {
       this->LinkGeneratesManifest = false;
@@ -2367,17 +2364,11 @@
     // pass it to the link command.
     this->ManifestFileRC = intDir + "/manifest.rc";
     this->ManifestFileRes = intDir + "/manifest.res";
-  } else if (this->UserManifests.empty()) {
-    // Prior to support for user-specified manifests CMake placed the
-    // linker-generated manifest next to the binary (as if it were not to be
-    // embedded) when not linking incrementally.  Preserve this behavior.
-    this->ManifestFile = this->TargetFile + ".manifest";
-    this->LinkerManifestFile = this->ManifestFile;
-  }
 
-  if (this->LinkGeneratesManifest) {
-    this->LinkCommand.emplace_back("/MANIFEST");
-    this->LinkCommand.push_back("/MANIFESTFILE:" + this->LinkerManifestFile);
+    if (this->LinkGeneratesManifest) {
+      this->LinkCommand.emplace_back("/MANIFEST");
+      this->LinkCommand.push_back("/MANIFESTFILE:" + this->LinkerManifestFile);
+    }
   }
 
   return true;
@@ -2511,20 +2502,23 @@
 
 int cmVSLink::LinkNonIncremental()
 {
-  // Run the link command (possibly generates intermediate manifest).
+  // Sort out any manifests.
+  if (this->LinkGeneratesManifest || !this->UserManifests.empty()) {
+    std::string opt =
+      std::string("/MANIFEST:EMBED,ID=") + (this->Type == 1 ? '1' : '2');
+    this->LinkCommand.emplace_back(opt);
+
+    for (auto const& m : this->UserManifests) {
+      opt = "/MANIFESTINPUT:" + m;
+      this->LinkCommand.emplace_back(opt);
+    }
+  }
+
+  // Run the link command.
   if (!RunCommand("LINK", this->LinkCommand, this->Verbose, FORMAT_DECIMAL)) {
     return -1;
   }
-
-  // If we have no manifest files we are done.
-  if (!this->LinkGeneratesManifest && this->UserManifests.empty()) {
-    return 0;
-  }
-
-  // Run the manifest tool to embed the final manifest in the binary.
-  std::string mtOut =
-    "/outputresource:" + this->TargetFile + (this->Type == 1 ? ";#1" : ";#2");
-  return this->RunMT(mtOut, false);
+  return 0;
 }
 
 int cmVSLink::RunMT(std::string const& out, bool notify)
diff --git a/Source/kwsys/CMakeLists.txt b/Source/kwsys/CMakeLists.txt
index af02f7f..2b8eedd 100644
--- a/Source/kwsys/CMakeLists.txt
+++ b/Source/kwsys/CMakeLists.txt
@@ -201,11 +201,7 @@
 
 # Enable testing if building standalone.
 if(KWSYS_STANDALONE)
-  include(Dart)
-  mark_as_advanced(BUILD_TESTING DART_ROOT TCL_TCLSH)
-  if(BUILD_TESTING)
-    enable_testing()
-  endif()
+  include(CTest)
 endif()
 
 # Choose default shared/static build if not specified.
@@ -630,8 +626,8 @@
 # Build a list of classes and headers we need to implement the
 # selected components.  Initialize with required components.
 set(KWSYS_CLASSES)
-set(KWSYS_H_FILES Configure SharedForward)
-set(KWSYS_HXX_FILES Configure String)
+set(KWSYS_H_FILES Configure)
+set(KWSYS_HXX_FILES Configure)
 
 # Add selected C++ classes.
 set(cppclasses
@@ -1038,6 +1034,10 @@
     set_property(TARGET ${KWSYS_NAMESPACE}TestsCxx PROPERTY CXX_INCLUDE_WHAT_YOU_USE "")
     set_property(TARGET ${KWSYS_NAMESPACE}TestsCxx PROPERTY LABELS ${KWSYS_LABELS_EXE})
     target_link_libraries(${KWSYS_NAMESPACE}TestsCxx ${KWSYS_TARGET_LINK})
+    get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+    if(_isMultiConfig)
+      set_property(TARGET ${KWSYS_NAMESPACE}TestsCxx APPEND PROPERTY COMPILE_DEFINITIONS BUILD_CONFIG="$<CONFIG>")
+    endif()
 
     set(TEST_SYSTEMTOOLS_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
     set(TEST_SYSTEMTOOLS_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}")
@@ -1118,16 +1118,6 @@
     endif()
     set_property(SOURCE testProcess.c PROPERTY COMPILE_FLAGS "${testProcess_COMPILE_FLAGS}")
 
-    # Test SharedForward
-    configure_file(${PROJECT_SOURCE_DIR}/testSharedForward.c.in
-                   ${PROJECT_BINARY_DIR}/testSharedForward.c @ONLY IMMEDIATE)
-    add_executable(${KWSYS_NAMESPACE}TestSharedForward
-                   ${PROJECT_BINARY_DIR}/testSharedForward.c)
-    set_property(TARGET ${KWSYS_NAMESPACE}TestSharedForward PROPERTY LABELS ${KWSYS_LABELS_EXE})
-    add_dependencies(${KWSYS_NAMESPACE}TestSharedForward ${KWSYS_TARGET_C_LINK})
-    add_test(kwsys.testSharedForward ${EXEC_DIR}/${KWSYS_NAMESPACE}TestSharedForward 1)
-    set_property(TEST kwsys.testSharedForward PROPERTY LABELS ${KWSYS_LABELS_TEST})
-
     # Configure some test properties.
     if(KWSYS_STANDALONE)
       # We expect test to fail
diff --git a/Source/kwsys/CommandLineArguments.cxx b/Source/kwsys/CommandLineArguments.cxx
index ccd5f6d..50171dd 100644
--- a/Source/kwsys/CommandLineArguments.cxx
+++ b/Source/kwsys/CommandLineArguments.cxx
@@ -4,20 +4,19 @@
 #include KWSYS_HEADER(CommandLineArguments.hxx)
 
 #include KWSYS_HEADER(Configure.hxx)
-#include KWSYS_HEADER(String.hxx)
 
 // Work-around CMake dependency scanning limitation.  This must
 // duplicate the above list of headers.
 #if 0
 #  include "CommandLineArguments.hxx.in"
 #  include "Configure.hxx.in"
-#  include "String.hxx.in"
 #endif
 
 #include <iostream>
 #include <map>
 #include <set>
 #include <sstream>
+#include <string>
 #include <vector>
 
 #include <cstdio>
@@ -52,14 +51,14 @@
   const char* Help;
 };
 
-class CommandLineArgumentsVectorOfStrings : public std::vector<kwsys::String>
+class CommandLineArgumentsVectorOfStrings : public std::vector<std::string>
 {
 };
-class CommandLineArgumentsSetOfStrings : public std::set<kwsys::String>
+class CommandLineArgumentsSetOfStrings : public std::set<std::string>
 {
 };
 class CommandLineArgumentsMapOfStrucs
-  : public std::map<kwsys::String, CommandLineArgumentsCallbackStructure>
+  : public std::map<std::string, CommandLineArgumentsCallbackStructure>
 {
 };
 
@@ -70,7 +69,7 @@
 
   using VectorOfStrings = CommandLineArgumentsVectorOfStrings;
   using CallbacksMap = CommandLineArgumentsMapOfStrucs;
-  using String = kwsys::String;
+  using String = std::string;
   using SetOfStrings = CommandLineArgumentsSetOfStrings;
 
   VectorOfStrings Argv;
@@ -306,7 +305,7 @@
 
   // Copy everything after the LastArgument, since that was not parsed.
   for (cc = 0; cc < this->Internals->UnusedArguments.size(); cc++) {
-    kwsys::String& str = this->Internals->UnusedArguments[cc];
+    std::string& str = this->Internals->UnusedArguments[cc];
     args[cnt] = new char[str.size() + 1];
     strcpy(args[cnt], str.c_str());
     cnt++;
diff --git a/Source/kwsys/RegularExpression.cxx b/Source/kwsys/RegularExpression.cxx
index f2f5143..b51e16d 100644
--- a/Source/kwsys/RegularExpression.cxx
+++ b/Source/kwsys/RegularExpression.cxx
@@ -378,6 +378,10 @@
     return false;
   }
 
+#ifdef __clang_analyzer__ /* Convince it that the program is initialized.  */
+  memset(this->program, 0, comp.regsize);
+#endif
+
   // Second pass: emit code.
   comp.regparse = exp;
   comp.regnpar = 1;
diff --git a/Source/kwsys/SharedForward.h.in b/Source/kwsys/SharedForward.h.in
deleted file mode 100644
index d6ae75c..0000000
--- a/Source/kwsys/SharedForward.h.in
+++ /dev/null
@@ -1,873 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-   file Copyright.txt or https://cmake.org/licensing#kwsys for details.  */
-#ifndef @KWSYS_NAMESPACE@_SharedForward_h
-#  define @KWSYS_NAMESPACE@_SharedForward_h
-
-/*
-  This header is used to create a forwarding executable sets up the
-  shared library search path and replaces itself with a real
-  executable.  This is useful when creating installations on UNIX with
-  shared libraries that will run from any install directory.  Typical
-  usage:
-
-  #if defined(CMAKE_INTDIR)
-  # define CONFIG_DIR_PRE CMAKE_INTDIR "/"
-  # define CONFIG_DIR_POST "/" CMAKE_INTDIR
-  #else
-  # define CONFIG_DIR_PRE ""
-  # define CONFIG_DIR_POST ""
-  #endif
-  #define @KWSYS_NAMESPACE@_SHARED_FORWARD_DIR_BUILD "/path/to/foo-build/bin"
-  #define @KWSYS_NAMESPACE@_SHARED_FORWARD_PATH_BUILD "." CONFIG_DIR_POST
-  #define @KWSYS_NAMESPACE@_SHARED_FORWARD_PATH_INSTALL "../lib/foo-1.2"
-  #define @KWSYS_NAMESPACE@_SHARED_FORWARD_EXE_BUILD CONFIG_DIR_PRE "foo-real"
-  #define @KWSYS_NAMESPACE@_SHARED_FORWARD_EXE_INSTALL
-  "../lib/foo-1.2/foo-real"
-  #define @KWSYS_NAMESPACE@_SHARED_FORWARD_OPTION_COMMAND "--command"
-  #define @KWSYS_NAMESPACE@_SHARED_FORWARD_OPTION_PRINT "--print"
-  #define @KWSYS_NAMESPACE@_SHARED_FORWARD_OPTION_LDD "--ldd"
-  #if defined(CMAKE_INTDIR)
-  # define @KWSYS_NAMESPACE@_SHARED_FORWARD_CONFIG_NAME CMAKE_INTDIR
-  #endif
-  #include <@KWSYS_NAMESPACE@/SharedForward.h>
-  int main(int argc, char** argv)
-  {
-    return @KWSYS_NAMESPACE@_shared_forward_to_real(argc, argv);
-  }
-
-  Specify search and executable paths relative to the forwarding
-  executable location or as full paths.  Include no trailing slash.
-  In the case of a multi-configuration build, when CMAKE_INTDIR is
-  defined, the DIR_BUILD setting should point at the directory above
-  the executable (the one containing the per-configuration
-  subdirectory specified by CMAKE_INTDIR).  Then PATH_BUILD entries
-  and EXE_BUILD should be specified relative to this location and use
-  CMAKE_INTDIR as necessary.  In the above example imagine appending
-  the PATH_BUILD or EXE_BUILD setting to the DIR_BUILD setting.  The
-  result should form a valid path with per-configuration subdirectory.
-
-  Additional paths may be specified in the PATH_BUILD and PATH_INSTALL
-  variables by using comma-separated strings.    For example:
-
-  #define @KWSYS_NAMESPACE@_SHARED_FORWARD_PATH_BUILD \
-          "." CONFIG_DIR_POST, "/path/to/bar-build" CONFIG_DIR_POST
-  #define @KWSYS_NAMESPACE@_SHARED_FORWARD_PATH_INSTALL \
-          "../lib/foo-1.2", "../lib/bar-4.5"
-
-  See the comments below for specific explanations of each macro.
-*/
-
-/* Disable -Wcast-qual warnings since they are too hard to fix in a
-   cross-platform way.  */
-#  if defined(__clang__) && defined(__has_warning)
-#    if __has_warning("-Wcast-qual")
-#      pragma clang diagnostic push
-#      pragma clang diagnostic ignored "-Wcast-qual"
-#    endif
-#  endif
-
-/* Full path to the directory in which this executable is built.  Do
-   not include a trailing slash.  */
-#  if !defined(@KWSYS_NAMESPACE@_SHARED_FORWARD_DIR_BUILD)
-#    error "Must define @KWSYS_NAMESPACE@_SHARED_FORWARD_DIR_BUILD"
-#  endif
-#  if !defined(KWSYS_SHARED_FORWARD_DIR_BUILD)
-#    define KWSYS_SHARED_FORWARD_DIR_BUILD                                    \
-      @KWSYS_NAMESPACE@_SHARED_FORWARD_DIR_BUILD
-#  endif
-
-/* Library search path for build tree.  */
-#  if !defined(@KWSYS_NAMESPACE@_SHARED_FORWARD_PATH_BUILD)
-#    error "Must define @KWSYS_NAMESPACE@_SHARED_FORWARD_PATH_BUILD"
-#  endif
-#  if !defined(KWSYS_SHARED_FORWARD_PATH_BUILD)
-#    define KWSYS_SHARED_FORWARD_PATH_BUILD                                   \
-      @KWSYS_NAMESPACE@_SHARED_FORWARD_PATH_BUILD
-#  endif
-
-/* Library search path for install tree.  */
-#  if !defined(@KWSYS_NAMESPACE@_SHARED_FORWARD_PATH_INSTALL)
-#    error "Must define @KWSYS_NAMESPACE@_SHARED_FORWARD_PATH_INSTALL"
-#  endif
-#  if !defined(KWSYS_SHARED_FORWARD_PATH_INSTALL)
-#    define KWSYS_SHARED_FORWARD_PATH_INSTALL                                 \
-      @KWSYS_NAMESPACE@_SHARED_FORWARD_PATH_INSTALL
-#  endif
-
-/* The real executable to which to forward in the build tree.  */
-#  if !defined(@KWSYS_NAMESPACE@_SHARED_FORWARD_EXE_BUILD)
-#    error "Must define @KWSYS_NAMESPACE@_SHARED_FORWARD_EXE_BUILD"
-#  endif
-#  if !defined(KWSYS_SHARED_FORWARD_EXE_BUILD)
-#    define KWSYS_SHARED_FORWARD_EXE_BUILD                                    \
-      @KWSYS_NAMESPACE@_SHARED_FORWARD_EXE_BUILD
-#  endif
-
-/* The real executable to which to forward in the install tree.  */
-#  if !defined(@KWSYS_NAMESPACE@_SHARED_FORWARD_EXE_INSTALL)
-#    error "Must define @KWSYS_NAMESPACE@_SHARED_FORWARD_EXE_INSTALL"
-#  endif
-#  if !defined(KWSYS_SHARED_FORWARD_EXE_INSTALL)
-#    define KWSYS_SHARED_FORWARD_EXE_INSTALL                                  \
-      @KWSYS_NAMESPACE@_SHARED_FORWARD_EXE_INSTALL
-#  endif
-
-/* The configuration name with which this executable was built (Debug/Release).
- */
-#  if defined(@KWSYS_NAMESPACE@_SHARED_FORWARD_CONFIG_NAME)
-#    define KWSYS_SHARED_FORWARD_CONFIG_NAME                                  \
-      @KWSYS_NAMESPACE@_SHARED_FORWARD_CONFIG_NAME
-#  else
-#    undef KWSYS_SHARED_FORWARD_CONFIG_NAME
-#  endif
-
-/* Create command line option to replace executable.  */
-#  if defined(@KWSYS_NAMESPACE@_SHARED_FORWARD_OPTION_COMMAND)
-#    if !defined(KWSYS_SHARED_FORWARD_OPTION_COMMAND)
-#      define KWSYS_SHARED_FORWARD_OPTION_COMMAND                             \
-        @KWSYS_NAMESPACE@_SHARED_FORWARD_OPTION_COMMAND
-#    endif
-#  else
-#    undef KWSYS_SHARED_FORWARD_OPTION_COMMAND
-#  endif
-
-/* Create command line option to print environment setting and exit.  */
-#  if defined(@KWSYS_NAMESPACE@_SHARED_FORWARD_OPTION_PRINT)
-#    if !defined(KWSYS_SHARED_FORWARD_OPTION_PRINT)
-#      define KWSYS_SHARED_FORWARD_OPTION_PRINT                               \
-        @KWSYS_NAMESPACE@_SHARED_FORWARD_OPTION_PRINT
-#    endif
-#  else
-#    undef KWSYS_SHARED_FORWARD_OPTION_PRINT
-#  endif
-
-/* Create command line option to run ldd or equivalent.  */
-#  if defined(@KWSYS_NAMESPACE@_SHARED_FORWARD_OPTION_LDD)
-#    if !defined(KWSYS_SHARED_FORWARD_OPTION_LDD)
-#      define KWSYS_SHARED_FORWARD_OPTION_LDD                                 \
-        @KWSYS_NAMESPACE@_SHARED_FORWARD_OPTION_LDD
-#    endif
-#  else
-#    undef KWSYS_SHARED_FORWARD_OPTION_LDD
-#  endif
-
-/* Include needed system headers.  */
-
-#  include <errno.h>
-#  include <limits.h>
-#  include <stddef.h> /* size_t */
-#  include <stdio.h>
-#  include <stdlib.h>
-#  include <string.h>
-
-#  if defined(_WIN32) && !defined(__CYGWIN__)
-#    include <windows.h>
-
-#    include <io.h>
-#    include <process.h>
-#    define KWSYS_SHARED_FORWARD_ESCAPE_ARGV /* re-escape argv for execvp */
-#  else
-#    include <sys/stat.h>
-#    include <unistd.h>
-#  endif
-
-/* Configuration for this platform.  */
-
-/* The path separator for this platform.  */
-#  if defined(_WIN32) && !defined(__CYGWIN__)
-#    define KWSYS_SHARED_FORWARD_PATH_SEP ';'
-#    define KWSYS_SHARED_FORWARD_PATH_SLASH '\\'
-#  else
-#    define KWSYS_SHARED_FORWARD_PATH_SEP ':'
-#    define KWSYS_SHARED_FORWARD_PATH_SLASH '/'
-#  endif
-static const char kwsys_shared_forward_path_sep[2] = {
-  KWSYS_SHARED_FORWARD_PATH_SEP, 0
-};
-static const char kwsys_shared_forward_path_slash[2] = {
-  KWSYS_SHARED_FORWARD_PATH_SLASH, 0
-};
-
-/* The maximum length of a file name.  */
-#  if defined(PATH_MAX)
-#    define KWSYS_SHARED_FORWARD_MAXPATH PATH_MAX
-#  elif defined(MAXPATHLEN)
-#    define KWSYS_SHARED_FORWARD_MAXPATH MAXPATHLEN
-#  else
-#    define KWSYS_SHARED_FORWARD_MAXPATH 16384
-#  endif
-
-/* Select the environment variable holding the shared library runtime
-   search path for this platform and build configuration.  Also select
-   ldd command equivalent.  */
-
-/* Linux */
-#  if defined(__linux)
-#    define KWSYS_SHARED_FORWARD_LDD "ldd"
-#    define KWSYS_SHARED_FORWARD_LDD_N 1
-#    define KWSYS_SHARED_FORWARD_LDPATH "LD_LIBRARY_PATH"
-
-/* FreeBSD */
-#  elif defined(__FreeBSD__)
-#    define KWSYS_SHARED_FORWARD_LDD "ldd"
-#    define KWSYS_SHARED_FORWARD_LDD_N 1
-#    define KWSYS_SHARED_FORWARD_LDPATH "LD_LIBRARY_PATH"
-
-/* OpenBSD */
-#  elif defined(__OpenBSD__)
-#    define KWSYS_SHARED_FORWARD_LDD "ldd"
-#    define KWSYS_SHARED_FORWARD_LDD_N 1
-#    define KWSYS_SHARED_FORWARD_LDPATH "LD_LIBRARY_PATH"
-
-/* OS X */
-#  elif defined(__APPLE__)
-#    define KWSYS_SHARED_FORWARD_LDD "otool", "-L"
-#    define KWSYS_SHARED_FORWARD_LDD_N 2
-#    define KWSYS_SHARED_FORWARD_LDPATH "DYLD_LIBRARY_PATH"
-
-/* AIX */
-#  elif defined(_AIX)
-#    define KWSYS_SHARED_FORWARD_LDD "dump", "-H"
-#    define KWSYS_SHARED_FORWARD_LDD_N 2
-#    define KWSYS_SHARED_FORWARD_LDPATH "LIBPATH"
-
-/* SUN */
-#  elif defined(__sun)
-#    define KWSYS_SHARED_FORWARD_LDD "ldd"
-#    define KWSYS_SHARED_FORWARD_LDD_N 1
-#    include <sys/isa_defs.h>
-#    if defined(_ILP32)
-#      define KWSYS_SHARED_FORWARD_LDPATH "LD_LIBRARY_PATH"
-#    elif defined(_LP64)
-#      define KWSYS_SHARED_FORWARD_LDPATH "LD_LIBRARY_PATH_64"
-#    endif
-
-/* HP-UX */
-#  elif defined(__hpux)
-#    define KWSYS_SHARED_FORWARD_LDD "chatr"
-#    define KWSYS_SHARED_FORWARD_LDD_N 1
-#    if defined(__LP64__)
-#      define KWSYS_SHARED_FORWARD_LDPATH "LD_LIBRARY_PATH"
-#    else
-#      define KWSYS_SHARED_FORWARD_LDPATH "SHLIB_PATH"
-#    endif
-
-/* SGI MIPS */
-#  elif defined(__sgi) && defined(_MIPS_SIM)
-#    define KWSYS_SHARED_FORWARD_LDD "ldd"
-#    define KWSYS_SHARED_FORWARD_LDD_N 1
-#    if _MIPS_SIM == _ABIO32
-#      define KWSYS_SHARED_FORWARD_LDPATH "LD_LIBRARY_PATH"
-#    elif _MIPS_SIM == _ABIN32
-#      define KWSYS_SHARED_FORWARD_LDPATH "LD_LIBRARYN32_PATH"
-#    elif _MIPS_SIM == _ABI64
-#      define KWSYS_SHARED_FORWARD_LDPATH "LD_LIBRARY64_PATH"
-#    endif
-
-/* Cygwin */
-#  elif defined(__CYGWIN__)
-#    define KWSYS_SHARED_FORWARD_LDD                                          \
-      "cygcheck" /* TODO: cygwin 1.7 has ldd                                  \
-                  */
-#    define KWSYS_SHARED_FORWARD_LDD_N 1
-#    define KWSYS_SHARED_FORWARD_LDPATH "PATH"
-
-/* Windows */
-#  elif defined(_WIN32)
-#    define KWSYS_SHARED_FORWARD_LDPATH "PATH"
-
-/* Guess on this unknown system.  */
-#  else
-#    define KWSYS_SHARED_FORWARD_LDD "ldd"
-#    define KWSYS_SHARED_FORWARD_LDD_N 1
-#    define KWSYS_SHARED_FORWARD_LDPATH "LD_LIBRARY_PATH"
-#  endif
-
-#  ifdef KWSYS_SHARED_FORWARD_ESCAPE_ARGV
-typedef struct kwsys_sf_arg_info_s
-{
-  const char* arg;
-  int size;
-  int quote;
-} kwsys_sf_arg_info;
-
-static kwsys_sf_arg_info kwsys_sf_get_arg_info(const char* in)
-{
-  /* Initialize information.  */
-  kwsys_sf_arg_info info;
-
-  /* String iterator.  */
-  const char* c;
-
-  /* Keep track of how many backslashes have been encountered in a row.  */
-  int windows_backslashes = 0;
-
-  /* Start with the length of the original argument, plus one for
-     either a terminating null or a separating space.  */
-  info.arg = in;
-  info.size = (int)strlen(in) + 1;
-  info.quote = 0;
-
-  /* Scan the string for characters that require escaping or quoting.  */
-  for (c = in; *c; ++c) {
-    /* Check whether this character needs quotes.  */
-    if (strchr(" \t?'#&<>|^", *c)) {
-      info.quote = 1;
-    }
-
-    /* On Windows only backslashes and double-quotes need escaping.  */
-    if (*c == '\\') {
-      /* Found a backslash.  It may need to be escaped later.  */
-      ++windows_backslashes;
-    } else if (*c == '"') {
-      /* Found a double-quote.  We need to escape it and all
-         immediately preceding backslashes.  */
-      info.size += windows_backslashes + 1;
-      windows_backslashes = 0;
-    } else {
-      /* Found another character.  This eliminates the possibility
-         that any immediately preceding backslashes will be
-         escaped.  */
-      windows_backslashes = 0;
-    }
-  }
-
-  /* Check whether the argument needs surrounding quotes.  */
-  if (info.quote) {
-    /* Surrounding quotes are needed.  Allocate space for them.  */
-    info.size += 2;
-
-    /* We must escape all ending backslashes when quoting on windows.  */
-    info.size += windows_backslashes;
-  }
-
-  return info;
-}
-
-static char* kwsys_sf_get_arg(kwsys_sf_arg_info info, char* out)
-{
-  /* String iterator.  */
-  const char* c;
-
-  /* Keep track of how many backslashes have been encountered in a row.  */
-  int windows_backslashes = 0;
-
-  /* Whether the argument must be quoted.  */
-  if (info.quote) {
-    /* Add the opening quote for this argument.  */
-    *out++ = '"';
-  }
-
-  /* Scan the string for characters that require escaping or quoting.  */
-  for (c = info.arg; *c; ++c) {
-    /* On Windows only backslashes and double-quotes need escaping.  */
-    if (*c == '\\') {
-      /* Found a backslash.  It may need to be escaped later.  */
-      ++windows_backslashes;
-    } else if (*c == '"') {
-      /* Found a double-quote.  Escape all immediately preceding
-         backslashes.  */
-      while (windows_backslashes > 0) {
-        --windows_backslashes;
-        *out++ = '\\';
-      }
-
-      /* Add the backslash to escape the double-quote.  */
-      *out++ = '\\';
-    } else {
-      /* We encountered a normal character.  This eliminates any
-         escaping needed for preceding backslashes.  */
-      windows_backslashes = 0;
-    }
-
-    /* Store this character.  */
-    *out++ = *c;
-  }
-
-  if (info.quote) {
-    /* Add enough backslashes to escape any trailing ones.  */
-    while (windows_backslashes > 0) {
-      --windows_backslashes;
-      *out++ = '\\';
-    }
-
-    /* Add the closing quote for this argument.  */
-    *out++ = '"';
-  }
-
-  /* Store a terminating null without incrementing.  */
-  *out = 0;
-
-  return out;
-}
-#  endif
-
-/* Function to convert a logical or relative path to a physical full path.  */
-static int kwsys_shared_forward_realpath(const char* in_path, char* out_path)
-{
-#  if defined(_WIN32) && !defined(__CYGWIN__)
-  /* Implementation for Windows.  */
-  DWORD n =
-    GetFullPathNameA(in_path, KWSYS_SHARED_FORWARD_MAXPATH, out_path, 0);
-  return n > 0 && n <= KWSYS_SHARED_FORWARD_MAXPATH;
-#  else
-  /* Implementation for UNIX.  */
-  return realpath(in_path, out_path) != 0;
-#  endif
-}
-
-static int kwsys_shared_forward_samepath(const char* file1, const char* file2)
-{
-#  if defined(_WIN32)
-  int result = 0;
-  HANDLE h1 = CreateFileA(file1, GENERIC_READ, FILE_SHARE_READ, NULL,
-                          OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
-  HANDLE h2 = CreateFileA(file2, GENERIC_READ, FILE_SHARE_READ, NULL,
-                          OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
-  if (h1 != INVALID_HANDLE_VALUE && h2 != INVALID_HANDLE_VALUE) {
-    BY_HANDLE_FILE_INFORMATION fi1;
-    BY_HANDLE_FILE_INFORMATION fi2;
-    GetFileInformationByHandle(h1, &fi1);
-    GetFileInformationByHandle(h2, &fi2);
-    result = (fi1.dwVolumeSerialNumber == fi2.dwVolumeSerialNumber &&
-              fi1.nFileIndexHigh == fi2.nFileIndexHigh &&
-              fi1.nFileIndexLow == fi2.nFileIndexLow);
-  }
-  CloseHandle(h1);
-  CloseHandle(h2);
-  return result;
-#  else
-  struct stat fs1, fs2;
-  return (stat(file1, &fs1) == 0 && stat(file2, &fs2) == 0 &&
-          memcmp(&fs2.st_dev, &fs1.st_dev, sizeof(fs1.st_dev)) == 0 &&
-          memcmp(&fs2.st_ino, &fs1.st_ino, sizeof(fs1.st_ino)) == 0 &&
-          fs2.st_size == fs1.st_size);
-#  endif
-}
-
-/* Function to report a system error message.  */
-static void kwsys_shared_forward_strerror(char* message)
-{
-#  if defined(_WIN32) && !defined(__CYGWIN__)
-  /* Implementation for Windows.  */
-  DWORD original = GetLastError();
-  DWORD length =
-    FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
-                   0, original, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
-                   message, KWSYS_SHARED_FORWARD_MAXPATH, 0);
-  if (length < 1 || length > KWSYS_SHARED_FORWARD_MAXPATH) {
-    /* FormatMessage failed.  Use a default message.  */
-    snprintf(message, KWSYS_SHARED_FORWARD_MAXPATH,
-             "Error 0x%lX (FormatMessage failed with error 0x%lX)", original,
-             GetLastError());
-  }
-#  else
-  /* Implementation for UNIX.  */
-  strcpy(message, strerror(errno));
-#  endif
-}
-
-/* Functions to execute a child process.  */
-static void kwsys_shared_forward_execvp(const char* cmd,
-                                        char const* const* argv)
-{
-#  ifdef KWSYS_SHARED_FORWARD_ESCAPE_ARGV
-  /* Count the number of arguments.  */
-  int argc = 0;
-  {
-    char const* const* argvc;
-    for (argvc = argv; *argvc; ++argvc, ++argc) {
-    }
-  }
-
-  /* Create the escaped arguments.  */
-  {
-    char** nargv = (char**)malloc((argc + 1) * sizeof(char*));
-    int i;
-    for (i = 0; i < argc; ++i) {
-      kwsys_sf_arg_info info = kwsys_sf_get_arg_info(argv[i]);
-      nargv[i] = (char*)malloc(info.size);
-      kwsys_sf_get_arg(info, nargv[i]);
-    }
-    nargv[argc] = 0;
-
-    /* Replace the command line to be used.  */
-    argv = (char const* const*)nargv;
-  }
-#  endif
-
-/* Invoke the child process.  */
-#  if defined(_MSC_VER)
-  _execvp(cmd, argv);
-#  elif defined(__MINGW32__) && !defined(__MINGW64__)
-  execvp(cmd, argv);
-#  else
-  execvp(cmd, (char* const*)argv);
-#  endif
-}
-
-/* Function to get the directory containing the given file or directory.  */
-static void kwsys_shared_forward_dirname(const char* begin, char* result)
-{
-  /* Find the location of the last slash.  */
-  int last_slash_index = -1;
-  const char* end = begin + strlen(begin);
-  for (; begin <= end && last_slash_index < 0; --end) {
-    if (*end == '/' || *end == '\\') {
-      last_slash_index = (int)(end - begin);
-    }
-  }
-
-  /* Handle each case of the index of the last slash.  */
-  if (last_slash_index < 0) {
-    /* No slashes.  */
-    strcpy(result, ".");
-  } else if (last_slash_index == 0) {
-    /* Only one leading slash.  */
-    strcpy(result, kwsys_shared_forward_path_slash);
-  }
-#  if defined(_WIN32)
-  else if (last_slash_index == 2 && begin[1] == ':') {
-    /* Only one leading drive letter and slash.  */
-    strncpy(result, begin, (size_t)last_slash_index);
-    result[last_slash_index] = KWSYS_SHARED_FORWARD_PATH_SLASH;
-    result[last_slash_index + 1] = 0;
-  }
-#  endif
-  else {
-    /* A non-leading slash.  */
-    strncpy(result, begin, (size_t)last_slash_index);
-    result[last_slash_index] = 0;
-  }
-}
-
-/* Function to check if a file exists and is executable.  */
-static int kwsys_shared_forward_is_executable(const char* f)
-{
-#  if defined(_MSC_VER)
-#    define KWSYS_SHARED_FORWARD_ACCESS _access
-#  else
-#    define KWSYS_SHARED_FORWARD_ACCESS access
-#  endif
-#  if defined(X_OK)
-#    define KWSYS_SHARED_FORWARD_ACCESS_OK X_OK
-#  else
-#    define KWSYS_SHARED_FORWARD_ACCESS_OK 04
-#  endif
-  if (KWSYS_SHARED_FORWARD_ACCESS(f, KWSYS_SHARED_FORWARD_ACCESS_OK) == 0) {
-    return 1;
-  } else {
-    return 0;
-  }
-}
-
-/* Function to locate the executable currently running.  */
-static int kwsys_shared_forward_self_path(const char* argv0, char* result)
-{
-  /* Check whether argv0 has a slash.  */
-  int has_slash = 0;
-  const char* p = argv0;
-  for (; *p && !has_slash; ++p) {
-    if (*p == '/' || *p == '\\') {
-      has_slash = 1;
-    }
-  }
-
-  if (has_slash) {
-    /* There is a slash.  Use the dirname of the given location.  */
-    kwsys_shared_forward_dirname(argv0, result);
-    return 1;
-  } else {
-    /* There is no slash.  Search the PATH for the executable.  */
-    const char* path = getenv("PATH");
-    const char* begin = path;
-    const char* end = begin + (begin ? strlen(begin) : 0);
-    const char* first = begin;
-    while (first != end) {
-      /* Store the end of this path entry.  */
-      const char* last;
-
-      /* Skip all path separators.  */
-      for (; *first && *first == KWSYS_SHARED_FORWARD_PATH_SEP; ++first)
-        ;
-
-      /* Find the next separator.  */
-      for (last = first; *last && *last != KWSYS_SHARED_FORWARD_PATH_SEP;
-           ++last)
-        ;
-
-      /* If we got a non-empty directory, look for the executable there.  */
-      if (first < last) {
-        /* Determine the length without trailing slash.  */
-        size_t length = (size_t)(last - first);
-        if (*(last - 1) == '/' || *(last - 1) == '\\') {
-          --length;
-        }
-
-        /* Construct the name of the executable in this location.  */
-        strncpy(result, first, length);
-        result[length] = KWSYS_SHARED_FORWARD_PATH_SLASH;
-        strcpy(result + (length) + 1, argv0);
-
-        /* Check if it exists and is executable.  */
-        if (kwsys_shared_forward_is_executable(result)) {
-          /* Found it.  */
-          result[length] = 0;
-          return 1;
-        }
-      }
-
-      /* Move to the next directory in the path.  */
-      first = last;
-    }
-  }
-
-  /* We could not find the executable.  */
-  return 0;
-}
-
-/* Function to convert a specified path to a full path.  If it is not
-   already full, it is taken relative to the self path.  */
-static int kwsys_shared_forward_fullpath(const char* self_path,
-                                         const char* in_path, char* result,
-                                         const char* desc)
-{
-  /* Check the specified path type.  */
-  if (in_path[0] == '/') {
-    /* Already a full path.  */
-    strcpy(result, in_path);
-  }
-#  if defined(_WIN32)
-  else if (in_path[0] && in_path[1] == ':') {
-    /* Already a full path.  */
-    strcpy(result, in_path);
-  }
-#  endif
-  else {
-    /* Relative to self path.  */
-    char temp_path[KWSYS_SHARED_FORWARD_MAXPATH];
-    strcpy(temp_path, self_path);
-    strcat(temp_path, kwsys_shared_forward_path_slash);
-    strcat(temp_path, in_path);
-    if (!kwsys_shared_forward_realpath(temp_path, result)) {
-      if (desc) {
-        char msgbuf[KWSYS_SHARED_FORWARD_MAXPATH];
-        kwsys_shared_forward_strerror(msgbuf);
-        fprintf(stderr, "Error converting %s \"%s\" to real path: %s\n", desc,
-                temp_path, msgbuf);
-      }
-      return 0;
-    }
-  }
-  return 1;
-}
-
-/* Function to compute the library search path and executable name
-   based on the self path.  */
-static int kwsys_shared_forward_get_settings(const char* self_path,
-                                             char* ldpath, char* exe)
-{
-  /* Possible search paths.  */
-  static const char* search_path_build[] = { KWSYS_SHARED_FORWARD_PATH_BUILD,
-                                             0 };
-  static const char* search_path_install[] = {
-    KWSYS_SHARED_FORWARD_PATH_INSTALL, 0
-  };
-
-  /* Chosen paths.  */
-  const char** search_path;
-  const char* exe_path;
-
-/* Get the real name of the build and self paths.  */
-#  if defined(KWSYS_SHARED_FORWARD_CONFIG_NAME)
-  char build_path[] =
-    KWSYS_SHARED_FORWARD_DIR_BUILD "/" KWSYS_SHARED_FORWARD_CONFIG_NAME;
-  char self_path_logical[KWSYS_SHARED_FORWARD_MAXPATH];
-#  else
-  char build_path[] = KWSYS_SHARED_FORWARD_DIR_BUILD;
-  const char* self_path_logical = self_path;
-#  endif
-  char build_path_real[KWSYS_SHARED_FORWARD_MAXPATH];
-  char self_path_real[KWSYS_SHARED_FORWARD_MAXPATH];
-  if (!kwsys_shared_forward_realpath(self_path, self_path_real)) {
-    char msgbuf[KWSYS_SHARED_FORWARD_MAXPATH];
-    kwsys_shared_forward_strerror(msgbuf);
-    fprintf(stderr, "Error converting self path \"%s\" to real path: %s\n",
-            self_path, msgbuf);
-    return 0;
-  }
-
-  /* Check whether we are running in the build tree or an install tree.  */
-  if (kwsys_shared_forward_realpath(build_path, build_path_real) &&
-      kwsys_shared_forward_samepath(self_path_real, build_path_real)) {
-    /* Running in build tree.  Use the build path and exe.  */
-    search_path = search_path_build;
-#  if defined(_WIN32)
-    exe_path = KWSYS_SHARED_FORWARD_EXE_BUILD ".exe";
-#  else
-    exe_path = KWSYS_SHARED_FORWARD_EXE_BUILD;
-#  endif
-
-#  if defined(KWSYS_SHARED_FORWARD_CONFIG_NAME)
-    /* Remove the configuration directory from self_path.  */
-    kwsys_shared_forward_dirname(self_path, self_path_logical);
-#  endif
-  } else {
-    /* Running in install tree.  Use the install path and exe.  */
-    search_path = search_path_install;
-#  if defined(_WIN32)
-    exe_path = KWSYS_SHARED_FORWARD_EXE_INSTALL ".exe";
-#  else
-    exe_path = KWSYS_SHARED_FORWARD_EXE_INSTALL;
-#  endif
-
-#  if defined(KWSYS_SHARED_FORWARD_CONFIG_NAME)
-    /* Use the original self path directory.  */
-    strcpy(self_path_logical, self_path);
-#  endif
-  }
-
-  /* Construct the runtime search path.  */
-  {
-    const char** dir;
-    for (dir = search_path; *dir; ++dir) {
-      /* Add separator between path components.  */
-      if (dir != search_path) {
-        strcat(ldpath, kwsys_shared_forward_path_sep);
-      }
-
-      /* Add this path component.  */
-      if (!kwsys_shared_forward_fullpath(self_path_logical, *dir,
-                                         ldpath + strlen(ldpath),
-                                         "runtime path entry")) {
-        return 0;
-      }
-    }
-  }
-
-  /* Construct the executable location.  */
-  if (!kwsys_shared_forward_fullpath(self_path_logical, exe_path, exe,
-                                     "executable file")) {
-    return 0;
-  }
-  return 1;
-}
-
-/* Function to print why execution of a command line failed.  */
-static void kwsys_shared_forward_print_failure(char const* const* argv)
-{
-  char msg[KWSYS_SHARED_FORWARD_MAXPATH];
-  char const* const* arg = argv;
-  kwsys_shared_forward_strerror(msg);
-  fprintf(stderr, "Error running");
-  for (; *arg; ++arg) {
-    fprintf(stderr, " \"%s\"", *arg);
-  }
-  fprintf(stderr, ": %s\n", msg);
-}
-
-/* Static storage space to store the updated environment variable.  */
-static char kwsys_shared_forward_ldpath[65535] =
-  KWSYS_SHARED_FORWARD_LDPATH "=";
-
-/* Main driver function to be called from main.  */
-static int @KWSYS_NAMESPACE@_shared_forward_to_real(int argc, char** argv_in)
-{
-  char const** argv = (char const**)argv_in;
-  /* Get the directory containing this executable.  */
-  char self_path[KWSYS_SHARED_FORWARD_MAXPATH];
-  if (kwsys_shared_forward_self_path(argv[0], self_path)) {
-    /* Found this executable.  Use it to get the library directory.  */
-    char exe[KWSYS_SHARED_FORWARD_MAXPATH];
-    if (kwsys_shared_forward_get_settings(self_path,
-                                          kwsys_shared_forward_ldpath, exe)) {
-      /* Append the old runtime search path.  */
-      const char* old_ldpath = getenv(KWSYS_SHARED_FORWARD_LDPATH);
-      if (old_ldpath) {
-        strcat(kwsys_shared_forward_ldpath, kwsys_shared_forward_path_sep);
-        strcat(kwsys_shared_forward_ldpath, old_ldpath);
-      }
-
-      /* Store the environment variable.  */
-      putenv(kwsys_shared_forward_ldpath);
-
-#  if defined(KWSYS_SHARED_FORWARD_OPTION_COMMAND)
-      /* Look for the command line replacement option.  */
-      if (argc > 1 &&
-          strcmp(argv[1], KWSYS_SHARED_FORWARD_OPTION_COMMAND) == 0) {
-        if (argc > 2) {
-          /* Use the command line given.  */
-          strcpy(exe, argv[2]);
-          argv += 2;
-          argc -= 2;
-        } else {
-          /* The option was not given an executable.  */
-          fprintf(stderr,
-                  "Option " KWSYS_SHARED_FORWARD_OPTION_COMMAND
-                  " must be followed by a command line.\n");
-          return 1;
-        }
-      }
-#  endif
-
-#  if defined(KWSYS_SHARED_FORWARD_OPTION_PRINT)
-      /* Look for the print command line option.  */
-      if (argc > 1 &&
-          strcmp(argv[1], KWSYS_SHARED_FORWARD_OPTION_PRINT) == 0) {
-        fprintf(stdout, "%s\n", kwsys_shared_forward_ldpath);
-        fprintf(stdout, "%s\n", exe);
-        return 0;
-      }
-#  endif
-
-#  if defined(KWSYS_SHARED_FORWARD_OPTION_LDD)
-      /* Look for the ldd command line option.  */
-      if (argc > 1 && strcmp(argv[1], KWSYS_SHARED_FORWARD_OPTION_LDD) == 0) {
-#    if defined(KWSYS_SHARED_FORWARD_LDD)
-        /* Use the named ldd-like executable and arguments.  */
-        char const* ldd_argv[] = { KWSYS_SHARED_FORWARD_LDD, 0, 0 };
-        ldd_argv[KWSYS_SHARED_FORWARD_LDD_N] = exe;
-        kwsys_shared_forward_execvp(ldd_argv[0], ldd_argv);
-
-        /* Report why execution failed.  */
-        kwsys_shared_forward_print_failure(ldd_argv);
-        return 1;
-#    else
-        /* We have no ldd-like executable available on this platform.  */
-        fprintf(stderr, "No ldd-like tool is known to this executable.\n");
-        return 1;
-#    endif
-      }
-#  endif
-
-      /* Replace this process with the real executable.  */
-      argv[0] = exe;
-      kwsys_shared_forward_execvp(argv[0], argv);
-
-      /* Report why execution failed.  */
-      kwsys_shared_forward_print_failure(argv);
-    } else {
-      /* Could not convert self path to the library directory.  */
-    }
-  } else {
-    /* Could not find this executable.  */
-    fprintf(stderr, "Error locating executable \"%s\".\n", argv[0]);
-  }
-
-  /* Avoid unused argument warning.  */
-  (void)argc;
-
-  /* Exit with failure.  */
-  return 1;
-}
-
-/* Restore warning stack.  */
-#  if defined(__clang__) && defined(__has_warning)
-#    if __has_warning("-Wcast-qual")
-#      pragma clang diagnostic pop
-#    endif
-#  endif
-
-#else
-#  error "@KWSYS_NAMESPACE@/SharedForward.h should be included only once."
-#endif
diff --git a/Source/kwsys/String.hxx.in b/Source/kwsys/String.hxx.in
deleted file mode 100644
index c36f4ce..0000000
--- a/Source/kwsys/String.hxx.in
+++ /dev/null
@@ -1,57 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-   file Copyright.txt or https://cmake.org/licensing#kwsys for details.  */
-#ifndef @KWSYS_NAMESPACE@_String_hxx
-#define @KWSYS_NAMESPACE@_String_hxx
-
-#include <string>
-
-namespace @KWSYS_NAMESPACE@ {
-
-/** \class String
- * \brief Short-name version of the STL basic_string class template.
- *
- * The standard library "string" type is actually a typedef for
- * "basic_string<..long argument list..>".  This string class is
- * simply a subclass of this type with the same interface so that the
- * name is shorter in debugging symbols and error messages.
- */
-class String : public std::string
-{
-  /** The original string type.  */
-  typedef std::string stl_string;
-
-public:
-  /** String member types.  */
-  typedef stl_string::value_type value_type;
-  typedef stl_string::pointer pointer;
-  typedef stl_string::reference reference;
-  typedef stl_string::const_reference const_reference;
-  typedef stl_string::size_type size_type;
-  typedef stl_string::difference_type difference_type;
-  typedef stl_string::iterator iterator;
-  typedef stl_string::const_iterator const_iterator;
-  typedef stl_string::reverse_iterator reverse_iterator;
-  typedef stl_string::const_reverse_iterator const_reverse_iterator;
-
-  /** String constructors.  */
-  String()
-    : stl_string()
-  {
-  }
-  String(const value_type* s)
-    : stl_string(s)
-  {
-  }
-  String(const value_type* s, size_type n)
-    : stl_string(s, n)
-  {
-  }
-  String(const stl_string& s, size_type pos = 0, size_type n = npos)
-    : stl_string(s, pos, n)
-  {
-  }
-}; // End Class: String
-
-} // namespace @KWSYS_NAMESPACE@
-
-#endif
diff --git a/Source/kwsys/SystemInformation.cxx b/Source/kwsys/SystemInformation.cxx
index 20e2edb..7f8485e 100644
--- a/Source/kwsys/SystemInformation.cxx
+++ b/Source/kwsys/SystemInformation.cxx
@@ -3453,6 +3453,10 @@
     fileSize++;
   }
   fclose(fd);
+  if (fileSize < 2) {
+    std::cout << "No data in /proc/cpuinfo" << std::endl;
+    return false;
+  }
   buffer.resize(fileSize - 2);
   // Number of logical CPUs (combination of multiple processors, multi-core
   // and SMT)
diff --git a/Source/kwsys/SystemTools.cxx b/Source/kwsys/SystemTools.cxx
index 573cc6a..3bb7869 100644
--- a/Source/kwsys/SystemTools.cxx
+++ b/Source/kwsys/SystemTools.cxx
@@ -3424,9 +3424,7 @@
 }
 
 bool SystemTools::FindProgramPath(const char* argv0, std::string& pathOut,
-                                  std::string& errorMsg, const char* exeName,
-                                  const char* buildDir,
-                                  const char* installPrefix)
+                                  std::string& errorMsg)
 {
   std::vector<std::string> failures;
   std::string self = argv0 ? argv0 : "";
@@ -3434,34 +3432,9 @@
   SystemTools::ConvertToUnixSlashes(self);
   self = SystemTools::FindProgram(self);
   if (!SystemTools::FileIsExecutable(self)) {
-    if (buildDir) {
-      std::string intdir = ".";
-#ifdef CMAKE_INTDIR
-      intdir = CMAKE_INTDIR;
-#endif
-      self = buildDir;
-      self += "/bin/";
-      self += intdir;
-      self += "/";
-      self += exeName;
-      self += SystemTools::GetExecutableExtension();
-    }
-  }
-  if (installPrefix) {
-    if (!SystemTools::FileIsExecutable(self)) {
-      failures.push_back(self);
-      self = installPrefix;
-      self += "/bin/";
-      self += exeName;
-    }
-  }
-  if (!SystemTools::FileIsExecutable(self)) {
     failures.push_back(self);
     std::ostringstream msg;
     msg << "Can not find the command line program ";
-    if (exeName) {
-      msg << exeName;
-    }
     msg << "\n";
     if (argv0) {
       msg << "  argv[0] = \"" << argv0 << "\"\n";
diff --git a/Source/kwsys/SystemTools.hxx.in b/Source/kwsys/SystemTools.hxx.in
index 56b65fd..729928e 100644
--- a/Source/kwsys/SystemTools.hxx.in
+++ b/Source/kwsys/SystemTools.hxx.in
@@ -395,10 +395,7 @@
    *  installPrefix is a possibly null pointer to the install directory.
    */
   static bool FindProgramPath(const char* argv0, std::string& pathOut,
-                              std::string& errorMsg,
-                              const char* exeName = nullptr,
-                              const char* buildDir = nullptr,
-                              const char* installPrefix = nullptr);
+                              std::string& errorMsg);
 
   /**
    * Given a path to a file or directory, convert it to a full path.
diff --git a/Source/kwsys/kwsysPlatformTests.cmake b/Source/kwsys/kwsysPlatformTests.cmake
index 89be4b8..6c006bc 100644
--- a/Source/kwsys/kwsysPlatformTests.cmake
+++ b/Source/kwsys/kwsysPlatformTests.cmake
@@ -8,9 +8,6 @@
   if(NOT DEFINED ${var}_COMPILED)
     message(STATUS "${description}")
     set(maybe_cxx_standard "")
-    if(CMAKE_VERSION VERSION_LESS 3.8 AND CMAKE_CXX_STANDARD)
-      set(maybe_cxx_standard "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}")
-    endif()
     try_compile(${var}_COMPILED
       ${CMAKE_CURRENT_BINARY_DIR}
       ${CMAKE_CURRENT_SOURCE_DIR}/${KWSYS_PLATFORM_TEST_FILE_${lang}}
@@ -18,14 +15,16 @@
       CMAKE_FLAGS "-DLINK_LIBRARIES:STRING=${KWSYS_PLATFORM_TEST_LINK_LIBRARIES}"
                   ${maybe_cxx_standard}
       OUTPUT_VARIABLE OUTPUT)
-    if(${var}_COMPILED)
-      file(APPEND
-        ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log
-        "${description} compiled with the following output:\n${OUTPUT}\n\n")
-    else()
-      file(APPEND
-        ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log
-        "${description} failed to compile with the following output:\n${OUTPUT}\n\n")
+    if(CMAKE_VERSION VERSION_LESS 3.26)
+      if(${var}_COMPILED)
+        file(APPEND
+          ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log
+          "${description} compiled with the following output:\n${OUTPUT}\n\n")
+      else()
+        file(APPEND
+          ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log
+          "${description} failed to compile with the following output:\n${OUTPUT}\n\n")
+      endif()
     endif()
     if(${invert} MATCHES INVERT)
       if(${var}_COMPILED)
@@ -67,19 +66,23 @@
 
     # Note that ${var} will be a 0 return value on success.
     if(${var}_COMPILED)
-      if(${var})
-        file(APPEND
-          ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log
-          "${description} compiled but failed to run with the following output:\n${OUTPUT}\n\n")
-      else()
-        file(APPEND
-          ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log
-          "${description} compiled and ran with the following output:\n${OUTPUT}\n\n")
+      if(CMAKE_VERSION VERSION_LESS 3.26)
+        if(${var})
+          file(APPEND
+            ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log
+            "${description} compiled but failed to run with the following output:\n${OUTPUT}\n\n")
+        else()
+          file(APPEND
+            ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log
+            "${description} compiled and ran with the following output:\n${OUTPUT}\n\n")
+        endif()
       endif()
     else()
-      file(APPEND
-        ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log
-        "${description} failed to compile with the following output:\n${OUTPUT}\n\n")
+      if(CMAKE_VERSION VERSION_LESS 3.26)
+        file(APPEND
+          ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log
+          "${description} failed to compile with the following output:\n${OUTPUT}\n\n")
+      endif()
       set(${var} -1 CACHE INTERNAL "${description} failed to compile.")
     endif()
 
@@ -188,14 +191,16 @@
         OUTPUT_VARIABLE OUTPUT
         COPY_FILE ${KWSYS_PLATFORM_INFO_FILE}
         )
-      if(${var}_COMPILED)
-        file(APPEND
-          ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log
-          "${description} compiled with the following output:\n${OUTPUT}\n\n")
-      else()
-        file(APPEND
-          ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log
-          "${description} failed to compile with the following output:\n${OUTPUT}\n\n")
+      if(CMAKE_VERSION VERSION_LESS 3.26)
+        if(${var}_COMPILED)
+          file(APPEND
+            ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log
+            "${description} compiled with the following output:\n${OUTPUT}\n\n")
+        else()
+          file(APPEND
+            ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log
+            "${description} failed to compile with the following output:\n${OUTPUT}\n\n")
+        endif()
       endif()
       if(${var}_COMPILED)
         message(STATUS "${description} - compiled")
diff --git a/Source/kwsys/testDynamicLoader.cxx b/Source/kwsys/testDynamicLoader.cxx
index 806c01a..a5095a5 100644
--- a/Source/kwsys/testDynamicLoader.cxx
+++ b/Source/kwsys/testDynamicLoader.cxx
@@ -53,9 +53,9 @@
     slname += "/";
     slname += subdir;
   }
-#ifdef CMAKE_INTDIR
+#ifdef BUILD_CONFIG
   slname += "/";
-  slname += CMAKE_INTDIR;
+  slname += BUILD_CONFIG;
 #endif
   slname += "/";
   slname += kwsys::DynamicLoader::LibPrefix();
diff --git a/Source/kwsys/testSharedForward.c.in b/Source/kwsys/testSharedForward.c.in
deleted file mode 100644
index e909458..0000000
--- a/Source/kwsys/testSharedForward.c.in
+++ /dev/null
@@ -1,31 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-   file Copyright.txt or https://cmake.org/licensing#kwsys for details.  */
-#if !defined(_WIN32) && !defined(__APPLE__) && !defined(__OpenBSD__)
-/* NOLINTNEXTLINE(bugprone-reserved-identifier) */
-#  define _XOPEN_SOURCE 600
-#endif
-#if defined(CMAKE_INTDIR)
-#  define CONFIG_DIR_PRE CMAKE_INTDIR "/"
-#  define CONFIG_DIR_POST "/" CMAKE_INTDIR
-#else
-#  define CONFIG_DIR_PRE ""
-#  define CONFIG_DIR_POST ""
-#endif
-#define @KWSYS_NAMESPACE@_SHARED_FORWARD_DIR_BUILD "@EXEC_DIR@"
-#define @KWSYS_NAMESPACE@_SHARED_FORWARD_PATH_BUILD "." CONFIG_DIR_POST
-#define @KWSYS_NAMESPACE@_SHARED_FORWARD_PATH_INSTALL 0
-#define @KWSYS_NAMESPACE@_SHARED_FORWARD_EXE_BUILD                            \
-  CONFIG_DIR_PRE "@KWSYS_NAMESPACE@TestProcess"
-#define @KWSYS_NAMESPACE@_SHARED_FORWARD_EXE_INSTALL                          \
-  "@KWSYS_NAMESPACE@TestProcess"
-#define @KWSYS_NAMESPACE@_SHARED_FORWARD_OPTION_COMMAND "--command"
-#define @KWSYS_NAMESPACE@_SHARED_FORWARD_OPTION_PRINT "--print"
-#define @KWSYS_NAMESPACE@_SHARED_FORWARD_OPTION_LDD "--ldd"
-#if defined(CMAKE_INTDIR)
-#  define @KWSYS_NAMESPACE@_SHARED_FORWARD_CONFIG_NAME CMAKE_INTDIR
-#endif
-#include <@KWSYS_NAMESPACE@/SharedForward.h>
-int main(int argc, char** argv)
-{
-  return @KWSYS_NAMESPACE@_shared_forward_to_real(argc, argv);
-}
diff --git a/Templates/TestDriver.cxx.in b/Templates/TestDriver.cxx.in
index c47266a..3bb2fd6 100644
--- a/Templates/TestDriver.cxx.in
+++ b/Templates/TestDriver.cxx.in
@@ -20,11 +20,19 @@
 #  else
 #    define CM_NULL NULL
 #  endif
+#  define CM_NAMESPACE_BEGIN namespace {
+#  define CM_NAMESPACE_END }
+#  define CM_LOCAL
 #else
 #  define CM_CAST(TYPE, EXPR) (TYPE)(EXPR)
 #  define CM_NULL NULL
+#  define CM_NAMESPACE_BEGIN
+#  define CM_NAMESPACE_END
+#  define CM_LOCAL static
 #endif
 
+CM_NAMESPACE_BEGIN
+
 /* Create map.  */
 
 typedef int (*MainFuncPointer)(int, char* []); /* NOLINT */
@@ -34,17 +42,17 @@
   MainFuncPointer func;
 } functionMapEntry;
 
-static functionMapEntry cmakeGeneratedFunctionMapEntries[] = {
+CM_LOCAL const functionMapEntry cmakeGeneratedFunctionMapEntries[] = {
   @CMAKE_FUNCTION_TABLE_ENTRIES@
   { CM_NULL, CM_NULL } /* NOLINT */
 };
 
-static const int NumTests = CM_CAST(int,
+CM_LOCAL const int NumTests = CM_CAST(int,
   sizeof(cmakeGeneratedFunctionMapEntries) / sizeof(functionMapEntry)) - 1;
 
 /* Allocate and create a lowercased copy of string
    (note that it has to be free'd manually) */
-static char* lowercase(const char* string)
+CM_LOCAL char* lowercase(const char* string)
 {
   char *new_string;
   char *p;
@@ -63,7 +71,7 @@
   return new_string;
 }
 
-static int isTestSkipped(const char *name, int n_skipped_tests, char *skipped_tests[]) {
+CM_LOCAL int isTestSkipped(const char *name, int n_skipped_tests, char *skipped_tests[]) {
   int i;
   for (i = 0; i < n_skipped_tests; i++) {
     if (strcmp(name, skipped_tests[i]) == 0) {
@@ -74,6 +82,8 @@
   return 0;
 }
 
+CM_NAMESPACE_END
+
 int main(int ac, char* av[])
 {
   int i;
diff --git a/Tests/Architecture/CMakeLists.txt b/Tests/Architecture/CMakeLists.txt
index 96def00..3d10ee0 100644
--- a/Tests/Architecture/CMakeLists.txt
+++ b/Tests/Architecture/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(Architecture C)
 
 function(test_for_xcode4 result_var)
diff --git a/Tests/ArgumentExpansion/CMakeLists.txt b/Tests/ArgumentExpansion/CMakeLists.txt
index da3bb4c..9ab87b2 100644
--- a/Tests/ArgumentExpansion/CMakeLists.txt
+++ b/Tests/ArgumentExpansion/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(ArgumentExpansion)
 
diff --git a/Tests/BundleGeneratorTest/CMakeLists.txt b/Tests/BundleGeneratorTest/CMakeLists.txt
index cf7e2ce..069fb77 100644
--- a/Tests/BundleGeneratorTest/CMakeLists.txt
+++ b/Tests/BundleGeneratorTest/CMakeLists.txt
@@ -1,6 +1,6 @@
 project(BundleGeneratorTest)
 
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 # Build a shared library and install it in lib/
 add_library(Library SHARED Library.cxx)
diff --git a/Tests/BundleUtilities/CMakeLists.txt b/Tests/BundleUtilities/CMakeLists.txt
index 4a95e2f..b53d499 100644
--- a/Tests/BundleUtilities/CMakeLists.txt
+++ b/Tests/BundleUtilities/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(BundleUtilities)
 
 if(CMAKE_GENERATOR STREQUAL "Xcode" AND
diff --git a/Tests/CFBundleTest/CMakeLists.txt b/Tests/CFBundleTest/CMakeLists.txt
index 5f2e8ec..40dd887 100644
--- a/Tests/CFBundleTest/CMakeLists.txt
+++ b/Tests/CFBundleTest/CMakeLists.txt
@@ -1,6 +1,6 @@
 #this is adapted from FireBreath (http://www.firebreath.org)
 
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(CFBundleTest)
 
diff --git a/Tests/CMakeCommands/add_compile_options/CMakeLists.txt b/Tests/CMakeCommands/add_compile_options/CMakeLists.txt
index a6b3ffe..96f553a 100644
--- a/Tests/CMakeCommands/add_compile_options/CMakeLists.txt
+++ b/Tests/CMakeCommands/add_compile_options/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 if(POLICY CMP0129)
   cmake_policy(SET CMP0129 NEW)
diff --git a/Tests/CMakeCommands/target_compile_definitions/CMakeLists.txt b/Tests/CMakeCommands/target_compile_definitions/CMakeLists.txt
index 72b3502..0c1af9e 100644
--- a/Tests/CMakeCommands/target_compile_definitions/CMakeLists.txt
+++ b/Tests/CMakeCommands/target_compile_definitions/CMakeLists.txt
@@ -1,5 +1,5 @@
 
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(target_compile_definitions)
 
diff --git a/Tests/CMakeCommands/target_compile_options/CMakeLists.txt b/Tests/CMakeCommands/target_compile_options/CMakeLists.txt
index 2e3760a..dd4fe02 100644
--- a/Tests/CMakeCommands/target_compile_options/CMakeLists.txt
+++ b/Tests/CMakeCommands/target_compile_options/CMakeLists.txt
@@ -1,5 +1,5 @@
 
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 if(POLICY CMP0129)
   cmake_policy(SET CMP0129 NEW)
diff --git a/Tests/CMakeCommands/target_include_directories/CMakeLists.txt b/Tests/CMakeCommands/target_include_directories/CMakeLists.txt
index 3de9ef7..d5d4970 100644
--- a/Tests/CMakeCommands/target_include_directories/CMakeLists.txt
+++ b/Tests/CMakeCommands/target_include_directories/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(target_include_directories)
 
diff --git a/Tests/CMakeCommands/target_link_libraries/CMakeLists.txt b/Tests/CMakeCommands/target_link_libraries/CMakeLists.txt
index 52080bd..8231a5c 100644
--- a/Tests/CMakeCommands/target_link_libraries/CMakeLists.txt
+++ b/Tests/CMakeCommands/target_link_libraries/CMakeLists.txt
@@ -1,6 +1,6 @@
 # Using 2.8 will trigger a deprecation warning.  In this case it's explicitly
 # intentional since the tests checks various policy implementations prior to
-# 2.8.12
+# 3.5
 cmake_minimum_required(VERSION 2.8)
 
 if(POLICY CMP0129)
@@ -155,3 +155,19 @@
 add_library(TopDirImported IMPORTED INTERFACE)
 target_compile_definitions(TopDirImported INTERFACE DEF_TopDirImported)
 cmake_policy(POP)
+
+#----------------------------------------------------------------------------
+# Test $<COMPILE_ONLY:> genex.
+cmake_policy(SET CMP0099 NEW)
+add_library(dont_link_too SHARED compile_only.cpp)
+target_compile_definitions(dont_link_too PUBLIC USE_EXAMPLE)
+target_link_options(dont_link_too INTERFACE invalid_link_option)
+target_link_libraries(dont_link_too INTERFACE invalid_link_library)
+
+add_library(uses_compile_only_genex SHARED compile_only.cpp)
+target_link_libraries(uses_compile_only_genex PUBLIC $<COMPILE_ONLY:dont_link_too>)
+
+add_library(uses_compile_only_genex_static STATIC compile_only.cpp)
+target_link_libraries(uses_compile_only_genex_static PRIVATE $<COMPILE_ONLY:dont_link_too>)
+add_executable(uses_via_static_linking main.cxx)
+target_link_libraries(uses_via_static_linking PRIVATE uses_compile_only_genex_static)
diff --git a/Tests/CMakeCommands/target_link_libraries/compile_only.cpp b/Tests/CMakeCommands/target_link_libraries/compile_only.cpp
new file mode 100644
index 0000000..7519bd0
--- /dev/null
+++ b/Tests/CMakeCommands/target_link_libraries/compile_only.cpp
@@ -0,0 +1,8 @@
+
+#ifndef USE_EXAMPLE
+#  error "Missing propagated define"
+#endif
+
+// Solaris needs non-empty content so ensure
+// we have at least one symbol
+int Solaris_requires_a_symbol_here = 0;
diff --git a/Tests/CMakeLib/CMakeLists.txt b/Tests/CMakeLib/CMakeLists.txt
index 612d4b4..5c14de2 100644
--- a/Tests/CMakeLib/CMakeLists.txt
+++ b/Tests/CMakeLib/CMakeLists.txt
@@ -30,12 +30,24 @@
   testCMExtMemory.cxx
   testCMExtAlgorithm.cxx
   testCMExtEnumSet.cxx
+  testList.cxx
   )
+if(CMake_ENABLE_DEBUGGER)
+  list(APPEND CMakeLib_TESTS
+    testDebuggerAdapter.cxx
+    testDebuggerAdapterPipe.cxx
+    testDebuggerBreakpointManager.cxx
+    testDebuggerVariables.cxx
+    testDebuggerVariablesHelper.cxx
+    testDebuggerVariablesManager.cxx
+    )
+endif()
 if (CMake_TEST_FILESYSTEM_PATH OR NOT CMake_HAVE_CXX_FILESYSTEM)
   list(APPEND CMakeLib_TESTS testCMFilesystemPath.cxx)
 endif()
 
 add_executable(testUVProcessChainHelper testUVProcessChainHelper.cxx)
+target_link_libraries(testUVProcessChainHelper CMakeLib)
 
 set(testRST_ARGS ${CMAKE_CURRENT_SOURCE_DIR})
 set(testUVProcessChain_ARGS $<TARGET_FILE:testUVProcessChainHelper>)
@@ -76,3 +88,18 @@
 
 add_executable(testAffinity testAffinity.cxx)
 target_link_libraries(testAffinity CMakeLib)
+
+if(CMake_ENABLE_DEBUGGER)
+  add_executable(testDebuggerNamedPipe testDebuggerNamedPipe.cxx)
+  target_link_libraries(testDebuggerNamedPipe PRIVATE CMakeLib)
+  set(testDebuggerNamedPipe_Project_ARGS
+    "$<TARGET_FILE:cmake>" ${CMAKE_CURRENT_SOURCE_DIR}/DebuggerSample ${CMAKE_CURRENT_BINARY_DIR}/DebuggerSample
+    )
+  set(testDebuggerNamedPipe_Script_ARGS
+    "$<TARGET_FILE:cmake>" ${CMAKE_CURRENT_SOURCE_DIR}/DebuggerSample/script.cmake
+    )
+  foreach(case Project Script)
+    add_test(NAME CMakeLib.testDebuggerNamedPipe-${case} COMMAND testDebuggerNamedPipe ${testDebuggerNamedPipe_${case}_ARGS})
+    set_property(TEST CMakeLib.testDebuggerNamedPipe-${case} PROPERTY TIMEOUT 300)
+  endforeach()
+endif()
diff --git a/Tests/CMakeLib/DebuggerSample/CMakeLists.txt b/Tests/CMakeLib/DebuggerSample/CMakeLists.txt
new file mode 100644
index 0000000..8f8603a
--- /dev/null
+++ b/Tests/CMakeLib/DebuggerSample/CMakeLists.txt
@@ -0,0 +1,9 @@
+cmake_minimum_required(VERSION 3.26)
+project(DebuggerSample NONE)
+message("Hello CMake Debugger")
+
+# There are concerns that because the debugger uses libuv for pipe
+# communication, libuv may register a SIGCHILD handler that interferes with
+# the existing handler used by kwsys process management. Test this case with a
+# simple external process.
+execute_process(COMMAND "${CMAKE_COMMAND}" -E echo test)
diff --git a/Tests/CMakeLib/DebuggerSample/script.cmake b/Tests/CMakeLib/DebuggerSample/script.cmake
new file mode 100644
index 0000000..4c0c00a
--- /dev/null
+++ b/Tests/CMakeLib/DebuggerSample/script.cmake
@@ -0,0 +1 @@
+message(STATUS "This is an example script")
diff --git a/Tests/CMakeLib/testCTestResourceAllocator.cxx b/Tests/CMakeLib/testCTestResourceAllocator.cxx
index 72e06e5..3a2e524 100644
--- a/Tests/CMakeLib/testCTestResourceAllocator.cxx
+++ b/Tests/CMakeLib/testCTestResourceAllocator.cxx
@@ -5,12 +5,16 @@
 
 #include "cmCTestResourceAllocator.h"
 #include "cmCTestResourceSpec.h"
+#include "cmJSONState.h"
 
-static const cmCTestResourceSpec spec{ { {
-  /* clang-format off */
-  { "gpus", { { "0", 4 }, { "1", 8 }, { "2", 0 }, { "3", 8 } } },
-  /* clang-format on */
-} } };
+static const cmCTestResourceSpec spec{
+  { {
+    /* clang-format off */
+  { "gpus", { { "0", 4 }, { "1", 8 }, { "2", 0 }, { "3", 8 } }, },
+    /* clang-format on */
+  } },
+  cmJSONState()
+};
 
 static bool testInitializeFromResourceSpec()
 {
diff --git a/Tests/CMakeLib/testCTestResourceSpec.cxx b/Tests/CMakeLib/testCTestResourceSpec.cxx
index b49f8ff..4a0021f 100644
--- a/Tests/CMakeLib/testCTestResourceSpec.cxx
+++ b/Tests/CMakeLib/testCTestResourceSpec.cxx
@@ -3,89 +3,72 @@
 #include <vector>
 
 #include "cmCTestResourceSpec.h"
+#include "cmJSONState.h"
 
 struct ExpectedSpec
 {
   std::string Path;
-  cmCTestResourceSpec::ReadFileResult ParseResult;
+  bool ParseResult;
   cmCTestResourceSpec Expected;
 };
 
 static const std::vector<ExpectedSpec> expectedResourceSpecs = {
   { "spec1.json",
-    cmCTestResourceSpec::ReadFileResult::READ_OK,
+    true,
     { { {
-      { "gpus",
-        {
-          { "2", 4 },
-          { "e", 1 },
-        } },
-      { "threads", {} },
-    } } } },
-  { "spec2.json", cmCTestResourceSpec::ReadFileResult::READ_OK, {} },
-  { "spec3.json",
-    cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC,
-    {} },
-  { "spec4.json",
-    cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC,
-    {} },
-  { "spec5.json",
-    cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC,
-    {} },
-  { "spec6.json",
-    cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC,
-    {} },
-  { "spec7.json",
-    cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE_TYPE,
-    {} },
-  { "spec8.json", cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE, {} },
-  { "spec9.json", cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE, {} },
-  { "spec10.json", cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE, {} },
-  { "spec11.json", cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE, {} },
-  { "spec12.json", cmCTestResourceSpec::ReadFileResult::INVALID_ROOT, {} },
-  { "spec13.json", cmCTestResourceSpec::ReadFileResult::JSON_PARSE_ERROR, {} },
-  { "spec14.json", cmCTestResourceSpec::ReadFileResult::READ_OK, {} },
-  { "spec15.json", cmCTestResourceSpec::ReadFileResult::READ_OK, {} },
-  { "spec16.json", cmCTestResourceSpec::ReadFileResult::READ_OK, {} },
-  { "spec17.json", cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE, {} },
-  { "spec18.json", cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE, {} },
-  { "spec19.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec20.json", cmCTestResourceSpec::ReadFileResult::READ_OK, {} },
-  { "spec21.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec22.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec23.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec24.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec25.json",
-    cmCTestResourceSpec::ReadFileResult::UNSUPPORTED_VERSION,
-    {} },
-  { "spec26.json",
-    cmCTestResourceSpec::ReadFileResult::UNSUPPORTED_VERSION,
-    {} },
-  { "spec27.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec28.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec29.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec30.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec31.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec32.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec33.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec34.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec35.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec36.json", cmCTestResourceSpec::ReadFileResult::NO_VERSION, {} },
-  { "noexist.json", cmCTestResourceSpec::ReadFileResult::FILE_NOT_FOUND, {} },
+        { "gpus",
+          {
+            { "2", 4 },
+            { "e", 1 },
+          } },
+        { "threads", {} },
+      } },
+      cmJSONState() } },
+  { "spec2.json", true, {} },
+  { "spec3.json", false, {} },
+  { "spec4.json", false, {} },
+  { "spec5.json", false, {} },
+  { "spec6.json", false, {} },
+  { "spec7.json", false, {} },
+  { "spec8.json", false, {} },
+  { "spec9.json", false, {} },
+  { "spec10.json", false, {} },
+  { "spec11.json", false, {} },
+  { "spec12.json", false, {} },
+  { "spec13.json", false, {} },
+  { "spec14.json", true, {} },
+  { "spec15.json", true, {} },
+  { "spec16.json", true, {} },
+  { "spec17.json", false, {} },
+  { "spec18.json", false, {} },
+  { "spec19.json", false, {} },
+  { "spec20.json", true, {} },
+  { "spec21.json", false, {} },
+  { "spec22.json", false, {} },
+  { "spec23.json", false, {} },
+  { "spec24.json", false, {} },
+  { "spec25.json", false, {} },
+  { "spec26.json", false, {} },
+  { "spec27.json", false, {} },
+  { "spec28.json", false, {} },
+  { "spec29.json", false, {} },
+  { "spec30.json", false, {} },
+  { "spec31.json", false, {} },
+  { "spec32.json", false, {} },
+  { "spec33.json", false, {} },
+  { "spec34.json", false, {} },
+  { "spec35.json", false, {} },
+  { "spec36.json", false, {} },
+  { "noexist.json", false, {} },
 };
 
-static bool testSpec(const std::string& path,
-                     cmCTestResourceSpec::ReadFileResult expectedResult,
+static bool testSpec(const std::string& path, bool expectedResult,
                      const cmCTestResourceSpec& expected)
 {
   cmCTestResourceSpec actual;
   auto result = actual.ReadFromJSONFile(path);
   if (result != expectedResult) {
-    std::cout << "ReadFromJSONFile(\"" << path << "\") returned \""
-              << cmCTestResourceSpec::ResultToString(result)
-              << "\", should be \""
-              << cmCTestResourceSpec::ResultToString(expectedResult) << "\""
-              << std::endl;
+    std::cout << "ReadFromJSONFile(\"" << path << "\") failed \"" << std::endl;
     return false;
   }
 
diff --git a/Tests/CMakeLib/testCommon.h b/Tests/CMakeLib/testCommon.h
new file mode 100644
index 0000000..bd2d54e
--- /dev/null
+++ b/Tests/CMakeLib/testCommon.h
@@ -0,0 +1,30 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <functional>
+#include <iostream>
+#include <vector>
+
+#define ASSERT_TRUE(x)                                                        \
+  do {                                                                        \
+    if (!(x)) {                                                               \
+      std::cout << "ASSERT_TRUE(" #x ") failed on line " << __LINE__ << "\n"; \
+      return false;                                                           \
+    }                                                                         \
+  } while (false)
+
+inline int runTests(std::vector<std::function<bool()>> const& tests)
+{
+  for (auto const& test : tests) {
+    if (!test()) {
+      return 1;
+    }
+    std::cout << ".";
+  }
+
+  std::cout << " Passed" << std::endl;
+  return 0;
+}
+
+#define BOOL_STRING(b) ((b) ? "TRUE" : "FALSE")
diff --git a/Tests/CMakeLib/testDebugger.h b/Tests/CMakeLib/testDebugger.h
new file mode 100644
index 0000000..8ba21f6
--- /dev/null
+++ b/Tests/CMakeLib/testDebugger.h
@@ -0,0 +1,99 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <memory>
+#include <vector>
+
+#include "cmDebuggerAdapter.h"
+#include "cmDebuggerProtocol.h"
+#include "cmListFileCache.h"
+#include "cmMessenger.h"
+#include <cmcppdap/include/dap/io.h>
+#include <cmcppdap/include/dap/session.h>
+#include <cmcppdap/include/dap/types.h>
+
+#include "testCommon.h"
+
+#define ASSERT_VARIABLE(x, expectedName, expectedValue, expectedType)         \
+  do {                                                                        \
+    ASSERT_TRUE(x.name == expectedName);                                      \
+    ASSERT_TRUE(x.value == expectedValue);                                    \
+    ASSERT_TRUE(x.type.value() == expectedType);                              \
+    ASSERT_TRUE(x.evaluateName.has_value() == false);                         \
+    if (std::string(expectedType) == "collection") {                          \
+      ASSERT_TRUE(x.variablesReference != 0);                                 \
+    }                                                                         \
+  } while (false)
+
+#define ASSERT_VARIABLE_REFERENCE(x, expectedName, expectedValue,             \
+                                  expectedType, expectedReference)            \
+  do {                                                                        \
+    ASSERT_VARIABLE(x, expectedName, expectedValue, expectedType);            \
+    ASSERT_TRUE(x.variablesReference == (expectedReference));                 \
+  } while (false)
+
+#define ASSERT_VARIABLE_REFERENCE_NOT_ZERO(x, expectedName, expectedValue,    \
+                                           expectedType)                      \
+  do {                                                                        \
+    ASSERT_VARIABLE(x, expectedName, expectedValue, expectedType);            \
+    ASSERT_TRUE(x.variablesReference != 0);                                   \
+  } while (false)
+
+#define ASSERT_BREAKPOINT(x, expectedId, expectedLine, sourcePath,            \
+                          isVerified)                                         \
+  do {                                                                        \
+    ASSERT_TRUE(x.id.has_value());                                            \
+    ASSERT_TRUE(x.id.value() == expectedId);                                  \
+    ASSERT_TRUE(x.line.has_value());                                          \
+    ASSERT_TRUE(x.line.value() == expectedLine);                              \
+    ASSERT_TRUE(x.source.has_value());                                        \
+    ASSERT_TRUE(x.source.value().path.has_value());                           \
+    ASSERT_TRUE(x.source.value().path.value() == sourcePath);                 \
+    ASSERT_TRUE(x.verified == isVerified);                                    \
+  } while (false)
+
+class DebuggerTestHelper
+{
+  std::shared_ptr<dap::ReaderWriter> Client2Debugger = dap::pipe();
+  std::shared_ptr<dap::ReaderWriter> Debugger2Client = dap::pipe();
+
+public:
+  std::unique_ptr<dap::Session> Client = dap::Session::create();
+  std::unique_ptr<dap::Session> Debugger = dap::Session::create();
+  void bind()
+  {
+    auto client2server = dap::pipe();
+    auto server2client = dap::pipe();
+    Client->bind(server2client, client2server);
+    Debugger->bind(client2server, server2client);
+  }
+  std::vector<cmListFileFunction> CreateListFileFunctions(const char* str,
+                                                          const char* filename)
+  {
+    cmMessenger messenger;
+    cmListFileBacktrace backtrace;
+    cmListFile listfile;
+    listfile.ParseString(str, filename, &messenger, backtrace);
+    return listfile.Functions;
+  }
+};
+
+class ScopedThread
+{
+public:
+  template <class... Args>
+  explicit ScopedThread(Args&&... args)
+    : Thread(std::forward<Args>(args)...)
+  {
+  }
+
+  ~ScopedThread()
+  {
+    if (Thread.joinable())
+      Thread.join();
+  }
+
+private:
+  std::thread Thread;
+};
diff --git a/Tests/CMakeLib/testDebuggerAdapter.cxx b/Tests/CMakeLib/testDebuggerAdapter.cxx
new file mode 100644
index 0000000..394986b
--- /dev/null
+++ b/Tests/CMakeLib/testDebuggerAdapter.cxx
@@ -0,0 +1,173 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include <chrono>
+#include <cstdio>
+#include <functional>
+#include <future>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <cm3p/cppdap/future.h>
+#include <cm3p/cppdap/io.h>
+#include <cm3p/cppdap/optional.h>
+#include <cm3p/cppdap/protocol.h>
+#include <cm3p/cppdap/session.h>
+#include <cm3p/cppdap/types.h>
+
+#include "cmDebuggerAdapter.h"
+#include "cmDebuggerProtocol.h"
+#include "cmVersionConfig.h"
+
+#include "testCommon.h"
+#include "testDebugger.h"
+
+class DebuggerLocalConnection : public cmDebugger::cmDebuggerConnection
+{
+public:
+  DebuggerLocalConnection()
+    : ClientToDebugger(dap::pipe())
+    , DebuggerToClient(dap::pipe())
+  {
+  }
+
+  bool StartListening(std::string& errorMessage) override
+  {
+    errorMessage = "";
+    return true;
+  }
+  void WaitForConnection() override {}
+
+  std::shared_ptr<dap::Reader> GetReader() override
+  {
+    return ClientToDebugger;
+  };
+
+  std::shared_ptr<dap::Writer> GetWriter() override
+  {
+    return DebuggerToClient;
+  }
+
+  std::shared_ptr<dap::ReaderWriter> ClientToDebugger;
+  std::shared_ptr<dap::ReaderWriter> DebuggerToClient;
+};
+
+bool testBasicProtocol()
+{
+  std::promise<bool> debuggerAdapterInitializedPromise;
+  std::future<bool> debuggerAdapterInitializedFuture =
+    debuggerAdapterInitializedPromise.get_future();
+
+  std::promise<bool> initializedEventReceivedPromise;
+  std::future<bool> initializedEventReceivedFuture =
+    initializedEventReceivedPromise.get_future();
+
+  std::promise<bool> exitedEventReceivedPromise;
+  std::future<bool> exitedEventReceivedFuture =
+    exitedEventReceivedPromise.get_future();
+
+  std::promise<bool> terminatedEventReceivedPromise;
+  std::future<bool> terminatedEventReceivedFuture =
+    terminatedEventReceivedPromise.get_future();
+
+  std::promise<bool> threadStartedPromise;
+  std::future<bool> threadStartedFuture = threadStartedPromise.get_future();
+
+  std::promise<bool> threadExitedPromise;
+  std::future<bool> threadExitedFuture = threadExitedPromise.get_future();
+
+  std::promise<bool> disconnectResponseReceivedPromise;
+  std::future<bool> disconnectResponseReceivedFuture =
+    disconnectResponseReceivedPromise.get_future();
+
+  auto futureTimeout = std::chrono::seconds(60);
+
+  auto connection = std::make_shared<DebuggerLocalConnection>();
+  std::unique_ptr<dap::Session> client = dap::Session::create();
+  client->registerHandler([&](const dap::InitializedEvent& e) {
+    (void)e;
+    initializedEventReceivedPromise.set_value(true);
+  });
+  client->registerHandler([&](const dap::ExitedEvent& e) {
+    (void)e;
+    exitedEventReceivedPromise.set_value(true);
+  });
+  client->registerHandler([&](const dap::TerminatedEvent& e) {
+    (void)e;
+    terminatedEventReceivedPromise.set_value(true);
+  });
+  client->registerHandler([&](const dap::ThreadEvent& e) {
+    if (e.reason == "started") {
+      threadStartedPromise.set_value(true);
+    } else if (e.reason == "exited") {
+      threadExitedPromise.set_value(true);
+    }
+  });
+
+  client->bind(connection->DebuggerToClient, connection->ClientToDebugger);
+
+  ScopedThread debuggerThread([&]() -> int {
+    std::shared_ptr<cmDebugger::cmDebuggerAdapter> debuggerAdapter =
+      std::make_shared<cmDebugger::cmDebuggerAdapter>(
+        connection, dap::file(stdout, false));
+
+    debuggerAdapterInitializedPromise.set_value(true);
+    debuggerAdapter->ReportExitCode(0);
+
+    // Ensure the disconnectResponse has been received before
+    // destructing debuggerAdapter.
+    ASSERT_TRUE(disconnectResponseReceivedFuture.wait_for(futureTimeout) ==
+                std::future_status::ready);
+    return 0;
+  });
+
+  dap::CMakeInitializeRequest initializeRequest;
+  auto initializeResponse = client->send(initializeRequest).get();
+  ASSERT_TRUE(initializeResponse.response.cmakeVersion.full == CMake_VERSION);
+  ASSERT_TRUE(initializeResponse.response.cmakeVersion.major ==
+              CMake_VERSION_MAJOR);
+  ASSERT_TRUE(initializeResponse.response.cmakeVersion.minor ==
+              CMake_VERSION_MINOR);
+  ASSERT_TRUE(initializeResponse.response.cmakeVersion.patch ==
+              CMake_VERSION_PATCH);
+  ASSERT_TRUE(initializeResponse.response.supportsExceptionInfoRequest);
+  ASSERT_TRUE(
+    initializeResponse.response.exceptionBreakpointFilters.has_value());
+
+  dap::LaunchRequest launchRequest;
+  auto launchResponse = client->send(launchRequest).get();
+  ASSERT_TRUE(!launchResponse.error);
+
+  dap::ConfigurationDoneRequest configurationDoneRequest;
+  auto configurationDoneResponse =
+    client->send(configurationDoneRequest).get();
+  ASSERT_TRUE(!configurationDoneResponse.error);
+
+  ASSERT_TRUE(debuggerAdapterInitializedFuture.wait_for(futureTimeout) ==
+              std::future_status::ready);
+  ASSERT_TRUE(initializedEventReceivedFuture.wait_for(futureTimeout) ==
+              std::future_status::ready);
+  ASSERT_TRUE(threadStartedFuture.wait_for(futureTimeout) ==
+              std::future_status::ready);
+  ASSERT_TRUE(threadExitedFuture.wait_for(futureTimeout) ==
+              std::future_status::ready);
+  ASSERT_TRUE(exitedEventReceivedFuture.wait_for(futureTimeout) ==
+              std::future_status::ready);
+  ASSERT_TRUE(terminatedEventReceivedFuture.wait_for(futureTimeout) ==
+              std::future_status::ready);
+
+  dap::DisconnectRequest disconnectRequest;
+  auto disconnectResponse = client->send(disconnectRequest).get();
+  disconnectResponseReceivedPromise.set_value(true);
+  ASSERT_TRUE(!disconnectResponse.error);
+
+  return true;
+}
+
+int testDebuggerAdapter(int, char*[])
+{
+  return runTests(std::vector<std::function<bool()>>{
+    testBasicProtocol,
+  });
+}
diff --git a/Tests/CMakeLib/testDebuggerAdapterPipe.cxx b/Tests/CMakeLib/testDebuggerAdapterPipe.cxx
new file mode 100644
index 0000000..643661d
--- /dev/null
+++ b/Tests/CMakeLib/testDebuggerAdapterPipe.cxx
@@ -0,0 +1,184 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include <chrono>
+#include <cstdio>
+#include <functional>
+#include <future>
+#include <iostream>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include <cm3p/cppdap/future.h>
+#include <cm3p/cppdap/io.h>
+#include <cm3p/cppdap/optional.h>
+#include <cm3p/cppdap/protocol.h>
+#include <cm3p/cppdap/session.h>
+#include <cm3p/cppdap/types.h>
+
+#include "cmDebuggerAdapter.h"
+#include "cmDebuggerPipeConnection.h"
+#include "cmDebuggerProtocol.h"
+#include "cmVersionConfig.h"
+
+#ifdef _WIN32
+#  include "cmCryptoHash.h"
+#  include "cmSystemTools.h"
+#endif
+
+#include "testCommon.h"
+#include "testDebugger.h"
+
+bool testProtocolWithPipes()
+{
+  std::promise<void> debuggerConnectionCreatedPromise;
+  std::future<void> debuggerConnectionCreatedFuture =
+    debuggerConnectionCreatedPromise.get_future();
+
+  std::future<void> startedListeningFuture;
+
+  std::promise<bool> debuggerAdapterInitializedPromise;
+  std::future<bool> debuggerAdapterInitializedFuture =
+    debuggerAdapterInitializedPromise.get_future();
+
+  std::promise<bool> initializedEventReceivedPromise;
+  std::future<bool> initializedEventReceivedFuture =
+    initializedEventReceivedPromise.get_future();
+
+  std::promise<bool> exitedEventReceivedPromise;
+  std::future<bool> exitedEventReceivedFuture =
+    exitedEventReceivedPromise.get_future();
+
+  std::promise<bool> terminatedEventReceivedPromise;
+  std::future<bool> terminatedEventReceivedFuture =
+    terminatedEventReceivedPromise.get_future();
+
+  std::promise<bool> threadStartedPromise;
+  std::future<bool> threadStartedFuture = threadStartedPromise.get_future();
+
+  std::promise<bool> threadExitedPromise;
+  std::future<bool> threadExitedFuture = threadExitedPromise.get_future();
+
+  std::promise<bool> disconnectResponseReceivedPromise;
+  std::future<bool> disconnectResponseReceivedFuture =
+    disconnectResponseReceivedPromise.get_future();
+
+  auto futureTimeout = std::chrono::seconds(60);
+
+#ifdef _WIN32
+  std::string namedPipe = R"(\\.\pipe\LOCAL\CMakeDebuggerPipe2_)" +
+    cmCryptoHash(cmCryptoHash::AlgoSHA256)
+      .HashString(cmSystemTools::GetCurrentWorkingDirectory());
+#else
+  std::string namedPipe = "CMakeDebuggerPipe2";
+#endif
+
+  std::unique_ptr<dap::Session> client = dap::Session::create();
+  client->registerHandler([&](const dap::InitializedEvent& e) {
+    (void)e;
+    initializedEventReceivedPromise.set_value(true);
+  });
+  client->registerHandler([&](const dap::ExitedEvent& e) {
+    (void)e;
+    exitedEventReceivedPromise.set_value(true);
+  });
+  client->registerHandler([&](const dap::TerminatedEvent& e) {
+    (void)e;
+    terminatedEventReceivedPromise.set_value(true);
+  });
+  client->registerHandler([&](const dap::ThreadEvent& e) {
+    if (e.reason == "started") {
+      threadStartedPromise.set_value(true);
+    } else if (e.reason == "exited") {
+      threadExitedPromise.set_value(true);
+    }
+  });
+
+  ScopedThread debuggerThread([&]() -> int {
+    try {
+      auto connection =
+        std::make_shared<cmDebugger::cmDebuggerPipeConnection>(namedPipe);
+      startedListeningFuture = connection->StartedListening.get_future();
+      debuggerConnectionCreatedPromise.set_value();
+      std::shared_ptr<cmDebugger::cmDebuggerAdapter> debuggerAdapter =
+        std::make_shared<cmDebugger::cmDebuggerAdapter>(
+          connection, dap::file(stdout, false));
+
+      debuggerAdapterInitializedPromise.set_value(true);
+      debuggerAdapter->ReportExitCode(0);
+
+      // Ensure the disconnectResponse has been received before
+      // destructing debuggerAdapter.
+      ASSERT_TRUE(disconnectResponseReceivedFuture.wait_for(futureTimeout) ==
+                  std::future_status::ready);
+      return 0;
+    } catch (const std::runtime_error& error) {
+      std::cerr << "Error: Failed to create debugger adapter.\n";
+      std::cerr << error.what() << "\n";
+      return -1;
+    }
+  });
+
+  ASSERT_TRUE(debuggerConnectionCreatedFuture.wait_for(futureTimeout) ==
+              std::future_status::ready);
+  ASSERT_TRUE(startedListeningFuture.wait_for(futureTimeout) ==
+              std::future_status::ready);
+
+  auto client2Debugger =
+    std::make_shared<cmDebugger::cmDebuggerPipeClient>(namedPipe);
+  client2Debugger->Start();
+  client2Debugger->WaitForConnection();
+  client->bind(client2Debugger, client2Debugger);
+
+  dap::CMakeInitializeRequest initializeRequest;
+  auto response = client->send(initializeRequest);
+  auto initializeResponse = response.get();
+  ASSERT_TRUE(!initializeResponse.error);
+  ASSERT_TRUE(initializeResponse.response.cmakeVersion.full == CMake_VERSION);
+  ASSERT_TRUE(initializeResponse.response.cmakeVersion.major ==
+              CMake_VERSION_MAJOR);
+  ASSERT_TRUE(initializeResponse.response.cmakeVersion.minor ==
+              CMake_VERSION_MINOR);
+  ASSERT_TRUE(initializeResponse.response.cmakeVersion.patch ==
+              CMake_VERSION_PATCH);
+  ASSERT_TRUE(initializeResponse.response.supportsExceptionInfoRequest);
+  ASSERT_TRUE(
+    initializeResponse.response.exceptionBreakpointFilters.has_value());
+  dap::LaunchRequest launchRequest;
+  auto launchResponse = client->send(launchRequest).get();
+  ASSERT_TRUE(!launchResponse.error);
+
+  dap::ConfigurationDoneRequest configurationDoneRequest;
+  auto configurationDoneResponse =
+    client->send(configurationDoneRequest).get();
+  ASSERT_TRUE(!configurationDoneResponse.error);
+
+  ASSERT_TRUE(debuggerAdapterInitializedFuture.wait_for(futureTimeout) ==
+              std::future_status::ready);
+  ASSERT_TRUE(initializedEventReceivedFuture.wait_for(futureTimeout) ==
+              std::future_status::ready);
+  ASSERT_TRUE(terminatedEventReceivedFuture.wait_for(futureTimeout) ==
+              std::future_status::ready);
+  ASSERT_TRUE(threadStartedFuture.wait_for(futureTimeout) ==
+              std::future_status::ready);
+  ASSERT_TRUE(threadExitedFuture.wait_for(futureTimeout) ==
+              std::future_status::ready);
+  ASSERT_TRUE(exitedEventReceivedFuture.wait_for(futureTimeout) ==
+              std::future_status::ready);
+
+  dap::DisconnectRequest disconnectRequest;
+  auto disconnectResponse = client->send(disconnectRequest).get();
+  disconnectResponseReceivedPromise.set_value(true);
+  ASSERT_TRUE(!disconnectResponse.error);
+
+  return true;
+}
+
+int testDebuggerAdapterPipe(int, char*[])
+{
+  return runTests(std::vector<std::function<bool()>>{
+    testProtocolWithPipes,
+  });
+}
diff --git a/Tests/CMakeLib/testDebuggerBreakpointManager.cxx b/Tests/CMakeLib/testDebuggerBreakpointManager.cxx
new file mode 100644
index 0000000..83734ea
--- /dev/null
+++ b/Tests/CMakeLib/testDebuggerBreakpointManager.cxx
@@ -0,0 +1,172 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include <atomic>
+#include <chrono>
+#include <functional>
+#include <future>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <cm3p/cppdap/future.h>
+#include <cm3p/cppdap/optional.h>
+#include <cm3p/cppdap/protocol.h>
+#include <cm3p/cppdap/session.h>
+#include <cm3p/cppdap/types.h>
+
+#include "cmDebuggerBreakpointManager.h"
+#include "cmDebuggerSourceBreakpoint.h" // IWYU pragma: keep
+#include "cmListFileCache.h"
+
+#include "testCommon.h"
+#include "testDebugger.h"
+
+static bool testHandleBreakpointRequestBeforeFileIsLoaded()
+{
+  // Arrange
+  DebuggerTestHelper helper;
+  cmDebugger::cmDebuggerBreakpointManager breakpointManager(
+    helper.Debugger.get());
+  helper.bind();
+  dap::SetBreakpointsRequest setBreakpointRequest;
+  std::string sourcePath = "C:/CMakeLists.txt";
+  setBreakpointRequest.source.path = sourcePath;
+  dap::array<dap::SourceBreakpoint> sourceBreakpoints(3);
+  sourceBreakpoints[0].line = 1;
+  sourceBreakpoints[1].line = 2;
+  sourceBreakpoints[2].line = 3;
+  setBreakpointRequest.breakpoints = sourceBreakpoints;
+
+  // Act
+  auto got = helper.Client->send(setBreakpointRequest).get();
+
+  // Assert
+  auto& response = got.response;
+  ASSERT_TRUE(!got.error);
+  ASSERT_TRUE(response.breakpoints.size() == sourceBreakpoints.size());
+  ASSERT_BREAKPOINT(response.breakpoints[0], 0, sourceBreakpoints[0].line,
+                    sourcePath, false);
+  ASSERT_BREAKPOINT(response.breakpoints[1], 1, sourceBreakpoints[1].line,
+                    sourcePath, false);
+  ASSERT_BREAKPOINT(response.breakpoints[2], 2, sourceBreakpoints[2].line,
+                    sourcePath, false);
+  return true;
+}
+
+static bool testHandleBreakpointRequestAfterFileIsLoaded()
+{
+  // Arrange
+  DebuggerTestHelper helper;
+  std::atomic<bool> notExpectBreakpointEvents(true);
+  helper.Client->registerHandler([&](const dap::BreakpointEvent&) {
+    notExpectBreakpointEvents.store(false);
+  });
+
+  cmDebugger::cmDebuggerBreakpointManager breakpointManager(
+    helper.Debugger.get());
+  helper.bind();
+  std::string sourcePath = "C:/CMakeLists.txt";
+  std::vector<cmListFileFunction> functions = helper.CreateListFileFunctions(
+    "# Comment1\nset(var1 foo)\n# Comment2\nset(var2\nbar)\n",
+    sourcePath.c_str());
+
+  breakpointManager.SourceFileLoaded(sourcePath, functions);
+  dap::SetBreakpointsRequest setBreakpointRequest;
+  setBreakpointRequest.source.path = sourcePath;
+  dap::array<dap::SourceBreakpoint> sourceBreakpoints(5);
+  sourceBreakpoints[0].line = 1;
+  sourceBreakpoints[1].line = 2;
+  sourceBreakpoints[2].line = 3;
+  sourceBreakpoints[3].line = 4;
+  sourceBreakpoints[4].line = 5;
+  setBreakpointRequest.breakpoints = sourceBreakpoints;
+
+  // Act
+  auto got = helper.Client->send(setBreakpointRequest).get();
+
+  // Assert
+  auto& response = got.response;
+  ASSERT_TRUE(!got.error);
+  ASSERT_TRUE(response.breakpoints.size() == sourceBreakpoints.size());
+  // Line 1 is a comment. Move it to next valid function, which is line 2.
+  ASSERT_BREAKPOINT(response.breakpoints[0], 0, 2, sourcePath, true);
+  ASSERT_BREAKPOINT(response.breakpoints[1], 1, sourceBreakpoints[1].line,
+                    sourcePath, true);
+  // Line 3 is a comment. Move it to next valid function, which is line 4.
+  ASSERT_BREAKPOINT(response.breakpoints[2], 2, 4, sourcePath, true);
+  ASSERT_BREAKPOINT(response.breakpoints[3], 3, sourceBreakpoints[3].line,
+                    sourcePath, true);
+  // Line 5 is the 2nd part of line 4 function. No valid function after line 5,
+  // show the breakpoint at line 4.
+  ASSERT_BREAKPOINT(response.breakpoints[4], 4, sourceBreakpoints[3].line,
+                    sourcePath, true);
+
+  ASSERT_TRUE(notExpectBreakpointEvents.load());
+
+  return true;
+}
+
+static bool testSourceFileLoadedAfterHandleBreakpointRequest()
+{
+  // Arrange
+  DebuggerTestHelper helper;
+  std::vector<dap::BreakpointEvent> breakpointEvents;
+  std::atomic<int> remainingBreakpointEvents(5);
+  std::promise<void> allBreakpointEventsReceivedPromise;
+  std::future<void> allBreakpointEventsReceivedFuture =
+    allBreakpointEventsReceivedPromise.get_future();
+  helper.Client->registerHandler([&](const dap::BreakpointEvent& event) {
+    breakpointEvents.emplace_back(event);
+    if (--remainingBreakpointEvents == 0) {
+      allBreakpointEventsReceivedPromise.set_value();
+    }
+  });
+  cmDebugger::cmDebuggerBreakpointManager breakpointManager(
+    helper.Debugger.get());
+  helper.bind();
+  dap::SetBreakpointsRequest setBreakpointRequest;
+  std::string sourcePath = "C:/CMakeLists.txt";
+  setBreakpointRequest.source.path = sourcePath;
+  dap::array<dap::SourceBreakpoint> sourceBreakpoints(5);
+  sourceBreakpoints[0].line = 1;
+  sourceBreakpoints[1].line = 2;
+  sourceBreakpoints[2].line = 3;
+  sourceBreakpoints[3].line = 4;
+  sourceBreakpoints[4].line = 5;
+  setBreakpointRequest.breakpoints = sourceBreakpoints;
+  std::vector<cmListFileFunction> functions = helper.CreateListFileFunctions(
+    "# Comment1\nset(var1 foo)\n# Comment2\nset(var2\nbar)\n",
+    sourcePath.c_str());
+  auto got = helper.Client->send(setBreakpointRequest).get();
+
+  // Act
+  breakpointManager.SourceFileLoaded(sourcePath, functions);
+  ASSERT_TRUE(allBreakpointEventsReceivedFuture.wait_for(
+                std::chrono::seconds(10)) == std::future_status::ready);
+
+  // Assert
+  ASSERT_TRUE(breakpointEvents.size() > 0);
+  // Line 1 is a comment. Move it to next valid function, which is line 2.
+  ASSERT_BREAKPOINT(breakpointEvents[0].breakpoint, 0, 2, sourcePath, true);
+  ASSERT_BREAKPOINT(breakpointEvents[1].breakpoint, 1,
+                    sourceBreakpoints[1].line, sourcePath, true);
+  // Line 3 is a comment. Move it to next valid function, which is line 4.
+  ASSERT_BREAKPOINT(breakpointEvents[2].breakpoint, 2, 4, sourcePath, true);
+  ASSERT_BREAKPOINT(breakpointEvents[3].breakpoint, 3,
+                    sourceBreakpoints[3].line, sourcePath, true);
+  // Line 5 is the 2nd part of line 4 function. No valid function after line 5,
+  // show the breakpoint at line 4.
+  ASSERT_BREAKPOINT(breakpointEvents[4].breakpoint, 4,
+                    sourceBreakpoints[3].line, sourcePath, true);
+  return true;
+}
+
+int testDebuggerBreakpointManager(int, char*[])
+{
+  return runTests(std::vector<std::function<bool()>>{
+    testHandleBreakpointRequestBeforeFileIsLoaded,
+    testHandleBreakpointRequestAfterFileIsLoaded,
+    testSourceFileLoadedAfterHandleBreakpointRequest,
+  });
+}
diff --git a/Tests/CMakeLib/testDebuggerNamedPipe.cxx b/Tests/CMakeLib/testDebuggerNamedPipe.cxx
new file mode 100644
index 0000000..d2b0728
--- /dev/null
+++ b/Tests/CMakeLib/testDebuggerNamedPipe.cxx
@@ -0,0 +1,218 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include <chrono>
+#include <cstdio>
+#include <exception>
+#include <iostream>
+#include <memory>
+#include <sstream>
+#include <stdexcept>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include <cm3p/cppdap/io.h>
+
+#include "cmsys/RegularExpression.hxx"
+
+#include "cmDebuggerPipeConnection.h"
+#include "cmSystemTools.h"
+
+#ifdef _WIN32
+#  include "cmCryptoHash.h"
+#endif
+
+static void sendCommands(std::shared_ptr<dap::ReaderWriter> const& debugger,
+                         int delayMs,
+                         std::vector<std::string> const& initCommands)
+{
+  for (const auto& command : initCommands) {
+    std::string contentLength = "Content-Length:";
+    contentLength += std::to_string(command.size()) + "\r\n\r\n";
+    debugger->write(contentLength.c_str(), contentLength.size());
+    if (!debugger->write(command.c_str(), command.size())) {
+      std::cout << "debugger write error" << std::endl;
+      break;
+    }
+    std::this_thread::sleep_for(std::chrono::milliseconds(delayMs));
+  }
+}
+
+/** \brief Test CMake debugger named pipe.
+ *
+ * Test CMake debugger named pipe by
+ * 1. Create a named pipe for DAP traffic between the client and the debugger.
+ * 2. Create a client thread to wait for the debugger connection.
+ *    - Once the debugger is connected, send the minimum required commands to
+ *      get debugger going.
+ *    - Wait for the CMake to complete the cache generation
+ *    - Send the disconnect command.
+ *    - Read and store the debugger's responses for validation.
+ * 3. Run the CMake command with debugger on and wait for it to complete.
+ * 4. Validate the response to ensure we are getting the expected responses.
+ *
+ */
+int runTest(int argc, char* argv[])
+{
+  if (argc < 3) {
+    std::cout << "Usage:\n";
+    std::cout << "\t(project mode) TestDebuggerNamedPipe <CMakePath> "
+                 "<SourceFolder> <OutputFolder>\n";
+    std::cout << "\t(script mode) TestDebuggerNamedPipe <CMakePath> "
+                 "<ScriptPath>\n";
+    return 1;
+  }
+
+  bool scriptMode = argc == 3;
+
+#ifdef _WIN32
+  std::string namedPipe = R"(\\.\pipe\LOCAL\CMakeDebuggerPipe_)" +
+    cmCryptoHash(cmCryptoHash::AlgoSHA256)
+      .HashString(scriptMode ? argv[2] : argv[3]);
+#else
+  std::string namedPipe =
+    std::string("CMakeDebuggerPipe") + (scriptMode ? "Script" : "Project");
+#endif
+
+  std::vector<std::string> cmakeCommand;
+  cmakeCommand.emplace_back(argv[1]);
+  cmakeCommand.emplace_back("--debugger");
+  cmakeCommand.emplace_back("--debugger-pipe");
+  cmakeCommand.emplace_back(namedPipe);
+
+  if (scriptMode) {
+    cmakeCommand.emplace_back("-P");
+    cmakeCommand.emplace_back(argv[2]);
+  } else {
+    cmakeCommand.emplace_back("-S");
+    cmakeCommand.emplace_back(argv[2]);
+    cmakeCommand.emplace_back("-B");
+    cmakeCommand.emplace_back(argv[3]);
+  }
+
+  // Capture debugger response stream.
+  std::stringstream debuggerResponseStream;
+
+  // Start the debugger client process.
+  std::thread clientThread([&]() {
+    // Poll until the pipe server is running. Clients can also look for a magic
+    // string in the CMake output, but this is easier for the test case.
+    std::shared_ptr<cmDebugger::cmDebuggerPipeClient> client;
+    int attempt = 0;
+    do {
+      attempt++;
+      try {
+        client = std::make_shared<cmDebugger::cmDebuggerPipeClient>(namedPipe);
+        client->Start();
+        client->WaitForConnection();
+        std::cout << "cmDebuggerPipeClient connected.\n";
+        break;
+      } catch (std::runtime_error&) {
+        std::cout << "Failed attempt " << attempt
+                  << " to connect to pipe server. Retrying.\n";
+        client.reset();
+        std::this_thread::sleep_for(std::chrono::milliseconds(200));
+      }
+    } while (attempt < 50); // 10 seconds
+
+    if (attempt >= 50) {
+      return -1;
+    }
+
+    // Send init commands to get debugger going.
+    sendCommands(
+      client, 400,
+      { "{\"arguments\":{\"adapterID\":\"\"},\"command\":\"initialize\","
+        "\"seq\":"
+        "1,\"type\":\"request\"}",
+        "{\"arguments\":{},\"command\":\"launch\",\"seq\":2,\"type\":"
+        "\"request\"}",
+        "{\"arguments\":{},\"command\":\"configurationDone\",\"seq\":3,"
+        "\"type\":"
+        "\"request\"}" });
+
+    // Look for "exitCode" as a sign that configuration has completed and
+    // it's now safe to disconnect.
+    for (;;) {
+      char buffer[1];
+      size_t result = client->read(buffer, 1);
+      if (result != 1) {
+        std::cout << "debugger read error: " << result << std::endl;
+        break;
+      }
+      debuggerResponseStream << buffer[0];
+      if (debuggerResponseStream.str().find("exitCode") != std::string::npos) {
+        break;
+      }
+    }
+
+    // Send disconnect command.
+    sendCommands(
+      client, 200,
+      { "{\"arguments\":{},\"command\":\"disconnect\",\"seq\":4,\"type\":"
+        "\"request\"}" });
+
+    // Read any remaining debugger responses.
+    for (;;) {
+      char buffer[1];
+      size_t result = client->read(buffer, 1);
+      if (result != 1) {
+        std::cout << "debugger read error: " << result << std::endl;
+        break;
+      }
+      debuggerResponseStream << buffer[0];
+    }
+
+    client->close();
+
+    return 0;
+  });
+
+  if (!cmSystemTools::RunSingleCommand(cmakeCommand, nullptr, nullptr, nullptr,
+                                       nullptr, cmSystemTools::OUTPUT_MERGE)) {
+    std::cout << "Error running command" << std::endl;
+    return -1;
+  }
+
+  clientThread.join();
+
+  auto debuggerResponse = debuggerResponseStream.str();
+
+  std::vector<std::string> expectedResponses = {
+    R"("event" : "initialized".*"type" : "event")",
+    R"("command" : "launch".*"success" : true.*"type" : "response")",
+    R"("command" : "configurationDone".*"success" : true.*"type" : "response")",
+    R"("reason" : "started".*"threadId" : 1.*"event" : "thread".*"type" : "event")",
+    R"("reason" : "exited".*"threadId" : 1.*"event" : "thread".*"type" : "event")",
+    R"("exitCode" : 0.*"event" : "exited".*"type" : "event")",
+    R"("command" : "disconnect".*"success" : true.*"type" : "response")"
+  };
+
+  for (auto& regexString : expectedResponses) {
+    cmsys::RegularExpression regex(regexString);
+    if (!regex.find(debuggerResponse)) {
+      std::cout << "Expected response not found: " << regexString << std::endl;
+      std::cout << debuggerResponse << std::endl;
+      return -1;
+    }
+  }
+
+  return 0;
+}
+
+int main(int argc, char* argv[])
+{
+  try {
+    return runTest(argc, argv);
+  } catch (const std::exception& ex) {
+    std::cout << "An exception occurred: " << ex.what() << std::endl;
+    return -1;
+  } catch (const std::string& ex) {
+    std::cout << "An exception occurred: " << ex << std::endl;
+    return -1;
+  } catch (...) {
+    std::cout << "An unknown exception occurred" << std::endl;
+    return -1;
+  }
+}
diff --git a/Tests/CMakeLib/testDebuggerVariables.cxx b/Tests/CMakeLib/testDebuggerVariables.cxx
new file mode 100644
index 0000000..6c19baa
--- /dev/null
+++ b/Tests/CMakeLib/testDebuggerVariables.cxx
@@ -0,0 +1,185 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include <cm3p/cppdap/protocol.h>
+#include <cm3p/cppdap/types.h>
+
+#include "cmDebuggerVariables.h"
+#include "cmDebuggerVariablesManager.h"
+
+#include "testCommon.h"
+#include "testDebugger.h"
+
+static dap::VariablesRequest CreateVariablesRequest(int64_t reference)
+{
+  dap::VariablesRequest variableRequest;
+  variableRequest.variablesReference = reference;
+  return variableRequest;
+}
+
+static bool testUniqueIds()
+{
+  auto variablesManager =
+    std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+  std::unordered_set<int64_t> variableIds;
+  bool noDuplicateIds = true;
+  for (int i = 0; i < 10000 && noDuplicateIds; ++i) {
+    auto variable =
+      cmDebugger::cmDebuggerVariables(variablesManager, "Locals", true, []() {
+        return std::vector<cmDebugger::cmDebuggerVariableEntry>();
+      });
+
+    if (variableIds.find(variable.GetId()) != variableIds.end()) {
+      noDuplicateIds = false;
+    }
+    variableIds.insert(variable.GetId());
+  }
+
+  ASSERT_TRUE(noDuplicateIds);
+
+  return true;
+}
+
+static bool testConstructors()
+{
+  auto variablesManager =
+    std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+  auto parent = std::make_shared<cmDebugger::cmDebuggerVariables>(
+    variablesManager, "Parent", true, [=]() {
+      return std::vector<cmDebugger::cmDebuggerVariableEntry>{
+        { "ParentKey", "ParentValue", "ParentType" }
+      };
+    });
+
+  auto children1 = std::make_shared<cmDebugger::cmDebuggerVariables>(
+    variablesManager, "Children1", true, [=]() {
+      return std::vector<cmDebugger::cmDebuggerVariableEntry>{
+        { "ChildKey1", "ChildValue1", "ChildType1" },
+        { "ChildKey2", "ChildValue2", "ChildType2" }
+      };
+    });
+
+  parent->AddSubVariables(children1);
+
+  auto children2 = std::make_shared<cmDebugger::cmDebuggerVariables>(
+    variablesManager, "Children2", true);
+
+  auto grandChildren21 = std::make_shared<cmDebugger::cmDebuggerVariables>(
+    variablesManager, "GrandChildren21", true);
+  grandChildren21->SetValue("GrandChildren21 Value");
+  children2->AddSubVariables(grandChildren21);
+  parent->AddSubVariables(children2);
+
+  dap::array<dap::Variable> variables =
+    variablesManager->HandleVariablesRequest(
+      CreateVariablesRequest(parent->GetId()));
+  ASSERT_TRUE(variables.size() == 3);
+  ASSERT_VARIABLE_REFERENCE(variables[0], "Children1", "", "collection",
+                            children1->GetId());
+  ASSERT_VARIABLE_REFERENCE(variables[1], "Children2", "", "collection",
+                            children2->GetId());
+  ASSERT_VARIABLE(variables[2], "ParentKey", "ParentValue", "ParentType");
+
+  variables = variablesManager->HandleVariablesRequest(
+    CreateVariablesRequest(children1->GetId()));
+  ASSERT_TRUE(variables.size() == 2);
+  ASSERT_VARIABLE(variables[0], "ChildKey1", "ChildValue1", "ChildType1");
+  ASSERT_VARIABLE(variables[1], "ChildKey2", "ChildValue2", "ChildType2");
+
+  variables = variablesManager->HandleVariablesRequest(
+    CreateVariablesRequest(children2->GetId()));
+  ASSERT_TRUE(variables.size() == 1);
+  ASSERT_VARIABLE_REFERENCE(variables[0], "GrandChildren21",
+                            "GrandChildren21 Value", "collection",
+                            grandChildren21->GetId());
+
+  return true;
+}
+
+static bool testIgnoreEmptyStringEntries()
+{
+  auto variablesManager =
+    std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+  auto vars = std::make_shared<cmDebugger::cmDebuggerVariables>(
+    variablesManager, "Variables", true, []() {
+      return std::vector<cmDebugger::cmDebuggerVariableEntry>{
+        { "IntValue1", 5 },           { "StringValue1", "" },
+        { "StringValue2", "foo" },    { "StringValue3", "" },
+        { "StringValue4", "bar" },    { "StringValue5", "" },
+        { "IntValue2", int64_t(99) }, { "BooleanTrue", true },
+        { "BooleanFalse", false },
+      };
+    });
+
+  vars->SetIgnoreEmptyStringEntries(true);
+  vars->SetEnableSorting(false);
+
+  dap::array<dap::Variable> variables =
+    variablesManager->HandleVariablesRequest(
+      CreateVariablesRequest(vars->GetId()));
+  ASSERT_TRUE(variables.size() == 6);
+  ASSERT_VARIABLE(variables[0], "IntValue1", "5", "int");
+  ASSERT_VARIABLE(variables[1], "StringValue2", "foo", "string");
+  ASSERT_VARIABLE(variables[2], "StringValue4", "bar", "string");
+  ASSERT_VARIABLE(variables[3], "IntValue2", "99", "int");
+  ASSERT_VARIABLE(variables[4], "BooleanTrue", "TRUE", "bool");
+  ASSERT_VARIABLE(variables[5], "BooleanFalse", "FALSE", "bool");
+
+  return true;
+}
+
+static bool testSortTheResult()
+{
+  auto variablesManager =
+    std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+  auto vars = std::make_shared<cmDebugger::cmDebuggerVariables>(
+    variablesManager, "Variables", true, []() {
+      return std::vector<cmDebugger::cmDebuggerVariableEntry>{
+        { "4", "4" }, { "2", "2" }, { "1", "1" }, { "3", "3" }, { "5", "5" },
+      };
+    });
+
+  dap::array<dap::Variable> variables =
+    variablesManager->HandleVariablesRequest(
+      CreateVariablesRequest(vars->GetId()));
+  ASSERT_TRUE(variables.size() == 5);
+  ASSERT_VARIABLE(variables[0], "1", "1", "string");
+  ASSERT_VARIABLE(variables[1], "2", "2", "string");
+  ASSERT_VARIABLE(variables[2], "3", "3", "string");
+  ASSERT_VARIABLE(variables[3], "4", "4", "string");
+  ASSERT_VARIABLE(variables[4], "5", "5", "string");
+
+  vars->SetEnableSorting(false);
+
+  variables = variablesManager->HandleVariablesRequest(
+    CreateVariablesRequest(vars->GetId()));
+  ASSERT_TRUE(variables.size() == 5);
+  ASSERT_VARIABLE(variables[0], "4", "4", "string");
+  ASSERT_VARIABLE(variables[1], "2", "2", "string");
+  ASSERT_VARIABLE(variables[2], "1", "1", "string");
+  ASSERT_VARIABLE(variables[3], "3", "3", "string");
+  ASSERT_VARIABLE(variables[4], "5", "5", "string");
+
+  return true;
+}
+
+int testDebuggerVariables(int, char*[])
+{
+  return runTests(std::vector<std::function<bool()>>{
+    testUniqueIds,
+    testConstructors,
+    testIgnoreEmptyStringEntries,
+    testSortTheResult,
+  });
+}
diff --git a/Tests/CMakeLib/testDebuggerVariablesHelper.cxx b/Tests/CMakeLib/testDebuggerVariablesHelper.cxx
new file mode 100644
index 0000000..e0bbdf0
--- /dev/null
+++ b/Tests/CMakeLib/testDebuggerVariablesHelper.cxx
@@ -0,0 +1,587 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include <functional>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <cm3p/cppdap/protocol.h>
+#include <cm3p/cppdap/types.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "cmDebuggerStackFrame.h"
+#include "cmDebuggerVariables.h"
+#include "cmDebuggerVariablesHelper.h"
+#include "cmDebuggerVariablesManager.h"
+#include "cmFileSet.h"
+#include "cmGlobalGenerator.h"
+#include "cmListFileCache.h"
+#include "cmMakefile.h"
+#include "cmPolicies.h"
+#include "cmPropertyMap.h"
+#include "cmState.h"
+#include "cmStateDirectory.h"
+#include "cmStateSnapshot.h"
+#include "cmStateTypes.h"
+#include "cmTarget.h"
+#include "cmTest.h"
+#include "cmake.h"
+
+#include "testCommon.h"
+#include "testDebugger.h"
+
+static dap::VariablesRequest CreateVariablesRequest(int64_t reference)
+{
+  dap::VariablesRequest variableRequest;
+  variableRequest.variablesReference = reference;
+  return variableRequest;
+}
+
+struct Dummies
+{
+  std::shared_ptr<cmake> CMake;
+  std::shared_ptr<cmMakefile> Makefile;
+  std::shared_ptr<cmGlobalGenerator> GlobalGenerator;
+};
+
+static Dummies CreateDummies(
+  std::string targetName,
+  std::string currentSourceDirectory = "c:/CurrentSourceDirectory",
+  std::string currentBinaryDirectory = "c:/CurrentBinaryDirectory")
+{
+  Dummies dummies;
+  dummies.CMake =
+    std::make_shared<cmake>(cmake::RoleProject, cmState::Project);
+  cmState* state = dummies.CMake->GetState();
+  dummies.GlobalGenerator =
+    std::make_shared<cmGlobalGenerator>(dummies.CMake.get());
+  cmStateSnapshot snapshot = state->CreateBaseSnapshot();
+  snapshot.GetDirectory().SetCurrentSource(currentSourceDirectory);
+  snapshot.GetDirectory().SetCurrentBinary(currentBinaryDirectory);
+  dummies.Makefile =
+    std::make_shared<cmMakefile>(dummies.GlobalGenerator.get(), snapshot);
+  dummies.Makefile->CreateNewTarget(targetName, cmStateEnums::EXECUTABLE);
+  return dummies;
+}
+
+static bool testCreateFromPolicyMap()
+{
+  auto variablesManager =
+    std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+  cmPolicies::PolicyMap policyMap;
+  policyMap.Set(cmPolicies::CMP0000, cmPolicies::NEW);
+  policyMap.Set(cmPolicies::CMP0003, cmPolicies::WARN);
+  policyMap.Set(cmPolicies::CMP0005, cmPolicies::OLD);
+  auto vars = cmDebugger::cmDebuggerVariablesHelper::Create(
+    variablesManager, "Locals", true, policyMap);
+
+  dap::array<dap::Variable> variables =
+    variablesManager->HandleVariablesRequest(
+      CreateVariablesRequest(vars->GetId()));
+  ASSERT_TRUE(variables.size() == 3);
+  ASSERT_VARIABLE(variables[0], "CMP0000", "NEW", "string");
+  ASSERT_VARIABLE(variables[1], "CMP0003", "WARN", "string");
+  ASSERT_VARIABLE(variables[2], "CMP0005", "OLD", "string");
+
+  return true;
+}
+
+static bool testCreateFromPairVector()
+{
+  auto variablesManager =
+    std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+  std::vector<std::pair<std::string, std::string>> pairs = {
+    { "Foo1", "Bar1" }, { "Foo2", "Bar2" }
+  };
+
+  auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+    variablesManager, "Locals", true, pairs);
+
+  dap::array<dap::Variable> variables =
+    variablesManager->HandleVariablesRequest(
+      CreateVariablesRequest(vars->GetId()));
+
+  ASSERT_TRUE(vars->GetValue() == std::to_string(pairs.size()));
+  ASSERT_TRUE(variables.size() == 2);
+  ASSERT_VARIABLE(variables[0], "Foo1", "Bar1", "string");
+  ASSERT_VARIABLE(variables[1], "Foo2", "Bar2", "string");
+
+  auto none = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+    variablesManager, "Locals", true,
+    std::vector<std::pair<std::string, std::string>>());
+
+  ASSERT_TRUE(none == nullptr);
+
+  return true;
+}
+
+static bool testCreateFromSet()
+{
+  auto variablesManager =
+    std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+  std::set<std::string> set = { "Foo", "Bar" };
+
+  auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+    variablesManager, "Locals", true, set);
+
+  dap::array<dap::Variable> variables =
+    variablesManager->HandleVariablesRequest(
+      CreateVariablesRequest(vars->GetId()));
+
+  ASSERT_TRUE(vars->GetValue() == std::to_string(set.size()));
+  ASSERT_TRUE(variables.size() == 2);
+  ASSERT_VARIABLE(variables[0], "[0]", "Bar", "string");
+  ASSERT_VARIABLE(variables[1], "[1]", "Foo", "string");
+
+  auto none = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+    variablesManager, "Locals", true, std::set<std::string>());
+
+  ASSERT_TRUE(none == nullptr);
+
+  return true;
+}
+
+static bool testCreateFromStringVector()
+{
+  auto variablesManager =
+    std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+  std::vector<std::string> list = { "Foo", "Bar" };
+
+  auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+    variablesManager, "Locals", true, list);
+
+  dap::array<dap::Variable> variables =
+    variablesManager->HandleVariablesRequest(
+      CreateVariablesRequest(vars->GetId()));
+
+  ASSERT_TRUE(vars->GetValue() == std::to_string(list.size()));
+  ASSERT_TRUE(variables.size() == 2);
+  ASSERT_VARIABLE(variables[0], "[0]", "Foo", "string");
+  ASSERT_VARIABLE(variables[1], "[1]", "Bar", "string");
+
+  auto none = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+    variablesManager, "Locals", true, std::vector<std::string>());
+
+  ASSERT_TRUE(none == nullptr);
+
+  return true;
+}
+
+static bool testCreateFromTarget()
+{
+  auto variablesManager =
+    std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+  auto dummies = CreateDummies("Foo");
+
+  auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+    variablesManager, "Locals", true, dummies.Makefile->GetOrderedTargets());
+
+  dap::array<dap::Variable> variables =
+    variablesManager->HandleVariablesRequest(
+      CreateVariablesRequest(vars->GetId()));
+
+  ASSERT_TRUE(variables.size() == 1);
+  ASSERT_VARIABLE(variables[0], "Foo", "EXECUTABLE", "collection");
+
+  variables = variablesManager->HandleVariablesRequest(
+    CreateVariablesRequest(variables[0].variablesReference));
+
+  ASSERT_TRUE(variables.size() == 15);
+  ASSERT_VARIABLE(variables[0], "GlobalGenerator", "Generic", "collection");
+  ASSERT_VARIABLE(variables[1], "IsAIX", "FALSE", "bool");
+  ASSERT_VARIABLE(variables[2], "IsAndroidGuiExecutable", "FALSE", "bool");
+  ASSERT_VARIABLE(variables[3], "IsAppBundleOnApple", "FALSE", "bool");
+  ASSERT_VARIABLE(variables[4], "IsDLLPlatform", "FALSE", "bool");
+  ASSERT_VARIABLE(variables[5], "IsExecutableWithExports", "FALSE", "bool");
+  ASSERT_VARIABLE(variables[6], "IsFrameworkOnApple", "FALSE", "bool");
+  ASSERT_VARIABLE(variables[7], "IsImported", "FALSE", "bool");
+  ASSERT_VARIABLE(variables[8], "IsImportedGloballyVisible", "FALSE", "bool");
+  ASSERT_VARIABLE(variables[9], "IsPerConfig", "TRUE", "bool");
+  ASSERT_VARIABLE(variables[10], "Makefile",
+                  dummies.Makefile->GetDirectoryId().String, "collection");
+  ASSERT_VARIABLE(variables[11], "Name", "Foo", "string");
+  ASSERT_VARIABLE(variables[12], "PolicyMap", "", "collection");
+  ASSERT_VARIABLE(variables[13], "Properties",
+                  std::to_string(dummies.Makefile->GetOrderedTargets()[0]
+                                   ->GetProperties()
+                                   .GetList()
+                                   .size()),
+                  "collection");
+  ASSERT_VARIABLE(variables[14], "Type", "EXECUTABLE", "string");
+
+  auto none = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+    variablesManager, "Locals", true, std::vector<cmTarget*>());
+
+  ASSERT_TRUE(none == nullptr);
+
+  return true;
+}
+
+static bool testCreateFromGlobalGenerator()
+{
+  auto variablesManager =
+    std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+  auto dummies = CreateDummies("Foo");
+
+  auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+    variablesManager, "Locals", true, dummies.GlobalGenerator.get());
+
+  dap::array<dap::Variable> variables =
+    variablesManager->HandleVariablesRequest(
+      CreateVariablesRequest(vars->GetId()));
+
+  ASSERT_TRUE(variables.size() == 10);
+  ASSERT_VARIABLE(variables[0], "AllTargetName", "ALL_BUILD", "string");
+  ASSERT_VARIABLE(variables[1], "ForceUnixPaths", "FALSE", "bool");
+  ASSERT_VARIABLE(variables[2], "InstallTargetName", "INSTALL", "string");
+  ASSERT_VARIABLE(variables[3], "IsMultiConfig", "FALSE", "bool");
+  ASSERT_VARIABLE(variables[4], "MakefileEncoding", "None", "string");
+  ASSERT_VARIABLE(variables[5], "Name", "Generic", "string");
+  ASSERT_VARIABLE(variables[6], "NeedSymbolicMark", "FALSE", "bool");
+  ASSERT_VARIABLE(variables[7], "PackageTargetName", "PACKAGE", "string");
+  ASSERT_VARIABLE(variables[8], "TestTargetName", "RUN_TESTS", "string");
+  ASSERT_VARIABLE(variables[9], "UseLinkScript", "FALSE", "bool");
+
+  auto none = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+    variablesManager, "Locals", true,
+    static_cast<cmGlobalGenerator*>(nullptr));
+
+  ASSERT_TRUE(none == nullptr);
+
+  return true;
+}
+
+static bool testCreateFromTests()
+{
+  auto variablesManager =
+    std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+  auto dummies = CreateDummies("Foo");
+  cmTest test1 = cmTest(dummies.Makefile.get());
+  test1.SetName("Test1");
+  test1.SetOldStyle(false);
+  test1.SetCommandExpandLists(true);
+  test1.SetCommand(std::vector<std::string>{ "Foo1", "arg1" });
+  test1.SetProperty("Prop1", "Prop1");
+  cmTest test2 = cmTest(dummies.Makefile.get());
+  test2.SetName("Test2");
+  test2.SetOldStyle(false);
+  test2.SetCommandExpandLists(false);
+  test2.SetCommand(std::vector<std::string>{ "Bar1", "arg1", "arg2" });
+  test2.SetProperty("Prop2", "Prop2");
+  test2.SetProperty("Prop3", "Prop3");
+
+  std::vector<cmTest*> tests = { &test1, &test2 };
+
+  auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+    variablesManager, "Locals", true, tests);
+
+  dap::array<dap::Variable> variables =
+    variablesManager->HandleVariablesRequest(
+      CreateVariablesRequest(vars->GetId()));
+
+  ASSERT_TRUE(vars->GetValue() == std::to_string(tests.size()));
+  ASSERT_TRUE(variables.size() == 2);
+  ASSERT_VARIABLE_REFERENCE_NOT_ZERO(variables[0], test1.GetName(), "",
+                                     "collection");
+  ASSERT_VARIABLE_REFERENCE_NOT_ZERO(variables[1], test2.GetName(), "",
+                                     "collection");
+
+  dap::array<dap::Variable> testVariables =
+    variablesManager->HandleVariablesRequest(
+      CreateVariablesRequest(variables[0].variablesReference));
+  ASSERT_TRUE(testVariables.size() == 5);
+  ASSERT_VARIABLE_REFERENCE_NOT_ZERO(testVariables[0], "Command",
+                                     std::to_string(test1.GetCommand().size()),
+                                     "collection");
+  ASSERT_VARIABLE(testVariables[1], "CommandExpandLists",
+                  BOOL_STRING(test1.GetCommandExpandLists()), "bool");
+  ASSERT_VARIABLE(testVariables[2], "Name", test1.GetName(), "string");
+  ASSERT_VARIABLE(testVariables[3], "OldStyle",
+                  BOOL_STRING(test1.GetOldStyle()), "bool");
+  ASSERT_VARIABLE_REFERENCE_NOT_ZERO(testVariables[4], "Properties", "1",
+                                     "collection");
+
+  dap::array<dap::Variable> commandVariables =
+    variablesManager->HandleVariablesRequest(
+      CreateVariablesRequest(testVariables[0].variablesReference));
+  ASSERT_TRUE(commandVariables.size() == test1.GetCommand().size());
+  for (size_t i = 0; i < commandVariables.size(); ++i) {
+    ASSERT_VARIABLE(commandVariables[i], "[" + std::to_string(i) + "]",
+                    test1.GetCommand()[i], "string");
+  }
+
+  dap::array<dap::Variable> propertiesVariables =
+    variablesManager->HandleVariablesRequest(
+      CreateVariablesRequest(testVariables[4].variablesReference));
+  ASSERT_TRUE(propertiesVariables.size() == 1);
+  ASSERT_VARIABLE(propertiesVariables[0], "Prop1", "Prop1", "string");
+
+  testVariables = variablesManager->HandleVariablesRequest(
+    CreateVariablesRequest(variables[1].variablesReference));
+  ASSERT_TRUE(testVariables.size() == 5);
+  ASSERT_VARIABLE_REFERENCE_NOT_ZERO(testVariables[0], "Command",
+                                     std::to_string(test2.GetCommand().size()),
+                                     "collection");
+  ASSERT_VARIABLE(testVariables[1], "CommandExpandLists",
+                  BOOL_STRING(test2.GetCommandExpandLists()), "bool");
+  ASSERT_VARIABLE(testVariables[2], "Name", test2.GetName(), "string");
+  ASSERT_VARIABLE(testVariables[3], "OldStyle",
+                  BOOL_STRING(test2.GetOldStyle()), "bool");
+  ASSERT_VARIABLE_REFERENCE_NOT_ZERO(testVariables[4], "Properties", "2",
+                                     "collection");
+
+  commandVariables = variablesManager->HandleVariablesRequest(
+    CreateVariablesRequest(testVariables[0].variablesReference));
+  ASSERT_TRUE(commandVariables.size() == test2.GetCommand().size());
+  for (size_t i = 0; i < commandVariables.size(); ++i) {
+    ASSERT_VARIABLE(commandVariables[i], "[" + std::to_string(i) + "]",
+                    test2.GetCommand()[i], "string");
+  }
+
+  propertiesVariables = variablesManager->HandleVariablesRequest(
+    CreateVariablesRequest(testVariables[4].variablesReference));
+  ASSERT_TRUE(propertiesVariables.size() == 2);
+  ASSERT_VARIABLE(propertiesVariables[0], "Prop2", "Prop2", "string");
+  ASSERT_VARIABLE(propertiesVariables[1], "Prop3", "Prop3", "string");
+
+  auto none = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+    variablesManager, "Locals", true, std::vector<cmTest*>());
+
+  ASSERT_TRUE(none == nullptr);
+
+  return true;
+}
+
+static bool testCreateFromMakefile()
+{
+  auto variablesManager =
+    std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+  auto dummies = CreateDummies("Foo");
+  auto snapshot = dummies.Makefile->GetStateSnapshot();
+  auto state = dummies.Makefile->GetState();
+  state->SetSourceDirectory("c:/HomeDirectory");
+  state->SetBinaryDirectory("c:/HomeOutputDirectory");
+  auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+    variablesManager, "Locals", true, dummies.Makefile.get());
+
+  dap::array<dap::Variable> variables =
+    variablesManager->HandleVariablesRequest(
+      CreateVariablesRequest(vars->GetId()));
+
+  ASSERT_TRUE(variables.size() == 12);
+  ASSERT_VARIABLE(variables[0], "AppleSDKType", "MacOS", "string");
+  ASSERT_VARIABLE(variables[1], "CurrentBinaryDirectory",
+                  snapshot.GetDirectory().GetCurrentBinary(), "string");
+  ASSERT_VARIABLE(variables[2], "CurrentSourceDirectory",
+                  snapshot.GetDirectory().GetCurrentSource(), "string");
+  ASSERT_VARIABLE(variables[3], "DefineFlags", " ", "string");
+  ASSERT_VARIABLE(variables[4], "DirectoryId",
+                  dummies.Makefile->GetDirectoryId().String, "string");
+  ASSERT_VARIABLE(variables[5], "HomeDirectory", state->GetSourceDirectory(),
+                  "string");
+  ASSERT_VARIABLE(variables[6], "HomeOutputDirectory",
+                  state->GetBinaryDirectory(), "string");
+  ASSERT_VARIABLE(variables[7], "IsRootMakefile", "TRUE", "bool");
+  ASSERT_VARIABLE(variables[8], "PlatformIs32Bit", "FALSE", "bool");
+  ASSERT_VARIABLE(variables[9], "PlatformIs64Bit", "FALSE", "bool");
+  ASSERT_VARIABLE(variables[10], "PlatformIsAppleEmbedded", "FALSE", "bool");
+  ASSERT_VARIABLE(variables[11], "PlatformIsx32", "FALSE", "bool");
+
+  auto none = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+    variablesManager, "Locals", true, static_cast<cmMakefile*>(nullptr));
+
+  ASSERT_TRUE(none == nullptr);
+
+  return true;
+}
+
+static bool testCreateFromStackFrame()
+{
+  auto variablesManager =
+    std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+  auto dummies = CreateDummies("Foo");
+
+  cmListFileFunction lff = cmListFileFunction("set", 99, 99, {});
+  auto frame = std::make_shared<cmDebugger::cmDebuggerStackFrame>(
+    dummies.Makefile.get(), "c:/CMakeLists.txt", lff);
+
+  dummies.CMake->AddCacheEntry("CMAKE_BUILD_TYPE", "Debug", "Build Type",
+                               cmStateEnums::CacheEntryType::STRING);
+
+  auto locals = cmDebugger::cmDebuggerVariablesHelper::Create(
+    variablesManager, "Locals", true, frame);
+
+  dap::array<dap::Variable> variables =
+    variablesManager->HandleVariablesRequest(
+      CreateVariablesRequest(locals->GetId()));
+
+  ASSERT_TRUE(variables.size() == 5);
+  ASSERT_VARIABLE(variables[0], "CacheVariables", "1", "collection");
+  ASSERT_VARIABLE(variables[1], "CurrentLine", std::to_string(lff.Line()),
+                  "int");
+  ASSERT_VARIABLE(variables[2], "Directories", "2", "collection");
+  ASSERT_VARIABLE(variables[3], "Locals", "2", "collection");
+  ASSERT_VARIABLE(variables[4], "Targets", "1", "collection");
+
+  dap::array<dap::Variable> cacheVariables =
+    variablesManager->HandleVariablesRequest(
+      CreateVariablesRequest(variables[0].variablesReference));
+  ASSERT_TRUE(cacheVariables.size() == 1);
+  ASSERT_VARIABLE(cacheVariables[0], "CMAKE_BUILD_TYPE:STRING", "Debug",
+                  "collection");
+
+  dap::array<dap::Variable> directoriesVariables =
+    variablesManager->HandleVariablesRequest(
+      CreateVariablesRequest(variables[2].variablesReference));
+  ASSERT_TRUE(directoriesVariables.size() == 2);
+  ASSERT_VARIABLE(
+    directoriesVariables[0], "CMAKE_CURRENT_BINARY_DIR",
+    dummies.Makefile->GetStateSnapshot().GetDirectory().GetCurrentBinary(),
+    "string");
+  ASSERT_VARIABLE(
+    directoriesVariables[1], "CMAKE_CURRENT_SOURCE_DIR",
+    dummies.Makefile->GetStateSnapshot().GetDirectory().GetCurrentSource(),
+    "string");
+
+  dap::array<dap::Variable> propertiesVariables =
+    variablesManager->HandleVariablesRequest(
+      CreateVariablesRequest(cacheVariables[0].variablesReference));
+  ASSERT_TRUE(propertiesVariables.size() == 3);
+  ASSERT_VARIABLE(propertiesVariables[0], "HELPSTRING", "Build Type",
+                  "string");
+  ASSERT_VARIABLE(propertiesVariables[1], "TYPE", "STRING", "string");
+  ASSERT_VARIABLE(propertiesVariables[2], "VALUE", "Debug", "string");
+
+  return true;
+}
+
+static bool testCreateFromBTStringVector()
+{
+  auto variablesManager =
+    std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+  std::vector<BT<std::string>> list(2);
+  list[0].Value = "Foo";
+  list[1].Value = "Bar";
+
+  auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+    variablesManager, "Locals", true, list);
+
+  dap::array<dap::Variable> variables =
+    variablesManager->HandleVariablesRequest(
+      CreateVariablesRequest(vars->GetId()));
+
+  ASSERT_TRUE(vars->GetValue() == std::to_string(list.size()));
+  ASSERT_TRUE(variables.size() == 2);
+  ASSERT_VARIABLE(variables[0], "[0]", "Foo", "string");
+  ASSERT_VARIABLE(variables[1], "[1]", "Bar", "string");
+
+  auto none = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+    variablesManager, "Locals", true, std::vector<std::string>());
+
+  ASSERT_TRUE(none == nullptr);
+
+  return true;
+}
+
+static bool testCreateFromFileSet()
+{
+  auto variablesManager =
+    std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+  cmake cm(cmake::RoleScript, cmState::Unknown);
+  cmFileSet fileSet(cm, "Foo", "HEADERS", cmFileSetVisibility::Public);
+  BT<std::string> directory;
+  directory.Value = "c:/";
+  fileSet.AddDirectoryEntry(directory);
+  BT<std::string> file;
+  file.Value = "c:/foo.cxx";
+  fileSet.AddFileEntry(file);
+
+  auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+    variablesManager, "Locals", true, &fileSet);
+
+  dap::array<dap::Variable> variables =
+    variablesManager->HandleVariablesRequest(
+      CreateVariablesRequest(vars->GetId()));
+
+  ASSERT_TRUE(variables.size() == 5);
+  ASSERT_VARIABLE_REFERENCE_NOT_ZERO(variables[0], "Directories", "1",
+                                     "collection");
+  ASSERT_VARIABLE_REFERENCE_NOT_ZERO(variables[1], "Files", "1", "collection");
+  ASSERT_VARIABLE(variables[2], "Name", "Foo", "string");
+  ASSERT_VARIABLE(variables[3], "Type", "HEADERS", "string");
+  ASSERT_VARIABLE(variables[4], "Visibility", "Public", "string");
+
+  dap::array<dap::Variable> directoriesVariables =
+    variablesManager->HandleVariablesRequest(
+      CreateVariablesRequest(variables[0].variablesReference));
+  ASSERT_TRUE(directoriesVariables.size() == 1);
+  ASSERT_VARIABLE(directoriesVariables[0], "[0]", directory.Value, "string");
+
+  dap::array<dap::Variable> filesVariables =
+    variablesManager->HandleVariablesRequest(
+      CreateVariablesRequest(variables[1].variablesReference));
+  ASSERT_TRUE(filesVariables.size() == 1);
+  ASSERT_VARIABLE(filesVariables[0], "[0]", file.Value, "string");
+
+  return true;
+}
+
+static bool testCreateFromFileSets()
+{
+  auto variablesManager =
+    std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+  cmake cm(cmake::RoleScript, cmState::Unknown);
+  cmFileSet fileSet(cm, "Foo", "HEADERS", cmFileSetVisibility::Public);
+  BT<std::string> directory;
+  directory.Value = "c:/";
+  fileSet.AddDirectoryEntry(directory);
+  BT<std::string> file;
+  file.Value = "c:/foo.cxx";
+  fileSet.AddFileEntry(file);
+
+  auto fileSets = std::vector<cmFileSet*>{ &fileSet };
+  auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+    variablesManager, "Locals", true, fileSets);
+
+  dap::array<dap::Variable> variables =
+    variablesManager->HandleVariablesRequest(
+      CreateVariablesRequest(vars->GetId()));
+
+  ASSERT_TRUE(variables.size() == 1);
+  ASSERT_VARIABLE_REFERENCE_NOT_ZERO(variables[0], "Foo", "", "collection");
+
+  return true;
+}
+
+int testDebuggerVariablesHelper(int, char*[])
+{
+  return runTests(std::vector<std::function<bool()>>{
+    testCreateFromPolicyMap,
+    testCreateFromPairVector,
+    testCreateFromSet,
+    testCreateFromStringVector,
+    testCreateFromTarget,
+    testCreateFromGlobalGenerator,
+    testCreateFromMakefile,
+    testCreateFromStackFrame,
+    testCreateFromTests,
+    testCreateFromBTStringVector,
+    testCreateFromFileSet,
+    testCreateFromFileSets,
+  });
+}
diff --git a/Tests/CMakeLib/testDebuggerVariablesManager.cxx b/Tests/CMakeLib/testDebuggerVariablesManager.cxx
new file mode 100644
index 0000000..3013b9f
--- /dev/null
+++ b/Tests/CMakeLib/testDebuggerVariablesManager.cxx
@@ -0,0 +1,50 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include <functional>
+#include <memory>
+#include <vector>
+
+#include <cm3p/cppdap/protocol.h>
+#include <cm3p/cppdap/types.h>
+#include <stdint.h>
+
+#include "cmDebuggerVariables.h"
+#include "cmDebuggerVariablesManager.h"
+
+#include "testCommon.h"
+
+static bool testVariablesRegistration()
+{
+  auto variablesManager =
+    std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+  int64_t line = 5;
+  auto local = std::make_shared<cmDebugger::cmDebuggerVariables>(
+    variablesManager, "Local", true, [=]() {
+      return std::vector<cmDebugger::cmDebuggerVariableEntry>{ { "CurrentLine",
+                                                                 line } };
+    });
+
+  dap::VariablesRequest variableRequest;
+  variableRequest.variablesReference = local->GetId();
+
+  dap::array<dap::Variable> variables =
+    variablesManager->HandleVariablesRequest(variableRequest);
+
+  ASSERT_TRUE(variables.size() == 1);
+
+  local.reset();
+
+  variables = variablesManager->HandleVariablesRequest(variableRequest);
+  ASSERT_TRUE(variables.size() == 0);
+
+  return true;
+}
+
+int testDebuggerVariablesManager(int, char*[])
+{
+  return runTests(std::vector<std::function<bool()>>{
+    testVariablesRegistration,
+  });
+}
diff --git a/Tests/CMakeLib/testGccDepfileReader_data/deps1.txt b/Tests/CMakeLib/testGccDepfileReader_data/deps1.txt
index fd2679f..4207b58 100644
--- a/Tests/CMakeLib/testGccDepfileReader_data/deps1.txt
+++ b/Tests/CMakeLib/testGccDepfileReader_data/deps1.txt
@@ -1,26 +1,26 @@
 --RULES--
 main.o
 --DEPENDENCIES--
-main.cpp
+/usr/include/features.h
 /usr/include/stdc-predef.h
 /usr/include/stdio.h
 /usr/include/x86_64-linux-gnu/bits/libc-header-start.h
-/usr/include/features.h
-/usr/include/x86_64-linux-gnu/sys/cdefs.h
-/usr/include/x86_64-linux-gnu/bits/wordsize.h
 /usr/include/x86_64-linux-gnu/bits/long-double.h
-/usr/include/x86_64-linux-gnu/gnu/stubs.h
-/usr/include/x86_64-linux-gnu/gnu/stubs-64.h
-/usr/lib/gcc/x86_64-linux-gnu/8/include/stddef.h
-/usr/lib/gcc/x86_64-linux-gnu/8/include/stdarg.h
-/usr/include/x86_64-linux-gnu/bits/types.h
-/usr/include/x86_64-linux-gnu/bits/typesizes.h
-/usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h
-/usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h
-/usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h
-/usr/include/x86_64-linux-gnu/bits/types/__FILE.h
-/usr/include/x86_64-linux-gnu/bits/types/FILE.h
-/usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h
-/usr/include/x86_64-linux-gnu/bits/types/cookie_io_functions_t.h
 /usr/include/x86_64-linux-gnu/bits/stdio_lim.h
 /usr/include/x86_64-linux-gnu/bits/sys_errlist.h
+/usr/include/x86_64-linux-gnu/bits/types.h
+/usr/include/x86_64-linux-gnu/bits/types/FILE.h
+/usr/include/x86_64-linux-gnu/bits/types/__FILE.h
+/usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h
+/usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h
+/usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h
+/usr/include/x86_64-linux-gnu/bits/types/cookie_io_functions_t.h
+/usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h
+/usr/include/x86_64-linux-gnu/bits/typesizes.h
+/usr/include/x86_64-linux-gnu/bits/wordsize.h
+/usr/include/x86_64-linux-gnu/gnu/stubs-64.h
+/usr/include/x86_64-linux-gnu/gnu/stubs.h
+/usr/include/x86_64-linux-gnu/sys/cdefs.h
+/usr/lib/gcc/x86_64-linux-gnu/8/include/stdarg.h
+/usr/lib/gcc/x86_64-linux-gnu/8/include/stddef.h
+main.cpp
diff --git a/Tests/CMakeLib/testGccDepfileReader_data/deps3.txt b/Tests/CMakeLib/testGccDepfileReader_data/deps3.txt
index 448f69c..8d82c60 100644
--- a/Tests/CMakeLib/testGccDepfileReader_data/deps3.txt
+++ b/Tests/CMakeLib/testGccDepfileReader_data/deps3.txt
@@ -1,11 +1,11 @@
 --RULES--
 main.o
 --DEPENDENCIES--
-main.cpp
-foo#bar.h
-foo\#bar.h
 foo bar.h
+foo#bar.h
+foo$bar.h
 foo\ bar.h
+foo\#bar.h
 foo\\ bar.h
 foo\\\\
-foo$bar.h
+main.cpp
diff --git a/Tests/CMakeLib/testJSONHelpers.cxx b/Tests/CMakeLib/testJSONHelpers.cxx
index 053c163..50f0386 100644
--- a/Tests/CMakeLib/testJSONHelpers.cxx
+++ b/Tests/CMakeLib/testJSONHelpers.cxx
@@ -10,6 +10,7 @@
 #include <cm3p/json/value.h>
 
 #include "cmJSONHelpers.h"
+#include "cmJSONState.h"
 
 #define ASSERT_TRUE(x)                                                        \
   do {                                                                        \
@@ -31,59 +32,65 @@
   std::string Field3;
 };
 
-enum class ErrorCode
+namespace ErrorMessages {
+using ErrorGenerator =
+  std::function<void(const Json::Value*, cmJSONState* state)>;
+ErrorGenerator ErrorGeneratorBuilder(std::string errorMessage)
 {
-  Success,
-  InvalidInt,
-  InvalidBool,
-  InvalidString,
-  InvalidSubObject,
-  InvalidObject,
-  InvalidArray,
-  MissingRequired,
+  return [errorMessage](const Json::Value* value, cmJSONState* state) -> void {
+    state->AddErrorAtValue(errorMessage, value);
+  };
+};
+ErrorGenerator InvalidArray = ErrorGeneratorBuilder("Invalid Array");
+ErrorGenerator MissingRequired = ErrorGeneratorBuilder("Missing Required");
+ErrorGenerator InvalidMap = ErrorGeneratorBuilder("Invalid Map");
+ErrorGenerator InvalidObject(JsonErrors::ObjectError /*errorType*/,
+                             const Json::Value::Members& extraFields)
+{
+  return [extraFields](const Json::Value* value, cmJSONState* state) -> void {
+    state->AddErrorAtValue("Invalid Object", value);
+  };
+};
 };
 
-using JSONHelperBuilder = cmJSONHelperBuilder<ErrorCode>;
+using JSONHelperBuilder = cmJSONHelperBuilder;
 
-auto const IntHelper =
-  JSONHelperBuilder::Int(ErrorCode::Success, ErrorCode::InvalidInt, 1);
+auto const IntHelper = JSONHelperBuilder::Int(1);
 auto const RequiredIntHelper =
-  JSONHelperBuilder::Required<int>(ErrorCode::MissingRequired, IntHelper);
-auto const UIntHelper =
-  JSONHelperBuilder::UInt(ErrorCode::Success, ErrorCode::InvalidInt, 1);
-auto const BoolHelper =
-  JSONHelperBuilder::Bool(ErrorCode::Success, ErrorCode::InvalidBool, false);
-auto const StringHelper = JSONHelperBuilder::String(
-  ErrorCode::Success, ErrorCode::InvalidString, "default");
+  JSONHelperBuilder::Required<int>(ErrorMessages::MissingRequired, IntHelper);
+auto const UIntHelper = JSONHelperBuilder::UInt(1);
+auto const BoolHelper = JSONHelperBuilder::Bool(false);
+auto const StringHelper = JSONHelperBuilder::String("default");
 auto const RequiredStringHelper = JSONHelperBuilder::Required<std::string>(
-  ErrorCode::MissingRequired, StringHelper);
+  ErrorMessages::MissingRequired, StringHelper);
 auto const StringVectorHelper = JSONHelperBuilder::Vector<std::string>(
-  ErrorCode::Success, ErrorCode::InvalidArray, StringHelper);
+  ErrorMessages::InvalidArray, StringHelper);
 auto const StringVectorFilterHelper =
   JSONHelperBuilder::VectorFilter<std::string>(
-    ErrorCode::Success, ErrorCode::InvalidArray, StringHelper,
+    ErrorMessages::InvalidArray, StringHelper,
     [](const std::string& value) { return value != "ignore"; });
-auto const StringMapHelper = JSONHelperBuilder::Map<std::string>(
-  ErrorCode::Success, ErrorCode::InvalidObject, StringHelper);
+auto const StringMapHelper =
+  JSONHelperBuilder::Map<std::string>(ErrorMessages::InvalidMap, StringHelper);
 auto const StringMapFilterHelper = JSONHelperBuilder::MapFilter<std::string>(
-  ErrorCode::Success, ErrorCode::InvalidObject, StringHelper,
+  ErrorMessages::InvalidMap, StringHelper,
   [](const std::string& key) { return key != "ignore"; });
 auto const OptionalStringHelper =
-  JSONHelperBuilder::Optional<std::string>(ErrorCode::Success, StringHelper);
+  JSONHelperBuilder::Optional<std::string>(StringHelper);
 
 bool testInt()
 {
   Json::Value v(2);
+  cmJSONState state;
   int i = 0;
-  ASSERT_TRUE(IntHelper(i, &v) == ErrorCode::Success);
+  ASSERT_TRUE(IntHelper(i, &v, &state));
   ASSERT_TRUE(i == 2);
 
   i = 0;
   v = Json::nullValue;
-  ASSERT_TRUE(IntHelper(i, &v) == ErrorCode::InvalidInt);
+  ASSERT_TRUE(!IntHelper(i, &v, &state));
 
   i = 0;
-  ASSERT_TRUE(IntHelper(i, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(IntHelper(i, nullptr, &state));
   ASSERT_TRUE(i == 1);
 
   return true;
@@ -92,16 +99,16 @@
 bool testUInt()
 {
   Json::Value v(2);
+  cmJSONState state;
   unsigned int i = 0;
-  ASSERT_TRUE(UIntHelper(i, &v) == ErrorCode::Success);
+  ASSERT_TRUE(UIntHelper(i, &v, &state));
   ASSERT_TRUE(i == 2);
-
   i = 0;
   v = Json::nullValue;
-  ASSERT_TRUE(UIntHelper(i, &v) == ErrorCode::InvalidInt);
+  ASSERT_TRUE(!UIntHelper(i, &v, &state));
 
   i = 0;
-  ASSERT_TRUE(UIntHelper(i, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(UIntHelper(i, nullptr, &state));
   ASSERT_TRUE(i == 1);
 
   return true;
@@ -110,21 +117,22 @@
 bool testBool()
 {
   Json::Value v(true);
+  cmJSONState state;
   bool b = false;
-  ASSERT_TRUE(BoolHelper(b, &v) == ErrorCode::Success);
+  ASSERT_TRUE(BoolHelper(b, &v, &state));
   ASSERT_TRUE(b);
 
   b = false;
   v = false;
-  ASSERT_TRUE(BoolHelper(b, &v) == ErrorCode::Success);
+  ASSERT_TRUE(BoolHelper(b, &v, &state));
   ASSERT_TRUE(!b);
 
   b = false;
   v = 4;
-  ASSERT_TRUE(BoolHelper(b, &v) == ErrorCode::InvalidBool);
+  ASSERT_TRUE(!BoolHelper(b, &v, &state));
 
   b = true;
-  ASSERT_TRUE(BoolHelper(b, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(BoolHelper(b, nullptr, &state));
   ASSERT_TRUE(!b);
 
   return true;
@@ -133,16 +141,17 @@
 bool testString()
 {
   Json::Value v("str");
+  cmJSONState state;
   std::string str = "";
-  ASSERT_TRUE(StringHelper(str, &v) == ErrorCode::Success);
+  ASSERT_TRUE(StringHelper(str, &v, &state));
   ASSERT_TRUE(str == "str");
 
   str = "";
   v = Json::nullValue;
-  ASSERT_TRUE(StringHelper(str, &v) == ErrorCode::InvalidString);
+  ASSERT_TRUE(!StringHelper(str, &v, &state));
 
   str = "";
-  ASSERT_TRUE(StringHelper(str, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(StringHelper(str, nullptr, &state));
   ASSERT_TRUE(str == "default");
 
   return true;
@@ -150,17 +159,15 @@
 
 bool testObject()
 {
-  auto const subhelper =
-    JSONHelperBuilder::Object<ObjectStruct>(ErrorCode::Success,
-                                            ErrorCode::InvalidSubObject)
-      .Bind("subfield"_s, &ObjectStruct::Field2, IntHelper);
-  auto const helper = JSONHelperBuilder::Object<ObjectStruct>(
-                        ErrorCode::Success, ErrorCode::InvalidObject)
+  auto const subhelper = JSONHelperBuilder::Object<ObjectStruct>().Bind(
+    "subfield"_s, &ObjectStruct::Field2, IntHelper);
+  auto const helper = JSONHelperBuilder::Object<ObjectStruct>()
                         .Bind("field1"_s, &ObjectStruct::Field1, StringHelper)
                         .Bind("field2"_s, subhelper)
                         .Bind<std::string>("field3"_s, nullptr, StringHelper);
 
   Json::Value v(Json::objectValue);
+  cmJSONState state;
   v["field1"] = "Hello";
   v["field2"] = Json::objectValue;
   v["field2"]["subfield"] = 2;
@@ -168,38 +175,38 @@
   v["extra"] = "extra";
 
   ObjectStruct s1;
-  ASSERT_TRUE(helper(s1, &v) == ErrorCode::Success);
+  ASSERT_TRUE(helper(s1, &v, &state));
   ASSERT_TRUE(s1.Field1 == "Hello");
   ASSERT_TRUE(s1.Field2 == 2);
 
   v["field2"]["subfield"] = "wrong";
   ObjectStruct s2;
-  ASSERT_TRUE(helper(s2, &v) == ErrorCode::InvalidInt);
+  ASSERT_TRUE(!helper(s2, &v, &state));
 
   v["field2"].removeMember("subfield");
   ObjectStruct s3;
-  ASSERT_TRUE(helper(s3, &v) == ErrorCode::InvalidSubObject);
+  ASSERT_TRUE(!helper(s3, &v, &state));
 
   v.removeMember("field2");
   ObjectStruct s4;
-  ASSERT_TRUE(helper(s4, &v) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(!helper(s4, &v, &state));
 
   v["field2"] = Json::objectValue;
   v["field2"]["subfield"] = 2;
   v["field3"] = 3;
   ObjectStruct s5;
-  ASSERT_TRUE(helper(s5, &v) == ErrorCode::InvalidString);
+  ASSERT_TRUE(!helper(s5, &v, &state));
 
   v.removeMember("field3");
   ObjectStruct s6;
-  ASSERT_TRUE(helper(s6, &v) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(!helper(s6, &v, &state));
 
   v = "Hello";
   ObjectStruct s7;
-  ASSERT_TRUE(helper(s7, &v) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(!helper(s7, &v, &state));
 
   ObjectStruct s8;
-  ASSERT_TRUE(helper(s8, nullptr) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(!helper(s8, nullptr, &state));
 
   return true;
 }
@@ -207,47 +214,48 @@
 bool testObjectInherited()
 {
   auto const helper =
-    JSONHelperBuilder::Object<InheritedStruct>(ErrorCode::Success,
-                                               ErrorCode::InvalidObject)
+    JSONHelperBuilder::Object<InheritedStruct>(ErrorMessages::InvalidObject,
+                                               true)
       .Bind("field1"_s, &InheritedStruct::Field1, StringHelper)
       .Bind("field2"_s, &InheritedStruct::Field2, IntHelper)
       .Bind("field3"_s, &InheritedStruct::Field3, StringHelper);
 
   Json::Value v(Json::objectValue);
+  cmJSONState state;
   v["field1"] = "Hello";
   v["field2"] = 2;
   v["field3"] = "world!";
   v["extra"] = "extra";
 
   InheritedStruct s1;
-  ASSERT_TRUE(helper(s1, &v) == ErrorCode::Success);
+  ASSERT_TRUE(helper(s1, &v, &state));
   ASSERT_TRUE(s1.Field1 == "Hello");
   ASSERT_TRUE(s1.Field2 == 2);
   ASSERT_TRUE(s1.Field3 == "world!");
 
   v["field2"] = "wrong";
   InheritedStruct s2;
-  ASSERT_TRUE(helper(s2, &v) == ErrorCode::InvalidInt);
+  ASSERT_TRUE(!helper(s2, &v, &state));
 
   v.removeMember("field2");
   InheritedStruct s3;
-  ASSERT_TRUE(helper(s3, &v) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(!helper(s3, &v, &state));
 
   v["field2"] = 2;
   v["field3"] = 3;
   InheritedStruct s4;
-  ASSERT_TRUE(helper(s4, &v) == ErrorCode::InvalidString);
+  ASSERT_TRUE(!helper(s4, &v, &state));
 
   v.removeMember("field3");
   InheritedStruct s5;
-  ASSERT_TRUE(helper(s5, &v) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(!helper(s5, &v, &state));
 
   v = "Hello";
   InheritedStruct s6;
-  ASSERT_TRUE(helper(s6, &v) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(!helper(s6, &v, &state));
 
   InheritedStruct s7;
-  ASSERT_TRUE(helper(s7, nullptr) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(!helper(s7, nullptr, &state));
 
   return true;
 }
@@ -255,22 +263,23 @@
 bool testObjectNoExtra()
 {
   auto const helper = JSONHelperBuilder::Object<ObjectStruct>(
-                        ErrorCode::Success, ErrorCode::InvalidObject, false)
+                        ErrorMessages::InvalidObject, false)
                         .Bind("field1"_s, &ObjectStruct::Field1, StringHelper)
                         .Bind("field2"_s, &ObjectStruct::Field2, IntHelper);
 
   Json::Value v(Json::objectValue);
+  cmJSONState state;
   v["field1"] = "Hello";
   v["field2"] = 2;
 
   ObjectStruct s1;
-  ASSERT_TRUE(helper(s1, &v) == ErrorCode::Success);
+  ASSERT_TRUE(helper(s1, &v, &state));
   ASSERT_TRUE(s1.Field1 == "Hello");
   ASSERT_TRUE(s1.Field2 == 2);
 
   v["extra"] = "world!";
   ObjectStruct s2;
-  ASSERT_TRUE(helper(s2, &v) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(!helper(s2, &v, &state));
 
   return true;
 }
@@ -278,31 +287,31 @@
 bool testObjectOptional()
 {
   auto const helper =
-    JSONHelperBuilder::Object<ObjectStruct>(ErrorCode::Success,
-                                            ErrorCode::InvalidObject)
+    JSONHelperBuilder::Object<ObjectStruct>(ErrorMessages::InvalidObject, true)
       .Bind("field1"_s, &ObjectStruct::Field1, StringHelper, false)
       .Bind("field2"_s, &ObjectStruct::Field2, IntHelper, false)
       .Bind<std::string>("field3_s", nullptr, StringHelper, false);
 
   Json::Value v(Json::objectValue);
+  cmJSONState state;
   v["field1"] = "Hello";
   v["field2"] = 2;
   v["field3"] = "world!";
   v["extra"] = "extra";
 
   ObjectStruct s1;
-  ASSERT_TRUE(helper(s1, &v) == ErrorCode::Success);
+  ASSERT_TRUE(helper(s1, &v, &state));
   ASSERT_TRUE(s1.Field1 == "Hello");
   ASSERT_TRUE(s1.Field2 == 2);
 
   v = Json::objectValue;
   ObjectStruct s2;
-  ASSERT_TRUE(helper(s2, &v) == ErrorCode::Success);
+  ASSERT_TRUE(helper(s2, &v, &state));
   ASSERT_TRUE(s2.Field1 == "default");
   ASSERT_TRUE(s2.Field2 == 1);
 
   ObjectStruct s3;
-  ASSERT_TRUE(helper(s3, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(helper(s3, nullptr, &state));
   ASSERT_TRUE(s3.Field1 == "default");
   ASSERT_TRUE(s3.Field2 == 1);
 
@@ -312,25 +321,26 @@
 bool testVector()
 {
   Json::Value v(Json::arrayValue);
+  cmJSONState state;
   v.append("Hello");
   v.append("world!");
   v.append("ignore");
 
   std::vector<std::string> l{ "default" };
   std::vector<std::string> expected{ "Hello", "world!", "ignore" };
-  ASSERT_TRUE(StringVectorHelper(l, &v) == ErrorCode::Success);
+  ASSERT_TRUE(StringVectorHelper(l, &v, &state));
   ASSERT_TRUE(l == expected);
 
   v[1] = 2;
   l = { "default" };
-  ASSERT_TRUE(StringVectorHelper(l, &v) == ErrorCode::InvalidString);
+  ASSERT_TRUE(!StringVectorHelper(l, &v, &state));
 
   v = "Hello";
   l = { "default" };
-  ASSERT_TRUE(StringVectorHelper(l, &v) == ErrorCode::InvalidArray);
+  ASSERT_TRUE(!StringVectorHelper(l, &v, &state));
 
   l = { "default" };
-  ASSERT_TRUE(StringVectorHelper(l, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(StringVectorHelper(l, nullptr, &state));
   ASSERT_TRUE(l.empty());
 
   return true;
@@ -339,6 +349,7 @@
 bool testVectorFilter()
 {
   Json::Value v(Json::arrayValue);
+  cmJSONState state;
   v.append("Hello");
   v.append("world!");
   v.append("ignore");
@@ -348,19 +359,19 @@
     "Hello",
     "world!",
   };
-  ASSERT_TRUE(StringVectorFilterHelper(l, &v) == ErrorCode::Success);
+  ASSERT_TRUE(StringVectorFilterHelper(l, &v, &state));
   ASSERT_TRUE(l == expected);
 
   v[1] = 2;
   l = { "default" };
-  ASSERT_TRUE(StringVectorFilterHelper(l, &v) == ErrorCode::InvalidString);
+  ASSERT_TRUE(!StringVectorFilterHelper(l, &v, &state));
 
   v = "Hello";
   l = { "default" };
-  ASSERT_TRUE(StringVectorFilterHelper(l, &v) == ErrorCode::InvalidArray);
+  ASSERT_TRUE(!StringVectorFilterHelper(l, &v, &state));
 
   l = { "default" };
-  ASSERT_TRUE(StringVectorFilterHelper(l, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(StringVectorFilterHelper(l, nullptr, &state));
   ASSERT_TRUE(l.empty());
 
   return true;
@@ -372,20 +383,21 @@
   v["field1"] = "Hello";
   v["field2"] = "world!";
   v["ignore"] = "ignore";
+  cmJSONState state;
 
   std::map<std::string, std::string> m{ { "key", "default" } };
   std::map<std::string, std::string> expected{ { "field1", "Hello" },
                                                { "field2", "world!" },
                                                { "ignore", "ignore" } };
-  ASSERT_TRUE(StringMapHelper(m, &v) == ErrorCode::Success);
+  ASSERT_TRUE(StringMapHelper(m, &v, &state));
   ASSERT_TRUE(m == expected);
 
   v = Json::arrayValue;
   m = { { "key", "default" } };
-  ASSERT_TRUE(StringMapHelper(m, &v) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(!StringMapHelper(m, &v, &state));
 
   m = { { "key", "default" } };
-  ASSERT_TRUE(StringMapHelper(m, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(StringMapHelper(m, nullptr, &state));
   ASSERT_TRUE(m.empty());
 
   return true;
@@ -394,6 +406,7 @@
 bool testMapFilter()
 {
   Json::Value v(Json::objectValue);
+  cmJSONState state;
   v["field1"] = "Hello";
   v["field2"] = "world!";
   v["ignore"] = "ignore";
@@ -401,15 +414,15 @@
   std::map<std::string, std::string> m{ { "key", "default" } };
   std::map<std::string, std::string> expected{ { "field1", "Hello" },
                                                { "field2", "world!" } };
-  ASSERT_TRUE(StringMapFilterHelper(m, &v) == ErrorCode::Success);
+  ASSERT_TRUE(StringMapFilterHelper(m, &v, &state));
   ASSERT_TRUE(m == expected);
 
   v = Json::arrayValue;
   m = { { "key", "default" } };
-  ASSERT_TRUE(StringMapFilterHelper(m, &v) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(!StringMapFilterHelper(m, &v, &state));
 
   m = { { "key", "default" } };
-  ASSERT_TRUE(StringMapFilterHelper(m, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(StringMapFilterHelper(m, nullptr, &state));
   ASSERT_TRUE(m.empty());
 
   return true;
@@ -418,13 +431,14 @@
 bool testOptional()
 {
   Json::Value v = "Hello";
+  cmJSONState state;
 
   cm::optional<std::string> str{ "default" };
-  ASSERT_TRUE(OptionalStringHelper(str, &v) == ErrorCode::Success);
+  ASSERT_TRUE(OptionalStringHelper(str, &v, &state));
   ASSERT_TRUE(str == "Hello");
 
   str.emplace("default");
-  ASSERT_TRUE(OptionalStringHelper(str, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(OptionalStringHelper(str, nullptr, &state));
   ASSERT_TRUE(str == cm::nullopt);
 
   return true;
@@ -433,25 +447,24 @@
 bool testRequired()
 {
   Json::Value v = "Hello";
-
   std::string str = "default";
   int i = 1;
-  ASSERT_TRUE(RequiredStringHelper(str, &v) == ErrorCode::Success);
+  cmJSONState state;
+  ASSERT_TRUE(RequiredStringHelper(str, &v, &state));
   ASSERT_TRUE(str == "Hello");
-  ASSERT_TRUE(RequiredIntHelper(i, &v) == ErrorCode::InvalidInt);
+  ASSERT_TRUE(!RequiredIntHelper(i, &v, &state));
 
   v = 2;
   str = "default";
   i = 1;
-  ASSERT_TRUE(RequiredStringHelper(str, &v) == ErrorCode::InvalidString);
-  ASSERT_TRUE(RequiredIntHelper(i, &v) == ErrorCode::Success);
+  ASSERT_TRUE(!RequiredStringHelper(str, &v, &state));
+  ASSERT_TRUE(RequiredIntHelper(i, &v, &state));
   ASSERT_TRUE(i == 2);
 
   str = "default";
   i = 1;
-  ASSERT_TRUE(RequiredStringHelper(str, nullptr) ==
-              ErrorCode::MissingRequired);
-  ASSERT_TRUE(RequiredIntHelper(i, nullptr) == ErrorCode::MissingRequired);
+  ASSERT_TRUE(!RequiredStringHelper(str, nullptr, &state));
+  ASSERT_TRUE(!RequiredIntHelper(i, nullptr, &state));
 
   return true;
 }
diff --git a/Tests/CMakeLib/testList.cxx b/Tests/CMakeLib/testList.cxx
new file mode 100644
index 0000000..f6ec720
--- /dev/null
+++ b/Tests/CMakeLib/testList.cxx
@@ -0,0 +1,995 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include <iostream>
+#include <stdexcept>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include <cmext/string_view>
+
+#include "cmList.h"
+
+namespace {
+
+void checkResult(bool success)
+{
+  if (!success) {
+    std::cout << " => failed";
+  }
+  std::cout << std::endl;
+}
+
+bool testConstructors()
+{
+  std::cout << "testConstructors()";
+
+  bool result = true;
+
+  {
+    cmList list;
+    if (!list.empty() || list != cmList{}) {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "aa;bb" };
+    if (list.size() != 2 || list.to_string() != "aa;bb") {
+      result = false;
+    }
+  }
+  {
+    cmList list1{ "aa", "bb" };
+    cmList list2("aa;bb"_s);
+
+    if (list1.size() != 2 || list2.size() != 2 || list1 != list2) {
+      result = false;
+    }
+    if (list1.to_string() != "aa;bb") {
+      result = false;
+    }
+    if (list1.to_string() != list2.to_string()) {
+      result = false;
+    }
+  }
+  {
+    std::vector<std::string> v{ "aa", "bb", "cc" };
+    cmList list(v.begin(), v.end());
+    if (list.size() != 3 || list.to_string() != "aa;bb;cc") {
+      result = false;
+    }
+  }
+  {
+    std::vector<std::string> values{ "aa;bb", "cc", "dd;ee" };
+    cmList list1(values.begin(), values.end());
+    cmList list2(values.begin(), values.end(), cmList::ExpandElements::No);
+
+    if (list1.size() != 5 || list1.to_string() != "aa;bb;cc;dd;ee") {
+      result = false;
+    }
+    if (list2.size() != 3 || list2.to_string() != "aa;bb;cc;dd;ee") {
+      result = false;
+    }
+  }
+  {
+    std::vector<std::string> values{ "aa;bb;;cc", "", "dd;ee" };
+    cmList list1(values.begin(), values.end(), cmList::ExpandElements::No,
+                 cmList::EmptyElements::No);
+    cmList list2(values.begin(), values.end(), cmList::ExpandElements::No,
+                 cmList::EmptyElements::Yes);
+    cmList list3(values.begin(), values.end(), cmList::ExpandElements::Yes,
+                 cmList::EmptyElements::No);
+    cmList list4(values.begin(), values.end(), cmList::ExpandElements::Yes,
+                 cmList::EmptyElements::Yes);
+
+    if (list1.size() != 2 || list1.to_string() != "aa;bb;;cc;dd;ee") {
+      result = false;
+    }
+    if (list2.size() != 3 || list2.to_string() != "aa;bb;;cc;;dd;ee") {
+      result = false;
+    }
+    if (list3.size() != 5 || list3.to_string() != "aa;bb;cc;dd;ee") {
+      result = false;
+    }
+    if (list4.size() != 7 || list4.to_string() != "aa;bb;;cc;;dd;ee") {
+      result = false;
+    }
+  }
+  {
+    std::vector<std::string> values{ "aa;bb", "cc", "dd;ee" };
+    cmList list1(values);
+    cmList list2(values, cmList::ExpandElements::No);
+
+    if (list1.size() != 5 || list1.to_string() != "aa;bb;cc;dd;ee") {
+      result = false;
+    }
+    if (list2.size() != 3 || list2.to_string() != "aa;bb;cc;dd;ee") {
+      result = false;
+    }
+  }
+  {
+    std::vector<std::string> values{ "aa", "bb", "cc", "dd", "ee" };
+    cmList list(std::move(values));
+
+    if (list.size() != 5 || list.to_string() != "aa;bb;cc;dd;ee") {
+      result = false;
+    }
+    if (!values.empty()) {
+      result = false;
+    }
+  }
+
+  checkResult(result);
+
+  return result;
+}
+
+bool testAssign()
+{
+  std::cout << "testAssign()";
+
+  bool result = true;
+
+  {
+    cmList list1{ "aa", "bb" };
+    cmList list2{ "cc", "dd" };
+
+    list2 = list1;
+    if (list1.size() != 2 || list2.size() != 2 || list1 != list2) {
+      result = false;
+    }
+    if (list1.to_string() != "aa;bb") {
+      result = false;
+    }
+    if (list1.to_string() != list2.to_string()) {
+      result = false;
+    }
+  }
+  {
+    cmList list1{ "aa", "bb" };
+    cmList list2{ "cc", "dd" };
+
+    list2 = std::move(list1);
+    if (!list1.empty() || list2.size() != 2) {
+      result = false;
+    }
+    if (list2.to_string() != "aa;bb") {
+      result = false;
+    }
+  }
+  {
+    std::vector<std::string> v{ "aa", "bb" };
+    cmList list{ "cc", "dd" };
+
+    list = std::move(v);
+    if (!v.empty() || list.size() != 2) {
+      result = false;
+    }
+    if (list.to_string() != "aa;bb") {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "cc", "dd" };
+
+    list = "aa;bb"_s;
+    if (list.size() != 2) {
+      result = false;
+    }
+    if (list.to_string() != "aa;bb") {
+      result = false;
+    }
+  }
+
+  checkResult(result);
+
+  return result;
+}
+
+bool testConversions()
+{
+  std::cout << "testConversions()";
+
+  bool result = true;
+
+  {
+    cmList list("a;b;c"_s);
+    std::string s = list.to_string();
+
+    if (s != "a;b;c") {
+      result = false;
+    }
+  }
+  {
+    cmList list("a;b;c"_s);
+    std::vector<std::string> v = list;
+
+    if (list.size() != 3 || v.size() != 3) {
+      result = false;
+    }
+  }
+  {
+    cmList list("a;b;c"_s);
+    std::vector<std::string> v = std::move(list);
+
+    // Microsoft compiler is not able to handle correctly the move semantics
+    // so the initial list is not moved, so do not check its size...
+    if (v.size() != 3) {
+      result = false;
+    }
+  }
+  {
+    cmList list("a;b;c"_s);
+    std::vector<std::string> v;
+
+    // compiler is not able to select the cmList conversion operator
+    // and the std::vector assignment operator using the move semantics
+    // v = std::move(list);
+    v = std::move(list.data());
+
+    if (!list.empty() || v.size() != 3) {
+      result = false;
+    }
+  }
+
+  checkResult(result);
+
+  return result;
+}
+
+bool testAccess()
+{
+  std::cout << "testAccess()";
+
+  bool result = true;
+
+  {
+    cmList list{ "a", "b", "c" };
+    if (list.get_item(1) != "b") {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "a", "b", "c" };
+    if (list.get_item(-3) != "a") {
+      result = false;
+    }
+  }
+  {
+    try {
+      cmList list{ "a", "b", "c" };
+      if (list.get_item(4) != "a") {
+        result = false;
+      }
+    } catch (std::out_of_range&) {
+    }
+  }
+  {
+    try {
+      cmList list{ "a", "b", "c" };
+      if (list.get_item(-4) != "a") {
+        result = false;
+      }
+    } catch (std::out_of_range&) {
+    }
+  }
+  {
+    cmList list{ "a", "b", "c", "d", "e" };
+    auto sublist = list.sublist(list.begin() + 1, list.begin() + 3);
+    if (sublist.size() != 2 || sublist != cmList{ "b", "c" }) {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "a", "b", "c", "d", "e" };
+    auto sublist = list.sublist(1, 2);
+    if (sublist.size() != 2 || sublist != cmList{ "b", "c" }) {
+      result = false;
+    }
+
+    sublist = list.sublist(1, cmList::npos);
+    if (sublist.size() != 4 || sublist != cmList{ "b", "c", "d", "e" }) {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "a", "b", "c", "d", "e", "f" };
+    auto sublist = list.get_items({ 1, 3, 5 });
+    if (sublist.size() != 3 || sublist != cmList{ "b", "d", "f" }) {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "a", "b", "c", "d", "e", "f" };
+    auto sublist = list.get_items({ 1, -3, 5, -3 });
+    if (sublist.size() != 4 || sublist != cmList{ "b", "d", "f", "d" }) {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "a", "b", "c", "d", "e", "f" };
+    try {
+      if (list.get_items({ 1, -3, 5, -3, 10 }).size() != 5) {
+        result = false;
+      }
+    } catch (std::out_of_range&) {
+    }
+  }
+  {
+    cmList list{ "a", "b", "c", "d", "e", "f" };
+
+    if (list.find("b") != 1) {
+      result = false;
+    }
+    if (list.find("x") != cmList::npos) {
+      result = false;
+    }
+  }
+
+  checkResult(result);
+
+  return result;
+}
+
+bool testModifiers()
+{
+  std::cout << "testModifiers()";
+
+  bool result = true;
+
+  {
+    cmList list{ "1;2;3;4;5" };
+
+    auto it = list.insert(list.begin() + 2, "6;7;8"_s);
+    if (list.size() != 8 || list.to_string() != "1;2;6;7;8;3;4;5") {
+      result = false;
+    }
+    if (*it != "6") {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "1;2;3;4;5" };
+
+    auto it =
+      list.insert(list.begin() + 2, "6;7;8"_s, cmList::ExpandElements::No);
+    if (list.size() != 6 || list.to_string() != "1;2;6;7;8;3;4;5") {
+      result = false;
+    }
+    if (*it != "6;7;8") {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "1;2;3;4;5" };
+    cmList v{ "6", "7", "8" };
+
+    auto it = list.insert(list.begin() + 2, v);
+    if (list.size() != 8 || list.to_string() != "1;2;6;7;8;3;4;5") {
+      result = false;
+    }
+    if (*it != "6") {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "1;2;3;4;5" };
+    cmList v{ "6", "7", "8" };
+
+    auto it = list.insert(list.begin() + 2, std::move(v));
+    if (list.size() != 8 || list.to_string() != "1;2;6;7;8;3;4;5") {
+      result = false;
+    }
+    if (*it != "6") {
+      result = false;
+    }
+
+    if (!v.empty()) {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "1;2;3;4;5" };
+    std::vector<std::string> v{ "6", "7", "8" };
+
+    auto it = list.insert(list.begin() + 2, v);
+    if (list.size() != 8 || list.to_string() != "1;2;6;7;8;3;4;5") {
+      result = false;
+    }
+    if (*it != "6") {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "1;2;3;4;5" };
+    std::vector<std::string> v{ "6;7", "8" };
+
+    auto it = list.insert(list.begin() + 2, v);
+    if (list.size() != 8 || list.to_string() != "1;2;6;7;8;3;4;5") {
+      result = false;
+    }
+    if (*it != "6") {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "1;2;3;4;5" };
+    std::vector<std::string> v{ "6;7", "8" };
+
+    auto it = list.insert(list.begin() + 2, v, cmList::ExpandElements::No);
+    if (list.size() != 7 || list.to_string() != "1;2;6;7;8;3;4;5") {
+      result = false;
+    }
+    if (*it != "6;7") {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "1;2;3;4;5" };
+    std::vector<std::string> v{ "6;;7", "8" };
+
+    auto it = list.insert(list.begin() + 2, v);
+    if (list.size() != 8 || list.to_string() != "1;2;6;7;8;3;4;5") {
+      result = false;
+    }
+    if (*it != "6") {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "1;2;3;4;5" };
+    std::vector<std::string> v{ "6;;7", "8" };
+
+    auto it = list.insert(list.begin() + 2, v, cmList::EmptyElements::Yes);
+    if (list.size() != 9 || list.to_string() != "1;2;6;;7;8;3;4;5") {
+      result = false;
+    }
+    if (*it != "6") {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "1;2;3;4;5" };
+    std::vector<std::string> v{ "6", "7", "8" };
+
+    auto it = list.insert(list.begin() + 2, std::move(v));
+    if (list.size() != 8 || list.to_string() != "1;2;6;7;8;3;4;5") {
+      result = false;
+    }
+    if (*it != "6") {
+      result = false;
+    }
+
+    if (!v.empty()) {
+      result = false;
+    }
+  }
+
+  checkResult(result);
+
+  return result;
+}
+
+bool testRemoveItems()
+{
+  std::cout << "testRemoveItems()";
+
+  bool result = true;
+
+  {
+    cmList list("a;b;c;d;e;f;g;h"_s);
+
+    list.remove_items({ 1, 3, 5 });
+
+    if (list.size() != 5 || list.to_string() != "a;c;e;g;h") {
+      result = false;
+    }
+  }
+  {
+    cmList list("a;b;c;b;a;d;e;f"_s);
+
+    list.remove_items({ "a", "b", "h" });
+
+    if (list.size() != 4 || list.to_string() != "c;d;e;f") {
+      result = false;
+    }
+  }
+  {
+    cmList list("a;b;c;d;e;f;g;h"_s);
+    std::vector<cmList::index_type> remove{ 1, 3, 5 };
+
+    list.remove_items(remove.begin(), remove.end());
+
+    if (list.size() != 5 || list.to_string() != "a;c;e;g;h") {
+      result = false;
+    }
+  }
+  {
+    cmList list("a;b;c;b;a;d;e;f"_s);
+    std::vector<std::string> remove{ "b", "a", "h" };
+
+    list.remove_items(remove.begin(), remove.end());
+
+    if (list.size() != 4 || list.to_string() != "c;d;e;f") {
+      result = false;
+    }
+  }
+
+  checkResult(result);
+
+  return result;
+}
+
+bool testRemoveDuplicates()
+{
+  std::cout << "testRemoveDuplicates()";
+
+  bool result = true;
+
+  {
+    cmList list("b;c;b;a;a;c;b;a;c;b"_s);
+
+    list.remove_duplicates();
+
+    if (list.size() != 3 || list.to_string() != "b;c;a") {
+      result = false;
+    }
+  }
+
+  checkResult(result);
+
+  return result;
+}
+
+bool testFilter()
+{
+  std::cout << "testFilter()";
+
+  bool result = true;
+
+  {
+    cmList list{ "AA", "Aa", "aA" };
+
+    list.filter("^A", cmList::FilterMode::INCLUDE);
+    if (list.size() != 2 || list.to_string() != "AA;Aa") {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "AA", "Aa", "aA" };
+
+    list.filter("^A", cmList::FilterMode::EXCLUDE);
+    if (list.size() != 1 || list.to_string() != "aA") {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "AA", "Aa", "aA" };
+
+    try {
+      list.filter("^(A", cmList::FilterMode::EXCLUDE);
+      if (list.size() != 1) {
+        result = false;
+      }
+    } catch (const std::invalid_argument&) {
+    }
+  }
+
+  checkResult(result);
+
+  return result;
+}
+
+bool testReverse()
+{
+  std::cout << "testReverse()";
+
+  bool result = true;
+
+  {
+    cmList list{ "a", "b", "c" };
+    if (list.reverse().to_string() != "c;b;a") {
+      result = false;
+    }
+  }
+
+  checkResult(result);
+
+  return result;
+}
+
+bool testSort()
+{
+  std::cout << "testSort()";
+
+  bool result = true;
+
+  using SortConfiguration = cmList::SortConfiguration;
+
+  {
+    cmList list{ "A", "D", "C", "B", "A" };
+
+    list.sort();
+    if (list.to_string() != "A;A;B;C;D") {
+      result = false;
+    }
+
+    list.sort({ SortConfiguration::OrderMode::DESCENDING,
+                SortConfiguration::CompareMethod::DEFAULT,
+                SortConfiguration::CaseSensitivity::DEFAULT });
+    if (list.to_string() != "D;C;B;A;A") {
+      result = false;
+    }
+  }
+  {
+    SortConfiguration sortCfg;
+    cmList list{ "1.0", "1.1", "2.5", "10.2" };
+
+    list.sort(sortCfg);
+    if (list.to_string() != "1.0;1.1;10.2;2.5") {
+      result = false;
+    }
+
+    sortCfg.Compare = SortConfiguration::CompareMethod::NATURAL;
+    list.sort(sortCfg);
+    if (list.to_string() != "1.0;1.1;2.5;10.2") {
+      result = false;
+    }
+
+    sortCfg.Order = SortConfiguration::OrderMode::DESCENDING;
+    list.sort(sortCfg);
+    if (list.to_string() != "10.2;2.5;1.1;1.0") {
+      result = false;
+    }
+  }
+  {
+    SortConfiguration sortCfg;
+    cmList list{ "/zz/bb.cc", "/xx/yy/dd.cc", "/aa/cc.aa" };
+
+    list.sort(sortCfg);
+    if (list.to_string() != "/aa/cc.aa;/xx/yy/dd.cc;/zz/bb.cc") {
+      result = false;
+    }
+
+    sortCfg.Compare = SortConfiguration::CompareMethod::FILE_BASENAME;
+    if (list.sort(sortCfg).to_string() != "/zz/bb.cc;/aa/cc.aa;/xx/yy/dd.cc") {
+      result = false;
+    }
+  }
+  {
+    SortConfiguration sortCfg;
+    cmList list{ "c/B", "a/c", "B/a" };
+
+    if (list.sort().to_string() != "B/a;a/c;c/B") {
+      result = false;
+    }
+
+    sortCfg.Case = SortConfiguration::CaseSensitivity::INSENSITIVE;
+    if (list.sort(sortCfg).to_string() != "a/c;B/a;c/B") {
+      result = false;
+    }
+  }
+
+  checkResult(result);
+
+  return result;
+}
+
+bool testTransform()
+{
+  std::cout << "testTransform()";
+
+  bool result = true;
+
+  using AT = cmList::TransformSelector::AT;
+  using FOR = cmList::TransformSelector::FOR;
+  using REGEX = cmList::TransformSelector::REGEX;
+
+  {
+    cmList list({ "AA", "BB", "CC", "DD", "EE" });
+
+    list.transform(cmList::TransformAction::APPEND, "-X");
+    if (list.to_string() != "AA-X;BB-X;CC-X;DD-X;EE-X") {
+      result = false;
+    }
+  }
+  {
+    cmList list({ "AA", "BB", "CC", "DD", "EE" });
+
+    list.transform(cmList::TransformAction::PREPEND, "X-");
+    if (list.to_string() != "X-AA;X-BB;X-CC;X-DD;X-EE") {
+      result = false;
+    }
+  }
+  {
+    cmList list({ "AA", "BB", "CC", "DD", "EE" });
+
+    list.transform(cmList::TransformAction::TOLOWER);
+    if (list.to_string() != "aa;bb;cc;dd;ee") {
+      result = false;
+    }
+  }
+  {
+    cmList list({ "aa", "bb", "cc", "dd", "ee" });
+
+    list.transform(cmList::TransformAction::TOUPPER);
+    if (list.to_string() != "AA;BB;CC;DD;EE") {
+      result = false;
+    }
+  }
+  {
+    cmList list({ "  AA", "BB  ", "  CC  ", "DD", "EE" });
+
+    list.transform(cmList::TransformAction::STRIP);
+    if (list.to_string() != "AA;BB;CC;DD;EE") {
+      result = false;
+    }
+  }
+  {
+    cmList list({ "$<CONFIG>AA", "BB$<OR>", "C$<AND>C", "$<OR>DD$<AND>",
+                  "$<>E$<>E$<>" });
+
+    list.transform(cmList::TransformAction::GENEX_STRIP);
+    if (list.to_string() != "AA;BB;CC;DD;EE") {
+      result = false;
+    }
+  }
+  {
+    cmList list({ "ABC", "BBCB", "BCCCBC", "BCBCDD", "EBCBCEBC" });
+
+    list.transform(cmList::TransformAction::REPLACE, "^BC|BC$", "X");
+    if (list.to_string() != "AX;BBCB;XCCX;XXDD;EBCBCEX") {
+      result = false;
+    }
+  }
+  {
+    auto atSelector = cmList::TransformSelector::New<AT>({ 1, 2, 4 });
+    cmList list({ "AA", "BB", "CC", "DD", "EE" });
+
+    list.transform(cmList::TransformAction::TOLOWER, std::move(atSelector));
+    if (list.to_string() != "AA;bb;cc;DD;ee") {
+      result = false;
+    }
+  }
+  {
+    auto atSelector = cmList::TransformSelector::New<AT>({ 1, 2, -1 });
+    cmList list({ "AA", "BB", "CC", "DD", "EE" });
+
+    list.transform(cmList::TransformAction::TOLOWER, std::move(atSelector));
+    if (list.to_string() != "AA;bb;cc;DD;ee") {
+      result = false;
+    }
+  }
+  {
+    auto forSelector = cmList::TransformSelector::New<FOR>({ 1, 3 });
+    cmList list({ "AA", "BB", "CC", "DD", "EE" });
+
+    list.transform(cmList::TransformAction::TOLOWER, std::move(forSelector));
+    if (list.to_string() != "AA;bb;cc;dd;EE") {
+      result = false;
+    }
+  }
+  {
+    auto forSelector = cmList::TransformSelector::New<FOR>({ 0, 4, 2 });
+    cmList list({ "AA", "BB", "CC", "DD", "EE" });
+
+    list.transform(cmList::TransformAction::TOLOWER, std::move(forSelector));
+    if (list.to_string() != "aa;BB;cc;DD;ee") {
+      result = false;
+    }
+  }
+  {
+    auto regexSelector = cmList::TransformSelector::New<REGEX>("^(A|D|E)");
+    cmList list({ "AA", "BB", "CC", "DD", "EE" });
+
+    list.transform(cmList::TransformAction::TOLOWER, std::move(regexSelector));
+    if (list.to_string() != "aa;BB;CC;dd;ee") {
+      result = false;
+    }
+  }
+
+  checkResult(result);
+
+  return result;
+}
+
+bool testStaticModifiers()
+{
+  std::cout << "testStaticModifiers()";
+
+  bool result = true;
+
+  {
+    std::vector<std::string> v{ "a", "b", "c" };
+    cmList::assign(v, "d;e"_s);
+
+    if (v.size() != 2 || v[0] != "d" || v[1] != "e") {
+      result = false;
+    }
+  }
+  {
+    std::vector<std::string> v{ "a", "b", "c" };
+    cmList::append(v, "d;;e"_s);
+
+    if (v.size() != 5 || v[3] != "d" || v[4] != "e") {
+      result = false;
+    }
+  }
+  {
+    std::vector<std::string> v{ "a", "b", "c" };
+    cmList::append(v, "d;;e"_s, cmList::EmptyElements::Yes);
+
+    if (v.size() != 6 || v[3] != "d" || !v[4].empty() || v[5] != "e") {
+      result = false;
+    }
+  }
+  {
+    std::vector<std::string> v{ "a", "b", "c" };
+    cmList::prepend(v, "d;e"_s);
+
+    if (v.size() != 5 || v[0] != "d" || v[1] != "e") {
+      result = false;
+    }
+  }
+  {
+    std::vector<std::string> v{ "a", "b", "c" };
+    cmList::prepend(v, "d;;e"_s, cmList::EmptyElements::Yes);
+
+    if (v.size() != 6 || v[0] != "d" || !v[1].empty() || v[2] != "e") {
+      result = false;
+    }
+  }
+  {
+    std::string list{ "a;b;c" };
+    cmList::append(list, "d;e"_s);
+
+    if (list != "a;b;c;d;e") {
+      result = false;
+    }
+  }
+  {
+    std::string list;
+    cmList::append(list, "d;e"_s);
+
+    if (list != "d;e") {
+      result = false;
+    }
+  }
+  {
+    std::string list{ "a;b;c" };
+    cmList::append(list, "");
+
+    if (list != "a;b;c;") {
+      result = false;
+    }
+  }
+  {
+    std::string list{ "a;b;c" };
+    std::vector<std::string> v{ "d", "e" };
+    cmList::append(list, v.begin(), v.end());
+
+    if (list != "a;b;c;d;e") {
+      result = false;
+    }
+  }
+  {
+    std::string list{ "a;b;c" };
+    std::vector<std::string> v;
+    cmList::append(list, v.begin(), v.end());
+
+    if (list != "a;b;c") {
+      result = false;
+    }
+  }
+  {
+    std::string list;
+    std::vector<std::string> v{ "d", "e" };
+    cmList::append(list, v.begin(), v.end());
+
+    if (list != "d;e") {
+      result = false;
+    }
+  }
+  {
+    std::string list{ "a;b;c" };
+    cmList::prepend(list, "d;e");
+
+    if (list != "d;e;a;b;c") {
+      result = false;
+    }
+  }
+  {
+    std::string list;
+    cmList::prepend(list, "d;e");
+
+    if (list != "d;e") {
+      result = false;
+    }
+  }
+  {
+    std::string list{ "a;b;c" };
+    cmList::prepend(list, "");
+
+    if (list != ";a;b;c") {
+      result = false;
+    }
+  }
+  {
+    std::string list{ "a;b;c" };
+    std::vector<std::string> v{ "d", "e" };
+    cmList::prepend(list, v.begin(), v.end());
+
+    if (list != "d;e;a;b;c") {
+      result = false;
+    }
+  }
+  {
+    std::string list{ "a;b;c" };
+    std::vector<std::string> v;
+    cmList::prepend(list, v.begin(), v.end());
+
+    if (list != "a;b;c") {
+      result = false;
+    }
+  }
+  {
+    std::string list;
+    std::vector<std::string> v{ "d", "e" };
+    cmList::prepend(list, v.begin(), v.end());
+
+    if (list != "d;e") {
+      result = false;
+    }
+  }
+
+  checkResult(result);
+
+  return result;
+}
+}
+
+int testList(int /*unused*/, char* /*unused*/[])
+{
+  int result = 0;
+
+  if (!testConstructors()) {
+    result = 1;
+  }
+  if (!testAssign()) {
+    result = 1;
+  }
+  if (!testConversions()) {
+    result = 1;
+  }
+  if (!testAccess()) {
+    result = 1;
+  }
+  if (!testModifiers()) {
+    result = 1;
+  }
+  if (!testRemoveItems()) {
+    result = 1;
+  }
+  if (!testRemoveDuplicates()) {
+    result = 1;
+  }
+  if (!testFilter()) {
+    result = 1;
+  }
+  if (!testReverse()) {
+    result = 1;
+  }
+  if (!testSort()) {
+    result = 1;
+  }
+  if (!testTransform()) {
+    result = 1;
+  }
+  if (!testStaticModifiers()) {
+    result = 1;
+  }
+
+  return result;
+}
diff --git a/Tests/CMakeLib/testRST.expect b/Tests/CMakeLib/testRST.expect
index 5e3cdb1..424b7d4 100644
--- a/Tests/CMakeLib/testRST.expect
+++ b/Tests/CMakeLib/testRST.expect
@@ -26,10 +26,15 @@
 Generator expression ``some genex`` with space and target.
 Generator expression ``$<SOME_GENEX>`` with brackets, space, and target.
 Generator expression ``$<SOME_GENEX:...>`` with brackets, parameter, space, and target.
-Inline literal ``~!@#$%^&*( )_+-=\\[]{}'":;,<>.?/``.
+Inline cref ``Link Dest``.
+Inline cref ``Link_Dest_<Placeholder>``.
+Inline cref ``Link Text``.
+Inline cref ``Link_Text_<Placeholder>``.
+Inline link Link Dest.
 Inline link Link Text.
 Inline link Link Text <With \-escaped Brackets>.
 Inline literal ``__`` followed by inline link Link Text.
+Inline literal ``~!@#$%^&*( )_+-=\\[]{}'":;,<>.?/``.
 
 First TOC entry.
 
@@ -46,7 +51,8 @@
 Bracket Comment Content
 ]
 
-.. cmake:command:: some_cmd
+.. cmake:command::
+   some_cmd
 
    Command some_cmd description.
 
@@ -54,7 +60,8 @@
 
    Command other_cmd description.
 
-.. cmake:envvar:: some_var
+.. cmake:envvar::
+   some_var
 
    Environment variable some_var description.
 
@@ -62,7 +69,8 @@
 
    Environment variable other_var description.
 
-.. cmake:genex:: SOME_GENEX
+.. cmake:genex::
+   SOME_GENEX
 
    Generator expression SOME_GENEX description.
 
@@ -70,7 +78,17 @@
 
    Generator expression $<OTHER_GENEX> description.
 
-.. cmake:variable:: some_var
+.. cmake:signature::
+   some_command(SOME_SIGNATURE)
+
+   Command some_command SOME_SIGNATURE description.
+
+.. signature:: other_command(OTHER_SIGNATURE)
+
+   Command other_command OTHER_SIGNATURE description.
+
+.. cmake:variable::
+   some_var
 
    Variable some_var description.
 
diff --git a/Tests/CMakeLib/testRST.rst b/Tests/CMakeLib/testRST.rst
index 4139801..3a38407 100644
--- a/Tests/CMakeLib/testRST.rst
+++ b/Tests/CMakeLib/testRST.rst
@@ -33,10 +33,15 @@
 Generator expression :genex:`some genex <SOME_GENEX>` with space and target.
 Generator expression :genex:`$<SOME_GENEX> <SOME_GENEX>` with brackets, space, and target.
 Generator expression :genex:`$<SOME_GENEX:...> <SOME_GENEX>` with brackets, parameter, space, and target.
-Inline literal ``~!@#$%^&*( )_+-=\\[]{}'":;,<>.?/``.
+Inline cref :cref:`Link Dest`.
+Inline cref :cref:`Link_Dest_<Placeholder>`.
+Inline cref :cref:`Link Text <ExternalDest>`.
+Inline cref :cref:`Link_Text_<Placeholder> <ExternalDest>`.
+Inline link `Link Dest`_.
 Inline link `Link Text <ExternalDest>`_.
 Inline link `Link Text \<With \\-escaped Brackets\> <ExternalDest>`_.
 Inline literal ``__`` followed by inline link `Link Text <InternalDest_>`_.
+Inline literal ``~!@#$%^&*( )_+-=\\[]{}'":;,<>.?/``.
 
 .. |not replaced| replace:: not replaced through toctree
 .. |not replaced in literal| replace:: replaced in parsed literal
@@ -49,7 +54,8 @@
 
 .. cmake-module:: testRSTmod.cmake
 
-.. cmake:command:: some_cmd
+.. cmake:command::
+   some_cmd
 
    Command some_cmd description.
 
@@ -57,7 +63,8 @@
 
    Command other_cmd description.
 
-.. cmake:envvar:: some_var
+.. cmake:envvar::
+   some_var
 
    Environment variable some_var description.
 
@@ -65,7 +72,8 @@
 
    Environment variable other_var description.
 
-.. cmake:genex:: SOME_GENEX
+.. cmake:genex::
+   SOME_GENEX
 
    Generator expression SOME_GENEX description.
 
@@ -73,7 +81,17 @@
 
    Generator expression $<OTHER_GENEX> description.
 
-.. cmake:variable:: some_var
+.. cmake:signature::
+   some_command(SOME_SIGNATURE)
+
+   Command some_command SOME_SIGNATURE description.
+
+.. signature:: other_command(OTHER_SIGNATURE)
+
+   Command other_command OTHER_SIGNATURE description.
+
+.. cmake:variable::
+   some_var
 
    Variable some_var description.
 
diff --git a/Tests/CMakeLib/testStringAlgorithms.cxx b/Tests/CMakeLib/testStringAlgorithms.cxx
index 1bb23df..78442ba 100644
--- a/Tests/CMakeLib/testStringAlgorithms.cxx
+++ b/Tests/CMakeLib/testStringAlgorithms.cxx
@@ -1,12 +1,11 @@
 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
    file Copyright.txt or https://cmake.org/licensing for details.  */
 
-#include <cmConfigure.h> // IWYU pragma: keep
+#include "cmConfigure.h" // IWYU pragma: keep
 
 #include <iostream>
 #include <sstream>
 #include <string>
-#include <type_traits>
 #include <utility>
 #include <vector>
 
diff --git a/Tests/CMakeLib/testSystemTools.cxx b/Tests/CMakeLib/testSystemTools.cxx
index 754205e..0f0c025 100644
--- a/Tests/CMakeLib/testSystemTools.cxx
+++ b/Tests/CMakeLib/testSystemTools.cxx
@@ -36,6 +36,53 @@
            "cmSystemTools::UpperCase");
 
   // ----------------------------------------------------------------------
+  // Test cmSystemTools::VersionCompare
+  cmAssert(cmSystemTools::VersionCompareEqual("", ""),
+           "VersionCompareEqual empty string");
+  cmAssert(!cmSystemTools::VersionCompareGreater("", ""),
+           "VersionCompareGreater empty string");
+  cmAssert(cmSystemTools::VersionCompareEqual("1", "1a"),
+           "VersionCompareEqual letters");
+  cmAssert(!cmSystemTools::VersionCompareGreater("1", "1a"),
+           "VersionCompareGreater letters");
+  cmAssert(cmSystemTools::VersionCompareEqual("001", "1"),
+           "VersionCompareEqual leading zeros equal");
+  cmAssert(!cmSystemTools::VersionCompareGreater("001", "1"),
+           "VersionCompareGreater leading zeros equal");
+  cmAssert(!cmSystemTools::VersionCompareEqual("002", "1"),
+           "VersionCompareEqual leading zeros greater");
+  cmAssert(cmSystemTools::VersionCompareGreater("002", "1"),
+           "VersionCompareGreater leading zeros greater");
+  cmAssert(!cmSystemTools::VersionCompareEqual("6.2.1", "6.3.1"),
+           "VersionCompareEqual components less");
+  cmAssert(!cmSystemTools::VersionCompareGreater("6.2.1", "6.3.1"),
+           "VersionCompareGreater components less");
+  cmAssert(!cmSystemTools::VersionCompareEqual("6.2.1", "6.2"),
+           "VersionCompareEqual different length");
+  cmAssert(cmSystemTools::VersionCompareGreater("6.2.1", "6.2"),
+           "VersionCompareGreater different length");
+  cmAssert(
+    !cmSystemTools::VersionCompareEqual(
+      "3.14159265358979323846264338327950288419716939937510582097494459230",
+      "3.14159265358979323846264338327950288419716939937510582097494459231"),
+    "VersionCompareEqual long number");
+  cmAssert(
+    !cmSystemTools::VersionCompareGreater(
+      "3.14159265358979323846264338327950288419716939937510582097494459230",
+      "3.14159265358979323846264338327950288419716939937510582097494459231"),
+    "VersionCompareGreater long number");
+  cmAssert(
+    !cmSystemTools::VersionCompareEqual(
+      "3.141592653589793238462643383279502884197169399375105820974944592307",
+      "3.14159265358979323846264338327950288419716939937510582097494459231"),
+    "VersionCompareEqual more digits");
+  cmAssert(
+    cmSystemTools::VersionCompareGreater(
+      "3.141592653589793238462643383279502884197169399375105820974944592307",
+      "3.14159265358979323846264338327950288419716939937510582097494459231"),
+    "VersionCompareGreater more digits");
+
+  // ----------------------------------------------------------------------
   // Test cmSystemTools::strverscmp
   cmAssert(cmSystemTools::strverscmp("", "") == 0, "strverscmp empty string");
   cmAssert(cmSystemTools::strverscmp("abc", "") > 0,
diff --git a/Tests/CMakeLib/testUVProcessChain.cxx b/Tests/CMakeLib/testUVProcessChain.cxx
index c924083..cbb4384 100644
--- a/Tests/CMakeLib/testUVProcessChain.cxx
+++ b/Tests/CMakeLib/testUVProcessChain.cxx
@@ -11,6 +11,7 @@
 #include <cm3p/uv.h>
 
 #include "cmGetPipes.h"
+#include "cmStringAlgorithms.h"
 #include "cmUVHandlePtr.h"
 #include "cmUVProcessChain.h"
 #include "cmUVStreambuf.h"
@@ -228,6 +229,61 @@
   return true;
 }
 
+bool testUVProcessChainBuiltinMerged(const char* helperCommand)
+{
+  cmUVProcessChainBuilder builder;
+  std::unique_ptr<cmUVProcessChain> chain;
+  builder.AddCommand({ helperCommand, "echo" })
+    .AddCommand({ helperCommand, "capitalize" })
+    .AddCommand({ helperCommand, "dedup" })
+    .SetMergedBuiltinStreams();
+
+  if (!checkExecution(builder, chain)) {
+    return false;
+  }
+
+  if (!chain->OutputStream()) {
+    std::cout << "OutputStream() was null, expecting not null" << std::endl;
+    return false;
+  }
+  if (!chain->ErrorStream()) {
+    std::cout << "ErrorStream() was null, expecting not null" << std::endl;
+    return false;
+  }
+  if (chain->OutputStream() != chain->ErrorStream()) {
+    std::cout << "OutputStream() and ErrorStream() expected to be the same"
+              << std::endl;
+    return false;
+  }
+
+  std::string merged = getInput(*chain->OutputStream());
+  auto qemuErrorPos = merged.find("qemu:");
+  if (qemuErrorPos != std::string::npos) {
+    merged.resize(qemuErrorPos);
+  }
+  if (merged.length() != cmStrLen("HELO WRD!123") ||
+      merged.find('1') == std::string::npos ||
+      merged.find('2') == std::string::npos ||
+      merged.find('3') == std::string::npos) {
+    std::cout << "Expected output to contain '1', '2', and '3', was \""
+              << merged << "\"" << std::endl;
+    return false;
+  }
+  std::string output;
+  for (auto const& c : merged) {
+    if (c != '1' && c != '2' && c != '3') {
+      output += c;
+    }
+  }
+  if (output != "HELO WRD!") {
+    std::cout << "Output was \"" << output << "\", expected \"HELO WRD!\""
+              << std::endl;
+    return false;
+  }
+
+  return true;
+}
+
 bool testUVProcessChainExternal(const char* helperCommand)
 {
   cmUVProcessChainBuilder builder;
@@ -314,6 +370,57 @@
   return true;
 }
 
+bool testUVProcessChainCwdUnchanged(const char* helperCommand)
+{
+  cmUVProcessChainBuilder builder;
+  builder.AddCommand({ helperCommand, "pwd" })
+    .SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT)
+    .SetBuiltinStream(cmUVProcessChainBuilder::Stream_ERROR);
+
+  auto chain = builder.Start();
+  chain.Wait();
+  if (chain.GetStatus().front()->ExitStatus != 0) {
+    std::cout << "Exit status was " << chain.GetStatus().front()->ExitStatus
+              << ", expecting 0" << std::endl;
+    return false;
+  }
+
+  auto cwd = getInput(*chain.OutputStream());
+  if (!cmHasLiteralSuffix(cwd, "/Tests/CMakeLib")) {
+    std::cout << "Working directory was \"" << cwd
+              << "\", expected to end in \"/Tests/CMakeLib\"" << std::endl;
+    return false;
+  }
+
+  return true;
+}
+
+bool testUVProcessChainCwdChanged(const char* helperCommand)
+{
+  cmUVProcessChainBuilder builder;
+  builder.AddCommand({ helperCommand, "pwd" })
+    .SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT)
+    .SetBuiltinStream(cmUVProcessChainBuilder::Stream_ERROR)
+    .SetWorkingDirectory("..");
+
+  auto chain = builder.Start();
+  chain.Wait();
+  if (chain.GetStatus().front()->ExitStatus != 0) {
+    std::cout << "Exit status was " << chain.GetStatus().front()->ExitStatus
+              << ", expecting 0" << std::endl;
+    return false;
+  }
+
+  auto cwd = getInput(*chain.OutputStream());
+  if (!cmHasLiteralSuffix(cwd, "/Tests")) {
+    std::cout << "Working directory was \"" << cwd
+              << "\", expected to end in \"/Tests\"" << std::endl;
+    return false;
+  }
+
+  return true;
+}
+
 int testUVProcessChain(int argc, char** const argv)
 {
   if (argc < 2) {
@@ -326,6 +433,11 @@
     return -1;
   }
 
+  if (!testUVProcessChainBuiltinMerged(argv[1])) {
+    std::cout << "While executing testUVProcessChainBuiltinMerged().\n";
+    return -1;
+  }
+
   if (!testUVProcessChainExternal(argv[1])) {
     std::cout << "While executing testUVProcessChainExternal().\n";
     return -1;
@@ -336,5 +448,15 @@
     return -1;
   }
 
+  if (!testUVProcessChainCwdUnchanged(argv[1])) {
+    std::cout << "While executing testUVProcessChainCwdUnchanged().\n";
+    return -1;
+  }
+
+  if (!testUVProcessChainCwdChanged(argv[1])) {
+    std::cout << "While executing testUVProcessChainCwdChanged().\n";
+    return -1;
+  }
+
   return 0;
 }
diff --git a/Tests/CMakeLib/testUVProcessChainHelper.cxx b/Tests/CMakeLib/testUVProcessChainHelper.cxx
index bc0ef8e..82dafd2 100644
--- a/Tests/CMakeLib/testUVProcessChainHelper.cxx
+++ b/Tests/CMakeLib/testUVProcessChainHelper.cxx
@@ -7,6 +7,8 @@
 #include <string>
 #include <thread>
 
+#include "cmSystemTools.h"
+
 static std::string getStdin()
 {
   char buffer[1024];
@@ -67,6 +69,11 @@
     std::abort();
 #endif
   }
+  if (command == "pwd") {
+    std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
+    std::cout << cwd << std::flush;
+    return 0;
+  }
 
   return -1;
 }
diff --git a/Tests/CMakeLib/testVisualStudioSlnParser.cxx b/Tests/CMakeLib/testVisualStudioSlnParser.cxx
index c1bf3d4..3485bac 100644
--- a/Tests/CMakeLib/testVisualStudioSlnParser.cxx
+++ b/Tests/CMakeLib/testVisualStudioSlnParser.cxx
@@ -80,7 +80,6 @@
       "cmsysProcessFwd9x",
       "cmsysTestDynload",
       "cmsysTestProcess",
-      "cmsysTestSharedForward",
       "cmsysTestsC",
       "cmsysTestsCxx",
       "cmsys_c",
diff --git a/Tests/CMakeLib/testVisualStudioSlnParser_data/valid.sln-file b/Tests/CMakeLib/testVisualStudioSlnParser_data/valid.sln-file
index 395b953..1f148fc 100644
--- a/Tests/CMakeLib/testVisualStudioSlnParser_data/valid.sln-file
+++ b/Tests/CMakeLib/testVisualStudioSlnParser_data/valid.sln-file
@@ -21,7 +21,6 @@
     {29D5FCAF-20D0-4DEF-8529-F035C249E996} = {29D5FCAF-20D0-4DEF-8529-F035C249E996}
     {A0421DCA-AC3E-42D0-94AC-379A21A1E591} = {A0421DCA-AC3E-42D0-94AC-379A21A1E591}
     {C6AF7E57-CE57-4462-AE1D-BF520701480E} = {C6AF7E57-CE57-4462-AE1D-BF520701480E}
-    {F2CAAAB3-9568-4284-B8E3-13955183A6D7} = {F2CAAAB3-9568-4284-B8E3-13955183A6D7}
     {D8294E4A-03C5-43D7-AE35-15603F502DC0} = {D8294E4A-03C5-43D7-AE35-15603F502DC0}
     {A4921D15-411F-436A-B6F3-F8381652A8E1} = {A4921D15-411F-436A-B6F3-F8381652A8E1}
     {60BEB3AF-B4EF-4363-8747-C40177BC2D9C} = {60BEB3AF-B4EF-4363-8747-C40177BC2D9C}
@@ -220,12 +219,6 @@
     {60BEB3AF-B4EF-4363-8747-C40177BC2D9C} = {60BEB3AF-B4EF-4363-8747-C40177BC2D9C}
   EndProjectSection
 EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cmsysTestSharedForward", "Source\kwsys\cmsysTestSharedForward.vcxproj", "{F2CAAAB3-9568-4284-B8E3-13955183A6D7}"
-  ProjectSection(ProjectDependencies) = postProject
-    {90BC31D7-A3E8-4F04-8049-2236C239A044} = {90BC31D7-A3E8-4F04-8049-2236C239A044}
-    {60BEB3AF-B4EF-4363-8747-C40177BC2D9C} = {60BEB3AF-B4EF-4363-8747-C40177BC2D9C}
-  EndProjectSection
-EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cmsysTestsC", "Source\kwsys\cmsysTestsC.vcxproj", "{D8294E4A-03C5-43D7-AE35-15603F502DC0}"
   ProjectSection(ProjectDependencies) = postProject
     {90BC31D7-A3E8-4F04-8049-2236C239A044} = {90BC31D7-A3E8-4F04-8049-2236C239A044}
@@ -528,14 +521,6 @@
     {C6AF7E57-CE57-4462-AE1D-BF520701480E}.MinSizeRel|x64.Build.0 = MinSizeRel|x64
     {C6AF7E57-CE57-4462-AE1D-BF520701480E}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
     {C6AF7E57-CE57-4462-AE1D-BF520701480E}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
-    {F2CAAAB3-9568-4284-B8E3-13955183A6D7}.Debug|x64.ActiveCfg = Debug|x64
-    {F2CAAAB3-9568-4284-B8E3-13955183A6D7}.Debug|x64.Build.0 = Debug|x64
-    {F2CAAAB3-9568-4284-B8E3-13955183A6D7}.Release|x64.ActiveCfg = Release|x64
-    {F2CAAAB3-9568-4284-B8E3-13955183A6D7}.Release|x64.Build.0 = Release|x64
-    {F2CAAAB3-9568-4284-B8E3-13955183A6D7}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64
-    {F2CAAAB3-9568-4284-B8E3-13955183A6D7}.MinSizeRel|x64.Build.0 = MinSizeRel|x64
-    {F2CAAAB3-9568-4284-B8E3-13955183A6D7}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
-    {F2CAAAB3-9568-4284-B8E3-13955183A6D7}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
     {D8294E4A-03C5-43D7-AE35-15603F502DC0}.Debug|x64.ActiveCfg = Debug|x64
     {D8294E4A-03C5-43D7-AE35-15603F502DC0}.Debug|x64.Build.0 = Debug|x64
     {D8294E4A-03C5-43D7-AE35-15603F502DC0}.Release|x64.ActiveCfg = Release|x64
@@ -667,7 +652,6 @@
     {29D5FCAF-20D0-4DEF-8529-F035C249E996} = {EF1DFA45-6F7A-4760-8EB5-69A8A221FC54}
     {A0421DCA-AC3E-42D0-94AC-379A21A1E591} = {EF1DFA45-6F7A-4760-8EB5-69A8A221FC54}
     {C6AF7E57-CE57-4462-AE1D-BF520701480E} = {EF1DFA45-6F7A-4760-8EB5-69A8A221FC54}
-    {F2CAAAB3-9568-4284-B8E3-13955183A6D7} = {EF1DFA45-6F7A-4760-8EB5-69A8A221FC54}
     {D8294E4A-03C5-43D7-AE35-15603F502DC0} = {EF1DFA45-6F7A-4760-8EB5-69A8A221FC54}
     {A4921D15-411F-436A-B6F3-F8381652A8E1} = {EF1DFA45-6F7A-4760-8EB5-69A8A221FC54}
     {60BEB3AF-B4EF-4363-8747-C40177BC2D9C} = {EF1DFA45-6F7A-4760-8EB5-69A8A221FC54}
diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt
index c22f704..5ef77fd 100644
--- a/Tests/CMakeLists.txt
+++ b/Tests/CMakeLists.txt
@@ -40,10 +40,12 @@
 endif()
 
 # Suppress generator deprecation warnings in test suite.
-if(CMAKE_GENERATOR MATCHES "^Visual Studio 11 2012")
-  set(TEST_WARN_VS11_CODE "set(ENV{CMAKE_WARN_VS11} OFF)")
+if(CMAKE_GENERATOR MATCHES "^Visual Studio 9 2008")
+  set(TEST_WARN_VS_CODE "set(ENV{CMAKE_WARN_VS9} OFF)")
+elseif(CMAKE_GENERATOR MATCHES "^Visual Studio 11 2012")
+  set(TEST_WARN_VS_CODE "set(ENV{CMAKE_WARN_VS11} OFF)")
 else()
-  set(TEST_WARN_VS11_CODE "")
+  set(TEST_WARN_VS_CODE "")
 endif()
 
 # 3.9 or later provides a definitive answer to whether we are multi-config
@@ -744,36 +746,6 @@
   ADD_LINK_FLAGS_TEST(mod_flags_config dll_flags_config)
   ADD_LINK_FLAGS_TEST(exe_flags_config mod_flags_config)
 
-  # If we are running right now with a Unix Makefiles or Ninja based generator,
-  # build the "Simple" test with the ExtraGenerators, if available
-  # This doesn't test whether the generated project files work (unfortunately),
-  # mainly it tests that cmake doesn't crash when generating these project files.
-  if(CMAKE_GENERATOR MATCHES "^(Unix Makefiles|Ninja)$"
-      AND NOT "${CMAKE_CURRENT_BINARY_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}")
-    foreach(
-      extraGenerator
-      IN ITEMS
-        "CodeBlocks"
-        "CodeLite"
-        "Eclipse CDT4"
-        "Kate"
-        "Sublime Text 2"
-      )
-      string(REPLACE " " "" extraGeneratorTestName "Simple_${extraGenerator}Generator")
-      add_test(${extraGeneratorTestName} ${CMAKE_CTEST_COMMAND}
-        --build-and-test
-        "${CMake_SOURCE_DIR}/Tests/Simple"
-        "${CMake_BINARY_DIR}/Tests/${extraGeneratorTestName}"
-        --build-two-config
-        --build-generator "${extraGenerator} - ${CMAKE_GENERATOR}"
-        --build-generator-platform "${CMAKE_GENERATOR_PLATFORM}"
-        --build-generator-toolset "${CMAKE_GENERATOR_TOOLSET}"
-        --build-project Simple
-        --test-command Simple)
-      list(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/${extraGeneratorTestName}")
-    endforeach()
-  endif()
-
   # test for correct sub-project generation
   # not implemented in Xcode or Ninja
   if(NOT CMAKE_GENERATOR MATCHES "Xcode|Ninja")
@@ -1014,6 +986,30 @@
     endif()
   endif()
 
+  # On Windows run the CPackInnoSetupGenerator test
+  if(WIN32 AND CMake_TEST_CPACK_INNOSETUP)
+    add_test(CPackInnoSetupGenerator ${CMAKE_CTEST_COMMAND}
+      -C \${CTEST_CONFIGURATION_TYPE}
+      --build-and-test
+      "${CMake_SOURCE_DIR}/Tests/CPackInnoSetupGenerator"
+      "${CMake_BINARY_DIR}/Tests/CPackInnoSetupGenerator"
+      ${build_generator_args}
+      --build-project CPackInnoSetupGenerator
+      --build-options
+      --test-command ${CMAKE_CMAKE_COMMAND}
+      "-DCPackInnoSetupGenerator_BINARY_DIR:PATH=${CMake_BINARY_DIR}/Tests/CPackInnoSetupGenerator"
+      "-Dconfig=\${CTEST_CONFIGURATION_TYPE}"
+      -P "${CMake_SOURCE_DIR}/Tests/CPackInnoSetupGenerator/RunCPackVerifyResult.cmake")
+
+    set_property(TEST CPackInnoSetupGenerator PROPERTY
+      ATTACHED_FILES_ON_FAIL
+      "${CMake_BINARY_DIR}/Tests/CPackInnoSetupGenerator/_CPack_Packages/win32/INNOSETUP/ISCCOutput.log")
+
+    set_property(TEST CPackInnoSetupGenerator PROPERTY
+      ATTACHED_FILES
+      "${CMake_BINARY_DIR}/Tests/CPackInnoSetupGenerator/_CPack_Packages/win32/INNOSETUP/ISScript.iss")
+  endif()
+
   # On Windows run the CPackNSISGenerator test
   # if the nsis is available
   if(WIN32 AND NSIS_MAKENSIS_EXECUTABLE)
@@ -1478,6 +1474,7 @@
       GnuTLS
       GSL
       GTK2
+      HDF5
       Iconv
       ICU
       ImageMagick
@@ -1512,6 +1509,7 @@
       SQLite3
       TIFF
       Vulkan
+      wxWidgets
       X11
       XalanC
       XercesC
@@ -3194,17 +3192,6 @@
     WORKING_DIRECTORY ${CMake_BINARY_DIR}/Tests/CTestTestTimeout)
 
   configure_file(
-    "${CMake_SOURCE_DIR}/Tests/CTestTestZeroTimeout/test.cmake.in"
-    "${CMake_BINARY_DIR}/Tests/CTestTestZeroTimeout/test.cmake"
-    @ONLY ESCAPE_QUOTES)
-  add_test(CTestTestZeroTimeout ${CMAKE_CTEST_COMMAND}
-    -S "${CMake_BINARY_DIR}/Tests/CTestTestZeroTimeout/test.cmake" -V
-    --output-log
-    "${CMake_BINARY_DIR}/Tests/CTestTestZeroTimeout/testOutput.log")
-  set_tests_properties(CTestTestZeroTimeout PROPERTIES
-    FAIL_REGULAR_EXPRESSION "\\*\\*\\*Timeout")
-
-  configure_file(
     "${CMake_SOURCE_DIR}/Tests/CTestTestDepends/test.cmake.in"
     "${CMake_BINARY_DIR}/Tests/CTestTestDepends/test.cmake"
     @ONLY ESCAPE_QUOTES)
@@ -3323,18 +3310,20 @@
     endif()
   endif()
 
-  if(CMake_TEST_EXTERNAL_CMAKE)
-    set(CMAKE_SKIP_BOOTSTRAP_TEST 1)
-  endif()
-  if("${CMAKE_GENERATOR}" MATCHES Xcode)
-    set(CMAKE_SKIP_BOOTSTRAP_TEST 1)
-  endif()
-  if(EXISTS "${CMake_BINARY_DIR}/CMakeLists.txt")
-    # If there is CMakeLists.txt in the binary tree, assume in-source build
-    set(CMAKE_SKIP_BOOTSTRAP_TEST 1)
+  if(NOT DEFINED CMake_TEST_BOOTSTRAP)
+    if(CMAKE_RUN_LONG_TESTS
+        AND NOT CMAKE_SKIP_BOOTSTRAP_TEST
+        AND NOT CMake_TEST_EXTERNAL_CMAKE
+        AND NOT CMAKE_GENERATOR MATCHES "Xcode"
+        AND NOT EXISTS "${CMake_BINARY_DIR}/CMakeLists.txt"
+        )
+      set(CMake_TEST_BOOTSTRAP 1)
+    else()
+      set(CMake_TEST_BOOTSTRAP 0)
+    endif()
   endif()
   set(bootstrap "")
-  if(CMAKE_RUN_LONG_TESTS AND NOT CMAKE_SKIP_BOOTSTRAP_TEST)
+  if(CMake_TEST_BOOTSTRAP)
     if(UNIX)
       set(bootstrap ${CMake_SOURCE_DIR}/bootstrap)
     elseif(MSYS)
@@ -3353,13 +3342,12 @@
       )
     list(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/BootstrapTest")
     # This test will use all processors.
-    set_tests_properties(BootstrapTest PROPERTIES RUN_SERIAL 1)
-
-    # provide more time for the bootstrap test
-    get_test_property(BootstrapTest TIMEOUT PREVIOUS_TIMEOUT)
-    if("${PREVIOUS_TIMEOUT}" MATCHES NOTFOUND)
-      set_tests_properties(BootstrapTest PROPERTIES TIMEOUT 5400)
+    set_property(TEST BootstrapTest PROPERTY RUN_SERIAL 1)
+    # This test may take a long time.
+    if(NOT DEFINED CMake_TEST_BOOTSTRAP_TIMEOUT)
+      set(CMake_TEST_BOOTSTRAP_TIMEOUT 5400)
     endif()
+    set_property(TEST BootstrapTest PROPERTY TIMEOUT "${CMake_TEST_BOOTSTRAP_TIMEOUT}")
   endif()
 
   if(CMAKE_Fortran_COMPILER)
diff --git a/Tests/CMakeOnly/CheckCXXCompilerFlag/CMakeLists.txt b/Tests/CMakeOnly/CheckCXXCompilerFlag/CMakeLists.txt
index c7e3105..e6ed559 100644
--- a/Tests/CMakeOnly/CheckCXXCompilerFlag/CMakeLists.txt
+++ b/Tests/CMakeOnly/CheckCXXCompilerFlag/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 if(POLICY CMP0129)
   cmake_policy(SET CMP0129 NEW)
 endif()
diff --git a/Tests/CMakeOnly/CheckCXXSymbolExists/CMakeLists.txt b/Tests/CMakeOnly/CheckCXXSymbolExists/CMakeLists.txt
index 2784e3b..24a0a86 100644
--- a/Tests/CMakeOnly/CheckCXXSymbolExists/CMakeLists.txt
+++ b/Tests/CMakeOnly/CheckCXXSymbolExists/CMakeLists.txt
@@ -9,7 +9,7 @@
 
 project(CheckCXXSymbolExists CXX)
 
-cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR)
+cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
 
 set(CMAKE_REQUIRED_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/../CheckSymbolExists")
 
diff --git a/Tests/CMakeOnly/CheckLanguage/CMakeLists.txt b/Tests/CMakeOnly/CheckLanguage/CMakeLists.txt
index 2e5d8d3..0c76158 100644
--- a/Tests/CMakeOnly/CheckLanguage/CMakeLists.txt
+++ b/Tests/CMakeOnly/CheckLanguage/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(CheckLanguage NONE)
 include(CheckLanguage)
 
diff --git a/Tests/CMakeOnly/CheckStructHasMember/CMakeLists.txt b/Tests/CMakeOnly/CheckStructHasMember/CMakeLists.txt
index 4cbccd3..6ecd194 100644
--- a/Tests/CMakeOnly/CheckStructHasMember/CMakeLists.txt
+++ b/Tests/CMakeOnly/CheckStructHasMember/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(CheckStructHasMember)
 
diff --git a/Tests/CMakeOnly/CheckSymbolExists/CMakeLists.txt b/Tests/CMakeOnly/CheckSymbolExists/CMakeLists.txt
index 3d65b7a..b6ed9e9 100644
--- a/Tests/CMakeOnly/CheckSymbolExists/CMakeLists.txt
+++ b/Tests/CMakeOnly/CheckSymbolExists/CMakeLists.txt
@@ -9,7 +9,7 @@
 
 project(CheckSymbolExists C)
 
-cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR)
+cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
 
 set(CMAKE_REQUIRED_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}")
 
diff --git a/Tests/CMakeOnly/CompilerIdOBJC/CMakeLists.txt b/Tests/CMakeOnly/CompilerIdOBJC/CMakeLists.txt
index 8f13787..18a1ff6 100644
--- a/Tests/CMakeOnly/CompilerIdOBJC/CMakeLists.txt
+++ b/Tests/CMakeOnly/CompilerIdOBJC/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(CompilerIdOBJC OBJC)
 
 foreach(v
diff --git a/Tests/CMakeOnly/CompilerIdOBJCXX/CMakeLists.txt b/Tests/CMakeOnly/CompilerIdOBJCXX/CMakeLists.txt
index 8f41db0..76c1e4b 100644
--- a/Tests/CMakeOnly/CompilerIdOBJCXX/CMakeLists.txt
+++ b/Tests/CMakeOnly/CompilerIdOBJCXX/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(CompilerIdOBJCXX OBJCXX)
 
 foreach(v
diff --git a/Tests/CMakeOnly/LinkInterfaceLoop/CMakeLists.txt b/Tests/CMakeOnly/LinkInterfaceLoop/CMakeLists.txt
index d66eb06..77dadcf 100644
--- a/Tests/CMakeOnly/LinkInterfaceLoop/CMakeLists.txt
+++ b/Tests/CMakeOnly/LinkInterfaceLoop/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(LinkInterfaceLoop C)
 
 # Add a shared library that incorrectly names itself as a
diff --git a/Tests/CMakeOnly/MajorVersionSelection/CMakeLists.txt b/Tests/CMakeOnly/MajorVersionSelection/CMakeLists.txt
index 9f30c7d..6646825 100644
--- a/Tests/CMakeOnly/MajorVersionSelection/CMakeLists.txt
+++ b/Tests/CMakeOnly/MajorVersionSelection/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 if (NOT MAJOR_TEST_MODULE OR NOT MAJOR_TEST_VERSION)
   message(FATAL_ERROR "test selection variables not set up")
diff --git a/Tests/CMakeOnly/TargetScope/CMakeLists.txt b/Tests/CMakeOnly/TargetScope/CMakeLists.txt
index faf2250..3bcbb00 100644
--- a/Tests/CMakeOnly/TargetScope/CMakeLists.txt
+++ b/Tests/CMakeOnly/TargetScope/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(TargetScope NONE)
 
 add_subdirectory(Sub)
diff --git a/Tests/CMakeOnly/find_library/CMakeLists.txt b/Tests/CMakeOnly/find_library/CMakeLists.txt
index b23d5e2..2d487e3 100644
--- a/Tests/CMakeOnly/find_library/CMakeLists.txt
+++ b/Tests/CMakeOnly/find_library/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(FindLibraryTest NONE)
 
 set(CMAKE_FIND_DEBUG_MODE 1)
diff --git a/Tests/CMakeOnly/find_path/CMakeLists.txt b/Tests/CMakeOnly/find_path/CMakeLists.txt
index bf4e350..7cc08ad 100644
--- a/Tests/CMakeOnly/find_path/CMakeLists.txt
+++ b/Tests/CMakeOnly/find_path/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(FindPathTest NONE)
 
 set(CMAKE_FIND_DEBUG_MODE 1)
diff --git a/Tests/CMakeTests/EndStuffTestScript.cmake b/Tests/CMakeTests/EndStuffTestScript.cmake
index e0d826d..bd89246 100644
--- a/Tests/CMakeTests/EndStuffTestScript.cmake
+++ b/Tests/CMakeTests/EndStuffTestScript.cmake
@@ -22,7 +22,7 @@
   do_end("endfunction()\n")
 
 elseif(testname STREQUAL bad_endif) # fail
-  do_end("cmake_minimum_required(VERSION 2.8.12)\nendif()\n")
+  do_end("cmake_minimum_required(VERSION 3.5)\nendif()\n")
 
 elseif(testname STREQUAL endif_low_min_version) # fail
   do_end("cmake_minimum_required(VERSION 1.2)\nendif()\n")
diff --git a/Tests/COnly/CMakeLists.txt b/Tests/COnly/CMakeLists.txt
index 1c24017..728ec5b 100644
--- a/Tests/COnly/CMakeLists.txt
+++ b/Tests/COnly/CMakeLists.txt
@@ -1,5 +1,5 @@
 # a simple C only test case
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project (COnly C)
 
 set(CMAKE_DEBUG_POSTFIX "_test_debug_postfix")
@@ -7,11 +7,5 @@
 add_library(testc2 SHARED libc2.c)
 add_executable (COnly conly.c foo.c foo.h)
 target_link_libraries(COnly testc1 testc2)
-if(MSVC_VERSION AND NOT CMAKE_C_COMPILER_ID STREQUAL Clang OR "x${CMAKE_C_COMPILER_FRONTEND_VARIANT}" STREQUAL "xMSVC")
-  set_target_properties(COnly PROPERTIES
-    LINK_FLAGS " /NODEFAULTLIB:\"libcdg.lib\" /NODEFAULTLIB:\"libcmtg.lib\" /NODEFAULTLIB:\"foomsvcrt.lib\" /NODEFAULTLIB:\"libbar.lib\" /NODEFAULTLIB:\"libfooba.lib\"")
-endif()
-string(ASCII 35 32 67 77 97 107 101 ASCII_STRING)
-message(STATUS "String: ${ASCII_STRING}")
 
 add_library(testCModule MODULE testCModule.c)
diff --git a/Tests/CPackComponents/CMakeLists.txt b/Tests/CPackComponents/CMakeLists.txt
index c1b348e..a886b3d 100644
--- a/Tests/CPackComponents/CMakeLists.txt
+++ b/Tests/CPackComponents/CMakeLists.txt
@@ -4,7 +4,7 @@
 # application (mylibapp). We create a binary installer that allows
 # users to select which pieces will be installed: the example
 # application, the library binaries, and/or the header file.
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(CPackComponents)
 
 # Create the mylib library
diff --git a/Tests/CPackInnoSetupGenerator/CMakeLists.txt b/Tests/CPackInnoSetupGenerator/CMakeLists.txt
new file mode 100644
index 0000000..bca0ad6
--- /dev/null
+++ b/Tests/CPackInnoSetupGenerator/CMakeLists.txt
@@ -0,0 +1,55 @@
+cmake_minimum_required(VERSION 3.13)
+
+project(CPackInnoSetupGenerator VERSION 42.0 HOMEPAGE_URL "https://www.example.com")
+
+add_executable(hello main.c)
+file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/empty)
+
+install(TARGETS hello DESTINATION / COMPONENT application)
+install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/empty DESTINATION / COMPONENT extras)
+install(FILES my_bitmap.bmp DESTINATION awesome COMPONENT extras)
+install(FILES my_file.txt DESTINATION / COMPONENT hidden_component)
+install(FILES my_file.txt DESTINATION / COMPONENT hidden_component2)
+
+set(CPACK_GENERATOR "INNOSETUP")
+
+set(CPACK_PACKAGE_NAME "Hello, World!") # Test constant escape (like {cm:...}, see code documentation)
+set(CPACK_PACKAGE_VENDOR "Sheldon Cooper")
+set(CPACK_PACKAGE_INSTALL_DIRECTORY "hello_world")
+set(CPACK_PACKAGE_INSTALL_REGISTRY_KEY "hello_world")
+set(CPACK_PACKAGE_FILE_NAME "hello_world_setup")
+set(CPACK_SYSTEM_NAME "win32")
+set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/my_bitmap.bmp")
+set(CPACK_VERBATIM_VARIABLES ON)
+set(CPACK_PACKAGE_EXECUTABLES "hello" "Hello, World!")
+set(CPACK_CREATE_DESKTOP_LINKS hello)
+
+set(CPACK_INNOSETUP_INSTALL_ROOT "{autopf}\\Sheldon Cooper")
+set(CPACK_INNOSETUP_PROGRAM_MENU_FOLDER ".")
+set(CPACK_INNOSETUP_IGNORE_LICENSE_PAGE ON)
+set(CPACK_INNOSETUP_IGNORE_README_PAGE OFF) # Test if only readme page is shown
+set(CPACK_INNOSETUP_SETUP_AppComments ON) # Test if CPACK_INNOSETUP_USE_CMAKE_BOOL_FORMAT works
+set(CPACK_INNOSETUP_CUSTOM_INSTALL_INSTRUCTIONS "extras/empty"
+    "Name: \"{userdocs}\\empty\"\; Check: ReturnTrue\; Components: accessories\\extras")
+set(CPACK_INNOSETUP_MENU_LINKS "https://www.example.com" "Web"
+    "my_file.txt" "Text")
+set(CPACK_INNOSETUP_RUN_EXECUTABLES hello)
+set(CPACK_INNOSETUP_CREATE_UNINSTALL_LINK ON)
+# Test if this macro is available in the code file below containing the check function
+set(CPACK_INNOSETUP_DEFINE_PascalMacro "end;")
+set(CPACK_INNOSETUP_CODE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/Code.pas")
+set(CPACK_INNOSETUP_EXECUTABLE "ISCC.exe")
+
+include(CPackComponent)
+
+cpack_add_install_type(basic DISPLAY_NAME "Basic installation")
+cpack_add_install_type(full DISPLAY_NAME "\"Large\" installation") # Test double quote syntax
+cpack_add_component_group(accessories DISPLAY_NAME "Accessories")
+
+cpack_add_component(application DISPLAY_NAME "Application" INSTALL_TYPES basic full REQUIRED)
+cpack_add_component(extras DISPLAY_NAME "Additional components" INSTALL_TYPES full GROUP accessories)
+cpack_add_component(hidden_component HIDDEN)
+cpack_add_component(hidden_component2 HIDDEN DISABLED)
+set(CPACK_INNOSETUP_extras_INSTALL_DIRECTORY "{userdocs}")
+
+include(CPack)
diff --git a/Tests/CPackInnoSetupGenerator/Code.pas b/Tests/CPackInnoSetupGenerator/Code.pas
new file mode 100644
index 0000000..d96d82f
--- /dev/null
+++ b/Tests/CPackInnoSetupGenerator/Code.pas
@@ -0,0 +1,4 @@
+function ReturnTrue(): Boolean;
+begin
+  Result := true;
+{#PascalMacro}
diff --git a/Tests/CPackInnoSetupGenerator/RunCPackVerifyResult.cmake b/Tests/CPackInnoSetupGenerator/RunCPackVerifyResult.cmake
new file mode 100644
index 0000000..72a26ee
--- /dev/null
+++ b/Tests/CPackInnoSetupGenerator/RunCPackVerifyResult.cmake
@@ -0,0 +1,136 @@
+message(STATUS "=============================================================")
+message(STATUS "CTEST_FULL_OUTPUT (Avoid ctest truncation of output)")
+message(STATUS "")
+
+if(NOT CPackInnoSetupGenerator_BINARY_DIR)
+  message(FATAL_ERROR "CPackInnoSetupGenerator_BINARY_DIR not set")
+endif()
+
+message(STATUS "CMAKE_COMMAND: ${CMAKE_COMMAND}")
+message(STATUS "CMAKE_CPACK_COMMAND: ${CMAKE_CPACK_COMMAND}")
+message(STATUS "CPackInnoSetupGenerator_BINARY_DIR: ${CPackInnoSetupGenerator_BINARY_DIR}")
+
+if(config)
+  set(_C_config -C ${config})
+endif()
+
+execute_process(COMMAND "${CMAKE_CPACK_COMMAND}"
+  ${_C_config}
+  RESULT_VARIABLE CPack_result
+  OUTPUT_VARIABLE CPack_output
+  ERROR_VARIABLE CPack_output
+  WORKING_DIRECTORY "${CPackInnoSetupGenerator_BINARY_DIR}")
+
+if(CPack_result)
+  message(FATAL_ERROR "CPack execution went wrong!, Output: ${CPack_output}")
+else ()
+  message(STATUS "Output: ${CPack_output}")
+endif()
+
+file(GLOB project_file "${CPackInnoSetupGenerator_BINARY_DIR}/_CPack_Packages/win32/INNOSETUP/ISScript.iss")
+file(GLOB installer_file "${CPackInnoSetupGenerator_BINARY_DIR}/_CPack_Packages/win32/INNOSETUP/hello_world_setup.exe")
+
+message(STATUS "Project file: '${project_file}'")
+message(STATUS "Installer file: '${installer_file}'")
+
+if(NOT project_file)
+  message(FATAL_ERROR "Project file does not exist")
+endif()
+
+if(NOT installer_file)
+  message(FATAL_ERROR "Installer file does not exist")
+endif()
+
+# Test if the correct registry key is set
+file(STRINGS "${project_file}" results REGEX "^AppId=hello_world$")
+if(results STREQUAL "")
+  message(FATAL_ERROR "CPACK_PACKAGE_INSTALL_REGISTRY_KEY doesn't match AppId")
+endif()
+
+# Test if only readme page is shown
+file(STRINGS "${project_file}" results REGEX "^LicenseFile=")
+file(STRINGS "${project_file}" results2 REGEX "^InfoBeforeFile=")
+if(NOT results STREQUAL "" OR results2 STREQUAL "")
+  message(FATAL_ERROR "Erroneous output with license and readme files")
+endif()
+
+# Test if classic style is used by default
+file(STRINGS "${project_file}" results REGEX "compiler:SetupClassicIcon\\.ico")
+file(STRINGS "${project_file}" results2 REGEX "compiler:WizClassicImage\\.bmp")
+if(results STREQUAL "" OR results2 STREQUAL "")
+  message(FATAL_ERROR "Images of classic style not used")
+endif()
+
+# Test if the top-level start menu folder is used
+file(STRINGS "${project_file}" results REGEX "{autoprograms}")
+file(STRINGS "${project_file}" results2 REGEX "{group}")
+if(results STREQUAL "" OR NOT results2 STREQUAL "")
+  message(FATAL_ERROR "Top-level start menu folder not used")
+endif()
+
+# Test CPACK_INNOSETUP_USE_CMAKE_BOOL_FORMAT
+file(STRINGS "${project_file}" results REGEX "^AppComments=yes$")
+if(results STREQUAL "")
+  message(FATAL_ERROR "CPACK_INNOSETUP_USE_CMAKE_BOOL_FORMAT doesn't convert booleans")
+endif()
+
+# Test the custom installation rule
+file(STRINGS "${project_file}" results REGEX "^Name: \"{userdocs}\\\\empty\"; Check: ReturnTrue; Components: accessories\\\\extras$")
+if(results STREQUAL "")
+  message(FATAL_ERROR "Custom installation rule not found or incomplete")
+endif()
+
+# Test if an uninstall shortcut has been created
+file(STRINGS "${project_file}" results REGEX "{uninstallexe}")
+if(results STREQUAL "")
+  message(FATAL_ERROR "No uninstall shortcut created")
+endif()
+
+# Test CPACK_INNOSETUP_<compName>_INSTALL_DIRECTORY
+file(STRINGS "${project_file}" results REGEX "{app}.*Components: accessories\\\\extras")
+if(NOT results STREQUAL "")
+  message(FATAL_ERROR "Component not in custom install directory")
+endif()
+
+# Test if component names are nested correctly
+file(STRINGS "${project_file}" results REGEX "Components:.* extras")
+if(NOT results STREQUAL "")
+  message(FATAL_ERROR "Component names must contain their parent groups according to the documentation")
+endif()
+
+# Test if custom installation type exists
+file(STRINGS "${project_file}" results REGEX "Flags: .*iscustom")
+if(results STREQUAL "")
+  message(FATAL_ERROR "Custom installation type doesn't exist")
+endif()
+
+# Test if hidden components are processed but not displayed
+file(STRINGS "${project_file}" results REGEX "Source:.+hidden_component\\\\my_file\\.txt")
+file(STRINGS "${project_file}" results2 REGEX "Name: \"hidden_component\"")
+if(results STREQUAL "" OR NOT results2 STREQUAL "")
+  message(FATAL_ERROR "Hidden component displayed or one of its files ignored")
+endif()
+
+# Test if disabled and hidden components are ignored at all
+file(STRINGS "${project_file}" results REGEX "Source:.+hidden_component2\\\\my_file\\.txt")
+if(NOT results STREQUAL "")
+  message(FATAL_ERROR "Disabled and hidden component not ignored")
+endif()
+
+# Test if required components ignore their installation types
+file(STRINGS "${project_file}" results REGEX "Types: (basic|full|custom|basic full|full basic|basic custom|full custom); Flags: fixed")
+if(NOT results STREQUAL "")
+  message(FATAL_ERROR "Required components don't ignore their installation types")
+endif()
+
+# Test constant escape (should contain Hello%2c World!)
+file(STRINGS "${project_file}" results REGEX "Hello%2c World!")
+if(results STREQUAL "")
+  message(FATAL_ERROR "The comma character isn't escaped to %2c")
+endif()
+
+# Test double quote syntax
+file(STRINGS "${project_file}" results REGEX "\"\"Large\"\"")
+if(results STREQUAL "")
+  message(FATAL_ERROR "The quote character isn't escaped correctly")
+endif()
diff --git a/Tests/CPackInnoSetupGenerator/main.c b/Tests/CPackInnoSetupGenerator/main.c
new file mode 100644
index 0000000..413899c
--- /dev/null
+++ b/Tests/CPackInnoSetupGenerator/main.c
@@ -0,0 +1,7 @@
+#include <stdio.h>
+
+int main()
+{
+  printf("Hello, World!\n");
+  return 42;
+}
diff --git a/Tests/CPackInnoSetupGenerator/my_bitmap.bmp b/Tests/CPackInnoSetupGenerator/my_bitmap.bmp
new file mode 100644
index 0000000..d0e562f
--- /dev/null
+++ b/Tests/CPackInnoSetupGenerator/my_bitmap.bmp
Binary files differ
diff --git a/Tests/CPackInnoSetupGenerator/my_file.txt b/Tests/CPackInnoSetupGenerator/my_file.txt
new file mode 100644
index 0000000..8ab686e
--- /dev/null
+++ b/Tests/CPackInnoSetupGenerator/my_file.txt
@@ -0,0 +1 @@
+Hello, World!
diff --git a/Tests/CPackTestAllGenerators/CMakeLists.txt b/Tests/CPackTestAllGenerators/CMakeLists.txt
index 95daabf..e7fed3b 100644
--- a/Tests/CPackTestAllGenerators/CMakeLists.txt
+++ b/Tests/CPackTestAllGenerators/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(CPackTestAllGenerators)
 add_subdirectory(../CTestTest/SmallAndFast SmallAndFast)
 install(FILES RunCPack.cmake DESTINATION .)
diff --git a/Tests/CPackWiXGenerator/CMakeLists.txt b/Tests/CPackWiXGenerator/CMakeLists.txt
index 2249d70..33fdc5e 100644
--- a/Tests/CPackWiXGenerator/CMakeLists.txt
+++ b/Tests/CPackWiXGenerator/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(CPackWiXGenerator)
 
diff --git a/Tests/CTestCoverageCollectGCOV/TestProject/CMakeLists.txt b/Tests/CTestCoverageCollectGCOV/TestProject/CMakeLists.txt
index ce6fac4..79e968a 100644
--- a/Tests/CTestCoverageCollectGCOV/TestProject/CMakeLists.txt
+++ b/Tests/CTestCoverageCollectGCOV/TestProject/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.2)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestProject CXX)
 
diff --git a/Tests/CTestCoverageCollectGCOV/test.cmake.in b/Tests/CTestCoverageCollectGCOV/test.cmake.in
index 7c7a3e5..aaf3070 100644
--- a/Tests/CTestCoverageCollectGCOV/test.cmake.in
+++ b/Tests/CTestCoverageCollectGCOV/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 set(CTEST_SOURCE_DIRECTORY "@CMake_SOURCE_DIR@/Tests/CTestCoverageCollectGCOV/TestProject")
 set(CTEST_BINARY_DIRECTORY "@CMake_BINARY_DIR@/Tests/CTestCoverageCollectGCOV/TestProject")
 set(CTEST_CMAKE_GENERATOR "@CMAKE_GENERATOR@")
diff --git a/Tests/CTestLimitDashJ/CMakeLists.txt b/Tests/CTestLimitDashJ/CMakeLists.txt
index d04b3ad..5bb7369 100644
--- a/Tests/CTestLimitDashJ/CMakeLists.txt
+++ b/Tests/CTestLimitDashJ/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(CTestLimitDashJ NONE)
 
 # This file demonstrates https://gitlab.kitware.com/cmake/cmake/-/issues/12904
diff --git a/Tests/CTestTest/SmallAndFast/CMakeLists.txt b/Tests/CTestTest/SmallAndFast/CMakeLists.txt
index 06cbafd..d5b3b61 100644
--- a/Tests/CTestTest/SmallAndFast/CMakeLists.txt
+++ b/Tests/CTestTest/SmallAndFast/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(SmallAndFast)
 
 include(CTest)
diff --git a/Tests/CTestTest2/test.cmake.in b/Tests/CTestTest2/test.cmake.in
index d5d4d2f..4f4f6cf 100644
--- a/Tests/CTestTest2/test.cmake.in
+++ b/Tests/CTestTest2/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 # Settings:
 set(CTEST_DASHBOARD_ROOT                "@CMake_BINARY_DIR@/Tests/CTestTest")
diff --git a/Tests/CTestTestBadExe/test.cmake.in b/Tests/CTestTestBadExe/test.cmake.in
index dd180f0..e46f71b 100644
--- a/Tests/CTestTestBadExe/test.cmake.in
+++ b/Tests/CTestTestBadExe/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 # Settings:
 set(CTEST_DASHBOARD_ROOT                "@CMake_BINARY_DIR@/Tests/CTestTest")
diff --git a/Tests/CTestTestBadGenerator/test.cmake.in b/Tests/CTestTestBadGenerator/test.cmake.in
index ae6d0b5..34003b4 100644
--- a/Tests/CTestTestBadGenerator/test.cmake.in
+++ b/Tests/CTestTestBadGenerator/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.5)
 
 # Settings:
 set(CTEST_DASHBOARD_ROOT                "@CMake_BINARY_DIR@/Tests/CTestTest")
diff --git a/Tests/CTestTestChecksum/test.cmake.in b/Tests/CTestTestChecksum/test.cmake.in
index 3bac0e0..916bbbb 100644
--- a/Tests/CTestTestChecksum/test.cmake.in
+++ b/Tests/CTestTestChecksum/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 # Settings:
 set(CTEST_DASHBOARD_ROOT                "@CMake_BINARY_DIR@/Tests/CTestTest")
diff --git a/Tests/CTestTestCostSerial/test.cmake.in b/Tests/CTestTestCostSerial/test.cmake.in
index 1c46d4c..0df9f37 100644
--- a/Tests/CTestTestCostSerial/test.cmake.in
+++ b/Tests/CTestTestCostSerial/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 # Settings:
 set(CTEST_DASHBOARD_ROOT                "@CMake_BINARY_DIR@/Tests/CTestTest")
diff --git a/Tests/CTestTestCrash/CMakeLists.txt b/Tests/CTestTestCrash/CMakeLists.txt
index 663d2e4..c7e5b91 100644
--- a/Tests/CTestTestCrash/CMakeLists.txt
+++ b/Tests/CTestTestCrash/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(CTestTestCrash)
 include(CTest)
 
diff --git a/Tests/CTestTestCrash/test.cmake.in b/Tests/CTestTestCrash/test.cmake.in
index 916d4e9..34c9f3e 100644
--- a/Tests/CTestTestCrash/test.cmake.in
+++ b/Tests/CTestTestCrash/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 # Settings:
 set(CTEST_DASHBOARD_ROOT                "@CMake_BINARY_DIR@/Tests/CTestTest")
diff --git a/Tests/CTestTestCycle/CMakeLists.txt b/Tests/CTestTestCycle/CMakeLists.txt
index 19f4dd7..4093111 100644
--- a/Tests/CTestTestCycle/CMakeLists.txt
+++ b/Tests/CTestTestCycle/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(CTestTestCycle)
 include(CTest)
 
diff --git a/Tests/CTestTestCycle/test.cmake.in b/Tests/CTestTestCycle/test.cmake.in
index 507d46b..78b0ebb 100644
--- a/Tests/CTestTestCycle/test.cmake.in
+++ b/Tests/CTestTestCycle/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 # Settings:
 set(CTEST_DASHBOARD_ROOT                "@CMake_BINARY_DIR@/Tests/CTestTest")
diff --git a/Tests/CTestTestDepends/CMakeLists.txt b/Tests/CTestTestDepends/CMakeLists.txt
index 462ad8c..5a011d0 100644
--- a/Tests/CTestTestDepends/CMakeLists.txt
+++ b/Tests/CTestTestDepends/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(CTestTestDepends)
 include(CTest)
 
diff --git a/Tests/CTestTestDepends/test.cmake.in b/Tests/CTestTestDepends/test.cmake.in
index 11bc92a..ea01297 100644
--- a/Tests/CTestTestDepends/test.cmake.in
+++ b/Tests/CTestTestDepends/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 # Settings:
 set(CTEST_DASHBOARD_ROOT                "@CMake_BINARY_DIR@/Tests/CTestTest")
diff --git a/Tests/CTestTestEmptyBinaryDirectory/test.cmake.in b/Tests/CTestTestEmptyBinaryDirectory/test.cmake.in
index 8eb808f..3aed1ab 100644
--- a/Tests/CTestTestEmptyBinaryDirectory/test.cmake.in
+++ b/Tests/CTestTestEmptyBinaryDirectory/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 set(CTEST_RUN_CURRENT_SCRIPT 0)
 
diff --git a/Tests/CTestTestFailure/CMakeLists.txt b/Tests/CTestTestFailure/CMakeLists.txt
index db14b3d..b6c1e7a 100644
--- a/Tests/CTestTestFailure/CMakeLists.txt
+++ b/Tests/CTestTestFailure/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(CTestTestFailure)
 include(CTest)
 
diff --git a/Tests/CTestTestFailure/testNoBuild.cmake.in b/Tests/CTestTestFailure/testNoBuild.cmake.in
index 47d254f..505916e 100644
--- a/Tests/CTestTestFailure/testNoBuild.cmake.in
+++ b/Tests/CTestTestFailure/testNoBuild.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 # Settings:
 set(CTEST_DASHBOARD_ROOT                "@CMake_BINARY_DIR@/Tests/CTestTest")
diff --git a/Tests/CTestTestFailure/testNoExe.cmake.in b/Tests/CTestTestFailure/testNoExe.cmake.in
index 8496c80..e3d7742 100644
--- a/Tests/CTestTestFailure/testNoExe.cmake.in
+++ b/Tests/CTestTestFailure/testNoExe.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 # Settings:
 set(CTEST_DASHBOARD_ROOT                "@CMake_BINARY_DIR@/Tests/CTestTest")
diff --git a/Tests/CTestTestFdSetSize/test.cmake.in b/Tests/CTestTestFdSetSize/test.cmake.in
index bfe4459..73b2cfa 100644
--- a/Tests/CTestTestFdSetSize/test.cmake.in
+++ b/Tests/CTestTestFdSetSize/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.10)
+cmake_minimum_required(VERSION 3.5)
 
 # Settings:
 set(CTEST_DASHBOARD_ROOT                "@CMake_BINARY_DIR@/Tests/CTestTest")
diff --git a/Tests/CTestTestLaunchers/launcher_compiler_test_project/CMakeLists.txt b/Tests/CTestTestLaunchers/launcher_compiler_test_project/CMakeLists.txt
index 7376a40..24da9ca 100644
--- a/Tests/CTestTestLaunchers/launcher_compiler_test_project/CMakeLists.txt
+++ b/Tests/CTestTestLaunchers/launcher_compiler_test_project/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(launcher_compiler_test_project)
 
diff --git a/Tests/CTestTestLaunchers/launcher_custom_command_test_project/CMakeLists.txt b/Tests/CTestTestLaunchers/launcher_custom_command_test_project/CMakeLists.txt
index b31f587..72764ca 100644
--- a/Tests/CTestTestLaunchers/launcher_custom_command_test_project/CMakeLists.txt
+++ b/Tests/CTestTestLaunchers/launcher_custom_command_test_project/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(launcher_custom_command_test_project)
 
diff --git a/Tests/CTestTestLaunchers/launcher_linker_test_project/CMakeLists.txt b/Tests/CTestTestLaunchers/launcher_linker_test_project/CMakeLists.txt
index 38980aa..7bf0362 100644
--- a/Tests/CTestTestLaunchers/launcher_linker_test_project/CMakeLists.txt
+++ b/Tests/CTestTestLaunchers/launcher_linker_test_project/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(launcher_linker_test_project)
 
diff --git a/Tests/CTestTestLaunchers/test.cmake.in b/Tests/CTestTestLaunchers/test.cmake.in
index 2db1ddd..c3edfd5 100644
--- a/Tests/CTestTestLaunchers/test.cmake.in
+++ b/Tests/CTestTestLaunchers/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 set(TEST_SUCCESS TRUE)
 
diff --git a/Tests/CTestTestMissingDependsExe/CMakeLists.txt b/Tests/CTestTestMissingDependsExe/CMakeLists.txt
index 9826da6..07df194 100644
--- a/Tests/CTestTestMissingDependsExe/CMakeLists.txt
+++ b/Tests/CTestTestMissingDependsExe/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(CTestTestMissingDependsExe)
 
diff --git a/Tests/CTestTestParallel/CMakeLists.txt b/Tests/CTestTestParallel/CMakeLists.txt
index 819fee4..7527202 100644
--- a/Tests/CTestTestParallel/CMakeLists.txt
+++ b/Tests/CTestTestParallel/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(CTestTestParallel)
 include(CTest)
 
diff --git a/Tests/CTestTestParallel/test.cmake.in b/Tests/CTestTestParallel/test.cmake.in
index 517db72..d60d16f 100644
--- a/Tests/CTestTestParallel/test.cmake.in
+++ b/Tests/CTestTestParallel/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 # Settings:
 set(CTEST_DASHBOARD_ROOT                "@CMake_BINARY_DIR@/Tests/CTestTest")
diff --git a/Tests/CTestTestResourceLock/CMakeLists.txt b/Tests/CTestTestResourceLock/CMakeLists.txt
index 4bc4366..683aba5 100644
--- a/Tests/CTestTestResourceLock/CMakeLists.txt
+++ b/Tests/CTestTestResourceLock/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(CTestTestResourceLock)
 include(CTest)
 
diff --git a/Tests/CTestTestResourceLock/test.cmake.in b/Tests/CTestTestResourceLock/test.cmake.in
index 826226d..dab26fc 100644
--- a/Tests/CTestTestResourceLock/test.cmake.in
+++ b/Tests/CTestTestResourceLock/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 # Settings:
 set(CTEST_DASHBOARD_ROOT                "@CMake_BINARY_DIR@/Tests/CTestTest")
diff --git a/Tests/CTestTestScheduler/CMakeLists.txt b/Tests/CTestTestScheduler/CMakeLists.txt
index a3f0f27..91d565d 100644
--- a/Tests/CTestTestScheduler/CMakeLists.txt
+++ b/Tests/CTestTestScheduler/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project (CTestTestScheduler)
 include (CTest)
 
diff --git a/Tests/CTestTestScheduler/test.cmake.in b/Tests/CTestTestScheduler/test.cmake.in
index 5dcfb63..3b03a7c 100644
--- a/Tests/CTestTestScheduler/test.cmake.in
+++ b/Tests/CTestTestScheduler/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 # Settings:
 set(CTEST_DASHBOARD_ROOT                "@CMake_BINARY_DIR@/Tests/CTestTest")
diff --git a/Tests/CTestTestSerialInDepends/CMakeLists.txt b/Tests/CTestTestSerialInDepends/CMakeLists.txt
index 90e50f9..03ad4b3 100644
--- a/Tests/CTestTestSerialInDepends/CMakeLists.txt
+++ b/Tests/CTestTestSerialInDepends/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(CTestTestSerialInDepends)
 
diff --git a/Tests/CTestTestSerialOrder/CMakeLists.txt b/Tests/CTestTestSerialOrder/CMakeLists.txt
index 69c11fc..d46d80e 100644
--- a/Tests/CTestTestSerialOrder/CMakeLists.txt
+++ b/Tests/CTestTestSerialOrder/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(CTestTestSerialOrder)
 
diff --git a/Tests/CTestTestSkipReturnCode/CMakeLists.txt b/Tests/CTestTestSkipReturnCode/CMakeLists.txt
index 26c4178..1eeeec6 100644
--- a/Tests/CTestTestSkipReturnCode/CMakeLists.txt
+++ b/Tests/CTestTestSkipReturnCode/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(CTestTestSkipReturnCode)
 include(CTest)
 
diff --git a/Tests/CTestTestSkipReturnCode/test.cmake.in b/Tests/CTestTestSkipReturnCode/test.cmake.in
index 2988d2f..b45e4a6 100644
--- a/Tests/CTestTestSkipReturnCode/test.cmake.in
+++ b/Tests/CTestTestSkipReturnCode/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 # Settings:
 set(CTEST_DASHBOARD_ROOT                "@CMake_BINARY_DIR@/Tests/CTestTest")
diff --git a/Tests/CTestTestStopTime/CMakeLists.txt b/Tests/CTestTestStopTime/CMakeLists.txt
index 08116e2..4f6e795 100644
--- a/Tests/CTestTestStopTime/CMakeLists.txt
+++ b/Tests/CTestTestStopTime/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(CTestTestStopTime)
 include(CTest)
 
diff --git a/Tests/CTestTestStopTime/GetDate.cmake b/Tests/CTestTestStopTime/GetDate.cmake
index 64a4fb9..f8e40fc 100644
--- a/Tests/CTestTestStopTime/GetDate.cmake
+++ b/Tests/CTestTestStopTime/GetDate.cmake
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.11)
+cmake_minimum_required(VERSION 3.5)
 
 macro(GET_DATE)
   #
diff --git a/Tests/CTestTestStopTime/test.cmake.in b/Tests/CTestTestStopTime/test.cmake.in
index 3797d40..2d69f1d 100644
--- a/Tests/CTestTestStopTime/test.cmake.in
+++ b/Tests/CTestTestStopTime/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 # Settings:
 set(CTEST_DASHBOARD_ROOT                "@CMake_BINARY_DIR@/Tests/CTestTest")
diff --git a/Tests/CTestTestSubdir/CMakeLists.txt b/Tests/CTestTestSubdir/CMakeLists.txt
index 87c4604..e6f3209 100644
--- a/Tests/CTestTestSubdir/CMakeLists.txt
+++ b/Tests/CTestTestSubdir/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(CTestTestSubdir)
 include(CTest)
 
diff --git a/Tests/CTestTestSubdir/test.cmake.in b/Tests/CTestTestSubdir/test.cmake.in
index 3b1fb5f..8b8d85e 100644
--- a/Tests/CTestTestSubdir/test.cmake.in
+++ b/Tests/CTestTestSubdir/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 # Settings:
 set(CTEST_DASHBOARD_ROOT                "@CMake_BINARY_DIR@/Tests/CTestTest")
diff --git a/Tests/CTestTestTimeout/test.cmake.in b/Tests/CTestTestTimeout/test.cmake.in
index ce9c497..9d9e430 100644
--- a/Tests/CTestTestTimeout/test.cmake.in
+++ b/Tests/CTestTestTimeout/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 # Settings:
 set(CTEST_DASHBOARD_ROOT                "@CMake_BINARY_DIR@/Tests/CTestTest")
diff --git a/Tests/CTestTestUpload/CMakeLists.txt b/Tests/CTestTestUpload/CMakeLists.txt
index 5e02b2f..5bf428d 100644
--- a/Tests/CTestTestUpload/CMakeLists.txt
+++ b/Tests/CTestTestUpload/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(CTestTestUpload)
 
 add_executable (Sleep sleep.c)
diff --git a/Tests/CTestTestUpload/test.cmake.in b/Tests/CTestTestUpload/test.cmake.in
index 74fd1ec..db428e9 100644
--- a/Tests/CTestTestUpload/test.cmake.in
+++ b/Tests/CTestTestUpload/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 # Settings:
 set(CTEST_DASHBOARD_ROOT                "@CMake_BINARY_DIR@/Tests/CTestTest")
diff --git a/Tests/CTestTestVerboseOutput/CMakeLists.txt b/Tests/CTestTestVerboseOutput/CMakeLists.txt
index 3792385..d44e9fc 100644
--- a/Tests/CTestTestVerboseOutput/CMakeLists.txt
+++ b/Tests/CTestTestVerboseOutput/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(CTestTestVerboseOutput)
 include(CTest)
 
diff --git a/Tests/CTestTestVerboseOutput/test.cmake.in b/Tests/CTestTestVerboseOutput/test.cmake.in
index 9c9a4dc..b47383a 100644
--- a/Tests/CTestTestVerboseOutput/test.cmake.in
+++ b/Tests/CTestTestVerboseOutput/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 # Settings:
 set(CTEST_DASHBOARD_ROOT                "@CMake_BINARY_DIR@/Tests/CTestTest")
diff --git a/Tests/CTestTestZeroTimeout/CMakeLists.txt b/Tests/CTestTestZeroTimeout/CMakeLists.txt
deleted file mode 100644
index 2d404c8..0000000
--- a/Tests/CTestTestZeroTimeout/CMakeLists.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-cmake_minimum_required (VERSION 2.8.12)
-project (CTestTestZeroTimeout)
-include (CTest)
-
-add_executable (Sleep sleep.c)
-
-add_test (TestExplicitZeroTimeout Sleep)
-set_tests_properties(TestExplicitZeroTimeout PROPERTIES TIMEOUT 0)
diff --git a/Tests/CTestTestZeroTimeout/CTestConfig.cmake b/Tests/CTestTestZeroTimeout/CTestConfig.cmake
deleted file mode 100644
index bd265f9..0000000
--- a/Tests/CTestTestZeroTimeout/CTestConfig.cmake
+++ /dev/null
@@ -1,4 +0,0 @@
-set(CTEST_NIGHTLY_START_TIME "21:00:00 EDT")
-set(CTEST_DROP_METHOD "http")
-set(CTEST_DROP_SITE "open.cdash.org")
-set(CTEST_DROP_LOCATION "/submit.php?project=PublicDashboard")
diff --git a/Tests/CTestTestZeroTimeout/sleep.c b/Tests/CTestTestZeroTimeout/sleep.c
deleted file mode 100644
index 5d0b89b..0000000
--- a/Tests/CTestTestZeroTimeout/sleep.c
+++ /dev/null
@@ -1,16 +0,0 @@
-#if defined(_WIN32)
-#  include <windows.h>
-#else
-#  include <unistd.h>
-#endif
-
-/* sleeps for 5 seconds */
-int main(int argc, char** argv)
-{
-#if defined(_WIN32)
-  Sleep(5000);
-#else
-  sleep(5);
-#endif
-  return 0;
-}
diff --git a/Tests/CTestTestZeroTimeout/test.cmake.in b/Tests/CTestTestZeroTimeout/test.cmake.in
deleted file mode 100644
index 50dbba0..0000000
--- a/Tests/CTestTestZeroTimeout/test.cmake.in
+++ /dev/null
@@ -1,22 +0,0 @@
-cmake_minimum_required(VERSION 2.8.12)
-
-# Settings:
-set(CTEST_DASHBOARD_ROOT                "@CMake_BINARY_DIR@/Tests/CTestTest")
-set(CTEST_SITE                          "@SITE@")
-set(CTEST_BUILD_NAME                    "CTestTest-@BUILDNAME@-ZeroTimeout")
-
-set(CTEST_SOURCE_DIRECTORY              "@CMake_SOURCE_DIR@/Tests/CTestTestZeroTimeout")
-set(CTEST_BINARY_DIRECTORY              "@CMake_BINARY_DIR@/Tests/CTestTestZeroTimeout")
-set(CTEST_CVS_COMMAND                   "@CVSCOMMAND@")
-set(CTEST_CMAKE_GENERATOR               "@CMAKE_GENERATOR@")
-set(CTEST_CMAKE_GENERATOR_PLATFORM      "@CMAKE_GENERATOR_PLATFORM@")
-set(CTEST_CMAKE_GENERATOR_TOOLSET       "@CMAKE_GENERATOR_TOOLSET@")
-set(CTEST_BUILD_CONFIGURATION           "$ENV{CMAKE_CONFIG_TYPE}")
-set(CTEST_COVERAGE_COMMAND              "@COVERAGE_COMMAND@")
-set(CTEST_NOTES_FILES                   "${CTEST_SCRIPT_DIRECTORY}/${CTEST_SCRIPT_NAME}")
-set(CTEST_TEST_TIMEOUT                  2)
-
-CTEST_START(Experimental)
-CTEST_CONFIGURE(BUILD "${CTEST_BINARY_DIRECTORY}" RETURN_VALUE res)
-CTEST_BUILD(BUILD "${CTEST_BINARY_DIRECTORY}" RETURN_VALUE res)
-CTEST_TEST(BUILD "${CTEST_BINARY_DIRECTORY}" RETURN_VALUE res)
diff --git a/Tests/CheckCompilerRelatedVariables/CMakeLists.txt b/Tests/CheckCompilerRelatedVariables/CMakeLists.txt
index 69fa4b6..9259309 100644
--- a/Tests/CheckCompilerRelatedVariables/CMakeLists.txt
+++ b/Tests/CheckCompilerRelatedVariables/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(CheckCompilerRelatedVariables)
 
 
diff --git a/Tests/CheckFortran.cmake b/Tests/CheckFortran.cmake
index 1e943a1..850406b 100644
--- a/Tests/CheckFortran.cmake
+++ b/Tests/CheckFortran.cmake
@@ -7,7 +7,7 @@
   message(STATUS ${_desc})
   file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CheckFortran)
   file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CheckFortran/CMakeLists.txt"
-    "cmake_minimum_required(VERSION 2.8.12)
+    "cmake_minimum_required(VERSION 3.5)
 project(CheckFortran Fortran)
 file(WRITE \"\${CMAKE_CURRENT_BINARY_DIR}/result.cmake\"
   \"set(CMAKE_Fortran_COMPILER \\\"\${CMAKE_Fortran_COMPILER}\\\")\\n\"
diff --git a/Tests/CompileDefinitions/CMakeLists.txt b/Tests/CompileDefinitions/CMakeLists.txt
index 8347d5a..cd0a0b0 100644
--- a/Tests/CompileDefinitions/CMakeLists.txt
+++ b/Tests/CompileDefinitions/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(CompileDefinitions)
 
 # Use compile flags to tell executables which config is built
diff --git a/Tests/CompileFeatures/default_dialect.c b/Tests/CompileFeatures/default_dialect.c
index b990e53..c696c83 100644
--- a/Tests/CompileFeatures/default_dialect.c
+++ b/Tests/CompileFeatures/default_dialect.c
@@ -20,7 +20,8 @@
 #    error Buildsystem error
 #  endif
 #  if defined(__STDC_VERSION__) &&                                            \
-    !(defined(__SUNPRO_C) && __STDC_VERSION__ == 199409L)
+    !(__STDC_VERSION__ == 199409L &&                                          \
+      (defined(__INTEL_COMPILER) || defined(__SUNPRO_C)))
 #    error Unexpected __STDC_VERSION__ definition
 #  endif
 #endif
diff --git a/Tests/Contracts/Trilinos/CMakeLists.txt b/Tests/Contracts/Trilinos/CMakeLists.txt
index 6cc2d09..e23a643 100644
--- a/Tests/Contracts/Trilinos/CMakeLists.txt
+++ b/Tests/Contracts/Trilinos/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(Trilinos)
 
 include(ExternalProject)
diff --git a/Tests/Contracts/VTK/CMakeLists.txt b/Tests/Contracts/VTK/CMakeLists.txt
index 0d36323..aee4dc6 100644
--- a/Tests/Contracts/VTK/CMakeLists.txt
+++ b/Tests/Contracts/VTK/CMakeLists.txt
@@ -1,6 +1,6 @@
 # The VTK external project for CMake
 # ---------------------------------------------------------------------------
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(VTK)
 include(ExternalProject)
 
diff --git a/Tests/Cuda/Complex/CMakeLists.txt b/Tests/Cuda/Complex/CMakeLists.txt
index 63defdf..e252304 100644
--- a/Tests/Cuda/Complex/CMakeLists.txt
+++ b/Tests/Cuda/Complex/CMakeLists.txt
@@ -53,5 +53,6 @@
 if(UNIX)
   # Help the shared cuda runtime find libcudart as it is not located
   # in a default system searched location
-  set_property(TARGET CudaComplexMixedLib PROPERTY BUILD_RPATH ${CMAKE_CUDA_IMPLICIT_LINK_DIRECTORIES})
+  find_package(CUDAToolkit REQUIRED)
+  set_property(TARGET CudaComplexMixedLib PROPERTY BUILD_RPATH "${CUDAToolkit_LIBRARY_DIR}")
 endif()
diff --git a/Tests/CudaOnly/CMakeLists.txt b/Tests/CudaOnly/CMakeLists.txt
index db08076..aa25c4c 100644
--- a/Tests/CudaOnly/CMakeLists.txt
+++ b/Tests/CudaOnly/CMakeLists.txt
@@ -27,6 +27,9 @@
 
   # Only NVCC defines __CUDACC_DEBUG__ when compiling in debug mode.
   add_cuda_test_macro(CudaOnly.GPUDebugFlag CudaOnlyGPUDebugFlag)
+  add_cuda_test_macro(CudaOnly.CUBIN CudaOnlyCUBIN)
+  add_cuda_test_macro(CudaOnly.Fatbin CudaOnlyFatbin)
+  add_cuda_test_macro(CudaOnly.OptixIR CudaOnlyOptixIR)
 endif()
 
 add_cuda_test_macro(CudaOnly.DeviceLTO CudaOnlyDeviceLTO)
diff --git a/Tests/CudaOnly/CUBIN/CMakeLists.txt b/Tests/CudaOnly/CUBIN/CMakeLists.txt
new file mode 100644
index 0000000..81787e4
--- /dev/null
+++ b/Tests/CudaOnly/CUBIN/CMakeLists.txt
@@ -0,0 +1,29 @@
+cmake_minimum_required(VERSION 3.18)
+unset(ENV{CMAKE_CUDA_ARCHITECTURES_NATIVE_CLAMP}) # CUBIN needs true native arch
+project(CudaCUBIN LANGUAGES CUDA)
+
+set(CMAKE_CUDA_ARCHITECTURES all-major)
+
+# CUBIN needs the true native arch to be supported by the CUDA toolkit.
+set(unavailable_native_archs "${CMAKE_CUDA_ARCHITECTURES_NATIVE}")
+list(REMOVE_ITEM unavailable_native_archs ${CMAKE_CUDA_ARCHITECTURES_ALL})
+if(unavailable_native_archs)
+  add_executable(CudaOnlyCUBIN main_no_native_archs.cu)
+  return()
+endif()
+
+add_library(CudaCUBIN OBJECT kernelA.cu kernelB.cu kernelC.cu)
+set_property(TARGET CudaCUBIN PROPERTY CUDA_CUBIN_COMPILATION ON)
+set_property(TARGET CudaCUBIN PROPERTY CUDA_ARCHITECTURES native)
+
+add_executable(CudaOnlyCUBIN main.cu)
+target_compile_features(CudaOnlyCUBIN PRIVATE cuda_std_11)
+target_compile_definitions(CudaOnlyCUBIN PRIVATE "CUBIN_FILE_PATHS=\"$<JOIN:$<TARGET_OBJECTS:CudaCUBIN>,~_~>\"")
+
+find_package(CUDAToolkit REQUIRED)
+target_link_libraries(CudaOnlyCUBIN PRIVATE CUDA::cuda_driver)
+
+if(APPLE)
+  # Help the static cuda runtime find the driver (libcuda.dyllib) at runtime.
+  set_property(TARGET CudaOnlyCUBIN PROPERTY BUILD_RPATH ${CMAKE_CUDA_IMPLICIT_LINK_DIRECTORIES})
+endif()
diff --git a/Tests/CudaOnly/CUBIN/kernelA.cu b/Tests/CudaOnly/CUBIN/kernelA.cu
new file mode 100644
index 0000000..fbe0d26
--- /dev/null
+++ b/Tests/CudaOnly/CUBIN/kernelA.cu
@@ -0,0 +1,7 @@
+
+__global__ void kernelA(float* r, float* x, float* y, float* z, int size)
+{
+  for (int i = threadIdx.x; i < size; i += blockDim.x) {
+    r[i] = x[i] * y[i] + z[i];
+  }
+}
diff --git a/Tests/CudaOnly/CUBIN/kernelB.cu b/Tests/CudaOnly/CUBIN/kernelB.cu
new file mode 100644
index 0000000..7478253
--- /dev/null
+++ b/Tests/CudaOnly/CUBIN/kernelB.cu
@@ -0,0 +1,7 @@
+
+__global__ void kernelB(float* r, float* x, float* y, float* z, int size)
+{
+  for (int i = threadIdx.x; i < size; i += blockDim.x) {
+    r[i] = x[i] * y[i] + z[i];
+  }
+}
diff --git a/Tests/CudaOnly/CUBIN/kernelC.cu b/Tests/CudaOnly/CUBIN/kernelC.cu
new file mode 100644
index 0000000..5f8a0ce
--- /dev/null
+++ b/Tests/CudaOnly/CUBIN/kernelC.cu
@@ -0,0 +1,7 @@
+
+__global__ void kernelC(float* r, float* x, float* y, float* z, int size)
+{
+  for (int i = threadIdx.x; i < size; i += blockDim.x) {
+    r[i] = x[i] * y[i] + z[i];
+  }
+}
diff --git a/Tests/CudaOnly/CUBIN/main.cu b/Tests/CudaOnly/CUBIN/main.cu
new file mode 100644
index 0000000..581970a
--- /dev/null
+++ b/Tests/CudaOnly/CUBIN/main.cu
@@ -0,0 +1,58 @@
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <cuda.h>
+
+#define GENERATED_HEADER(x) GENERATED_HEADER1(x)
+#define GENERATED_HEADER1(x) <x>
+
+static std::string input_paths = { CUBIN_FILE_PATHS };
+
+int main()
+{
+  const std::string delimiter = "~_~";
+  input_paths += delimiter;
+
+  size_t end = 0;
+  size_t previous_end = 0;
+  std::vector<std::string> actual_paths;
+  while ((end = input_paths.find(delimiter, previous_end)) !=
+         std::string::npos) {
+    actual_paths.emplace_back(
+      input_paths.substr(previous_end, end - previous_end));
+    previous_end = end + 3;
+  }
+
+  cuInit(0);
+  int count = 0;
+  cuDeviceGetCount(&count);
+  if (count == 0) {
+    std::cerr << "No CUDA devices found\n";
+    return 1;
+  }
+
+  CUdevice device;
+  cuDeviceGet(&device, 0);
+
+  CUcontext context;
+  cuCtxCreate(&context, 0, device);
+
+  CUmodule module;
+  for (auto p : actual_paths) {
+    if (p.find(".cubin") == std::string::npos) {
+      std::cout << p << " Doesn't have the .cubin suffix" << p << std::endl;
+      return 1;
+    }
+    std::cout << "trying to load cubin: " << p << std::endl;
+    CUresult result = cuModuleLoad(&module, p.c_str());
+    std::cout << "module pointer: " << module << '\n';
+    if (result != CUDA_SUCCESS || module == nullptr) {
+      std::cerr << "Failed to load the embedded cubin with error: "
+                << static_cast<unsigned int>(result) << '\n';
+      return 1;
+    }
+  }
+
+  return 0;
+}
diff --git a/Tests/CudaOnly/CUBIN/main_no_native_archs.cu b/Tests/CudaOnly/CUBIN/main_no_native_archs.cu
new file mode 100644
index 0000000..f8b643a
--- /dev/null
+++ b/Tests/CudaOnly/CUBIN/main_no_native_archs.cu
@@ -0,0 +1,4 @@
+int main()
+{
+  return 0;
+}
diff --git a/Tests/CudaOnly/Fatbin/CMakeLists.txt b/Tests/CudaOnly/Fatbin/CMakeLists.txt
new file mode 100644
index 0000000..db0dc22
--- /dev/null
+++ b/Tests/CudaOnly/Fatbin/CMakeLists.txt
@@ -0,0 +1,25 @@
+cmake_minimum_required(VERSION 3.18)
+project(CudaFATBIN LANGUAGES CUDA)
+
+
+set(CMAKE_CUDA_ARCHITECTURES all-major)
+
+add_library(CudaFATBIN OBJECT
+${CMAKE_CURRENT_SOURCE_DIR}/../CUBIN/kernelA.cu
+${CMAKE_CURRENT_SOURCE_DIR}/../CUBIN/kernelB.cu
+${CMAKE_CURRENT_SOURCE_DIR}/../CUBIN/kernelC.cu)
+
+set_property(TARGET CudaFATBIN PROPERTY CUDA_FATBIN_COMPILATION ON)
+
+# Will use `cuModuleLoadFatBinary` to load the fatbinaries
+add_executable(CudaOnlyFatbin main.cu)
+target_compile_features(CudaOnlyFatbin PRIVATE cuda_std_11)
+target_compile_definitions(CudaOnlyFatbin PRIVATE "FATBIN_FILE_PATHS=\"$<JOIN:$<TARGET_OBJECTS:CudaFATBIN>,~_~>\"")
+
+find_package(CUDAToolkit REQUIRED)
+target_link_libraries(CudaOnlyFatbin PRIVATE CUDA::cuda_driver)
+
+if(APPLE)
+  # Help the static cuda runtime find the driver (libcuda.dyllib) at runtime.
+  set_property(TARGET CudaOnlyFatbin PROPERTY BUILD_RPATH ${CMAKE_CUDA_IMPLICIT_LINK_DIRECTORIES})
+endif()
diff --git a/Tests/CudaOnly/Fatbin/main.cu b/Tests/CudaOnly/Fatbin/main.cu
new file mode 100644
index 0000000..89af0e3
--- /dev/null
+++ b/Tests/CudaOnly/Fatbin/main.cu
@@ -0,0 +1,58 @@
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <cuda.h>
+
+#define GENERATED_HEADER(x) GENERATED_HEADER1(x)
+#define GENERATED_HEADER1(x) <x>
+
+static std::string input_paths = { FATBIN_FILE_PATHS };
+
+int main()
+{
+  const std::string delimiter = "~_~";
+  input_paths += delimiter;
+
+  size_t end = 0;
+  size_t previous_end = 0;
+  std::vector<std::string> actual_paths;
+  while ((end = input_paths.find(delimiter, previous_end)) !=
+         std::string::npos) {
+    actual_paths.emplace_back(
+      input_paths.substr(previous_end, end - previous_end));
+    previous_end = end + 3;
+  }
+
+  cuInit(0);
+  int count = 0;
+  cuDeviceGetCount(&count);
+  if (count == 0) {
+    std::cerr << "No CUDA devices found\n";
+    return 1;
+  }
+
+  CUdevice device;
+  cuDeviceGet(&device, 0);
+
+  CUcontext context;
+  cuCtxCreate(&context, 0, device);
+
+  CUmodule module;
+  for (auto p : actual_paths) {
+    if (p.find(".fatbin") == std::string::npos) {
+      std::cout << p << " Doesn't have the .fatbin suffix" << p << std::endl;
+      return 1;
+    }
+    std::cout << "trying to load fatbin: " << p << std::endl;
+    CUresult result = cuModuleLoad(&module, p.c_str());
+    std::cout << "module pointer: " << module << '\n';
+    if (result != CUDA_SUCCESS || module == nullptr) {
+      std::cerr << "Failed to load the embedded fatbin with error: "
+                << static_cast<unsigned int>(result) << '\n';
+      return 1;
+    }
+  }
+
+  return 0;
+}
diff --git a/Tests/CudaOnly/OptixIR/CMakeLists.txt b/Tests/CudaOnly/OptixIR/CMakeLists.txt
new file mode 100644
index 0000000..afeabda
--- /dev/null
+++ b/Tests/CudaOnly/OptixIR/CMakeLists.txt
@@ -0,0 +1,33 @@
+cmake_minimum_required(VERSION 3.18)
+project(CudaOptix LANGUAGES CUDA)
+
+
+set(CMAKE_CUDA_ARCHITECTURES all-major)
+
+add_library(CudaOptix OBJECT
+  ${CMAKE_CURRENT_SOURCE_DIR}/../CUBIN/kernelA.cu
+  ${CMAKE_CURRENT_SOURCE_DIR}/../CUBIN/kernelB.cu
+  ${CMAKE_CURRENT_SOURCE_DIR}/../CUBIN/kernelC.cu)
+
+if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL "11.7.0")
+  set_property(TARGET CudaOptix PROPERTY CUDA_OPTIX_COMPILATION ON)
+endif()
+
+set_property(TARGET CudaOptix PROPERTY CUDA_ARCHITECTURES native)
+
+add_executable(CudaOnlyOptixIR main.cu)
+target_compile_features(CudaOnlyOptixIR PRIVATE cuda_std_11)
+
+if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL "11.7.0")
+  target_compile_definitions(CudaOnlyOptixIR PRIVATE "OPTIX_FILE_PATHS=\"$<JOIN:$<TARGET_OBJECTS:CudaOptix>,~_~>\"")
+else()
+  target_compile_definitions(CudaOnlyOptixIR PRIVATE "OPTIX_FILE_PATHS=\"NO_OPTIX_SUPPORT\"")
+endif()
+
+find_package(CUDAToolkit REQUIRED)
+target_link_libraries(CudaOnlyOptixIR PRIVATE CUDA::cuda_driver)
+
+if(APPLE)
+  # Help the static cuda runtime find the driver (libcuda.dyllib) at runtime.
+  set_property(TARGET CudaOnlyOptixIR PROPERTY BUILD_RPATH ${CMAKE_CUDA_IMPLICIT_LINK_DIRECTORIES})
+endif()
diff --git a/Tests/CudaOnly/OptixIR/main.cu b/Tests/CudaOnly/OptixIR/main.cu
new file mode 100644
index 0000000..c79829b
--- /dev/null
+++ b/Tests/CudaOnly/OptixIR/main.cu
@@ -0,0 +1,53 @@
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <cuda.h>
+
+#define GENERATED_HEADER(x) GENERATED_HEADER1(x)
+#define GENERATED_HEADER1(x) <x>
+
+static std::string input_paths = { OPTIX_FILE_PATHS };
+
+int main()
+{
+  if (input_paths == "NO_OPTIX_SUPPORT") {
+    return 0;
+  }
+
+  const std::string delimiter = "~_~";
+  input_paths += delimiter;
+
+  size_t end = 0;
+  size_t previous_end = 0;
+  std::vector<std::string> actual_paths;
+  while ((end = input_paths.find(delimiter, previous_end)) !=
+         std::string::npos) {
+    actual_paths.emplace_back(
+      input_paths.substr(previous_end, end - previous_end));
+    previous_end = end + 3;
+  }
+
+  if (actual_paths.empty()) {
+    std::cerr << "Failed to parse OPTIX_FILE_PATHS" << std::endl;
+    return 1;
+  }
+
+  const std::uint32_t optix_magic_value = 0x7f4e43ed;
+  for (auto p : actual_paths) {
+    if (p.find(".optixir") == std::string::npos) {
+      std::cout << p << " Doesn't have the .optixir suffix" << p << std::endl;
+      return 1;
+    }
+    std::ifstream input(p, std::ios::binary);
+    std::uint32_t value;
+    input.read(reinterpret_cast<char*>(&value), sizeof(value));
+    if (value != optix_magic_value) {
+      std::cerr << p << " Doesn't look like an optix-ir file" << std::endl;
+      return 1;
+    }
+  }
+
+  return 0;
+}
diff --git a/Tests/CudaOnly/RuntimeControls/verify_runtime.cmake b/Tests/CudaOnly/RuntimeControls/verify_runtime.cmake
index b313dac..27fbe45 100644
--- a/Tests/CudaOnly/RuntimeControls/verify_runtime.cmake
+++ b/Tests/CudaOnly/RuntimeControls/verify_runtime.cmake
@@ -7,7 +7,7 @@
   EXECUTABLES ${EXEC_PATH}
   )
 
-list(FILTER resolved_libs INCLUDE REGEX ".*cudart.*")
+list(FILTER resolved_libs INCLUDE REGEX ".*[Cc][Uu][Dd][Aa][Rr][Tt].*")
 list(LENGTH resolved_libs has_cudart)
 
 if(has_cudart EQUAL 0)
diff --git a/Tests/CudaOnly/SharedRuntimePlusToolkit/CMakeLists.txt b/Tests/CudaOnly/SharedRuntimePlusToolkit/CMakeLists.txt
index 0b01085..7dc919f 100644
--- a/Tests/CudaOnly/SharedRuntimePlusToolkit/CMakeLists.txt
+++ b/Tests/CudaOnly/SharedRuntimePlusToolkit/CMakeLists.txt
@@ -40,5 +40,6 @@
 if(UNIX)
   # Help the shared cuda runtime find libcudart as it is not located
   # in a default system searched location
-  set_property(TARGET CudaOnlySharedRuntimePlusToolkit PROPERTY BUILD_RPATH ${CMAKE_CUDA_IMPLICIT_LINK_DIRECTORIES})
+  find_package(CUDAToolkit REQUIRED)
+  set_property(TARGET CudaOnlySharedRuntimePlusToolkit PROPERTY BUILD_RPATH "${CUDAToolkit_LIBRARY_DIR}")
 endif()
diff --git a/Tests/CudaOnly/SharedRuntimeViaCUDAFlags/CMakeLists.txt b/Tests/CudaOnly/SharedRuntimeViaCUDAFlags/CMakeLists.txt
index 24ff478..cf6eef2 100644
--- a/Tests/CudaOnly/SharedRuntimeViaCUDAFlags/CMakeLists.txt
+++ b/Tests/CudaOnly/SharedRuntimeViaCUDAFlags/CMakeLists.txt
@@ -11,5 +11,6 @@
 if(UNIX)
   # Help the shared cuda runtime find libcudart as it is not located
   # in a default system searched location
-  set_property(TARGET CudaOnlySharedRuntimeViaCUDAFlags PROPERTY BUILD_RPATH ${CMAKE_CUDA_IMPLICIT_LINK_DIRECTORIES})
+  find_package(CUDAToolkit REQUIRED)
+  set_property(TARGET CudaOnlySharedRuntimeViaCUDAFlags PROPERTY BUILD_RPATH "${CUDAToolkit_LIBRARY_DIR}")
 endif()
diff --git a/Tests/CudaOnly/StaticRuntimePlusToolkit/CMakeLists.txt b/Tests/CudaOnly/StaticRuntimePlusToolkit/CMakeLists.txt
index ae03b66..8149060 100644
--- a/Tests/CudaOnly/StaticRuntimePlusToolkit/CMakeLists.txt
+++ b/Tests/CudaOnly/StaticRuntimePlusToolkit/CMakeLists.txt
@@ -39,5 +39,6 @@
 if(UNIX)
   # Help the shared cuda runtime find libcurand and libnppif when they are not located
   # in a default system searched location
-  set_property(TARGET CudaOnlyStaticRuntimePlusToolkit PROPERTY BUILD_RPATH ${CMAKE_CUDA_IMPLICIT_LINK_DIRECTORIES})
+  find_package(CUDAToolkit REQUIRED)
+  set_property(TARGET CudaOnlyStaticRuntimePlusToolkit PROPERTY BUILD_RPATH "${CUDAToolkit_LIBRARY_DIR}")
 endif()
diff --git a/Tests/CustomCommand/CMakeLists.txt b/Tests/CustomCommand/CMakeLists.txt
index fa06a94..25df300 100644
--- a/Tests/CustomCommand/CMakeLists.txt
+++ b/Tests/CustomCommand/CMakeLists.txt
@@ -1,7 +1,7 @@
 #
 # Wrapping
 #
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project (CustomCommand)
 
 add_subdirectory(GeneratedHeader)
diff --git a/Tests/CustomCommandByproducts/External/CMakeLists.txt b/Tests/CustomCommandByproducts/External/CMakeLists.txt
index feaa12e..81e072b 100644
--- a/Tests/CustomCommandByproducts/External/CMakeLists.txt
+++ b/Tests/CustomCommandByproducts/External/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(External C)
 
 add_library(ExternalLibrary STATIC ExternalLibrary.c)
diff --git a/Tests/CustomCommandWorkingDirectory/CMakeLists.txt b/Tests/CustomCommandWorkingDirectory/CMakeLists.txt
index 7697a9b..531690a 100644
--- a/Tests/CustomCommandWorkingDirectory/CMakeLists.txt
+++ b/Tests/CustomCommandWorkingDirectory/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(TestWorkingDir)
 
 add_custom_command(
diff --git a/Tests/CxxDialect/CMakeLists.txt b/Tests/CxxDialect/CMakeLists.txt
index 8c90339..c88641b 100644
--- a/Tests/CxxDialect/CMakeLists.txt
+++ b/Tests/CxxDialect/CMakeLists.txt
@@ -1,5 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
-cmake_policy(SET CMP0025 NEW)
+cmake_minimum_required(VERSION 3.5)
 project(CxxDialect)
 
 add_executable(use_typeof use_typeof.cxx)
diff --git a/Tests/CxxOnly/CMakeLists.txt b/Tests/CxxOnly/CMakeLists.txt
index 09689cb..6cd3a8e 100644
--- a/Tests/CxxOnly/CMakeLists.txt
+++ b/Tests/CxxOnly/CMakeLists.txt
@@ -1,5 +1,5 @@
 # a simple CXX only test case
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project (CxxOnly CXX)
 
 set(CMAKE_DEBUG_POSTFIX "_test_debug_postfix")
diff --git a/Tests/Dependency/CMakeLists.txt b/Tests/Dependency/CMakeLists.txt
index 58d3fb7..cae108e 100644
--- a/Tests/Dependency/CMakeLists.txt
+++ b/Tests/Dependency/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project( Dependency )
 
 # to test directories with only one character One was changed to 1
diff --git a/Tests/EmptyDepends/CMakeLists.txt b/Tests/EmptyDepends/CMakeLists.txt
index 272eff7..5ba0b49 100644
--- a/Tests/EmptyDepends/CMakeLists.txt
+++ b/Tests/EmptyDepends/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(EmptyDepends)
 
 include(CTest)
diff --git a/Tests/EnforceConfig.cmake.in b/Tests/EnforceConfig.cmake.in
index 7722d7d..a652efc 100644
--- a/Tests/EnforceConfig.cmake.in
+++ b/Tests/EnforceConfig.cmake.in
@@ -35,5 +35,8 @@
 unset(ENV{CMAKE_GENERATOR_TOOLSET})
 unset(ENV{CMAKE_EXPORT_COMPILE_COMMANDS})
 
+# Verify that our module implementations do not recurse too much.
+set(ENV{CMAKE_MAXIMUM_RECURSION_DEPTH} 100)
+
 @TEST_HOME_ENV_CODE@
-@TEST_WARN_VS11_CODE@
+@TEST_WARN_VS_CODE@
diff --git a/Tests/ExportImport/Export/CMakeLists.txt b/Tests/ExportImport/Export/CMakeLists.txt
index 6f19c13..67f2fcb 100644
--- a/Tests/ExportImport/Export/CMakeLists.txt
+++ b/Tests/ExportImport/Export/CMakeLists.txt
@@ -22,9 +22,18 @@
 set_property(TARGET testExe2 PROPERTY ENABLE_EXPORTS 1)
 set_property(TARGET testExe2 PROPERTY LINK_INTERFACE_LIBRARIES testExe2lib)
 
+add_library(compileOnly INTERFACE)
+target_compile_definitions(compileOnly INTERFACE FROM_compileOnly)
+target_link_options(compileOnly INTERFACE -fthis-flag-does-not-exist)
+
 add_library(testLib1 STATIC testLib1.c)
 add_library(testLib2 STATIC testLib2.c)
 target_link_libraries(testLib2 testLib1)
+target_link_libraries(testLib2
+  PRIVATE
+    testLib1
+    "$<COMPILE_ONLY:compileOnly>")
+
 
 # Test install(FILES) with generator expressions referencing testLib1.
 add_custom_command(TARGET testLib1 POST_BUILD
@@ -556,6 +565,7 @@
 # Install and export from install tree.
 install(
   TARGETS
+  compileOnly
   testExe1 testLib1 testLib2 testExe2 testLib3 testLib4 testExe3 testExe4
   testExe2lib testLib4lib testLib4libdbg testLib4libopt
   testLib6 testLib7 testLib8
diff --git a/Tests/ExportImport/Export/testLib2.c b/Tests/ExportImport/Export/testLib2.c
index 7a5206f..f5faffa 100644
--- a/Tests/ExportImport/Export/testLib2.c
+++ b/Tests/ExportImport/Export/testLib2.c
@@ -1,4 +1,8 @@
 
+#ifndef FROM_compileOnly
+#  error "Usage requirements from `compileOnly` not found"
+#endif
+
 extern int testLib1(void);
 
 int testLib2(void)
diff --git a/Tests/ExportImport/Import/try_compile/CMakeLists.txt b/Tests/ExportImport/Import/try_compile/CMakeLists.txt
index 813cf06..bb390f9 100644
--- a/Tests/ExportImport/Import/try_compile/CMakeLists.txt
+++ b/Tests/ExportImport/Import/try_compile/CMakeLists.txt
@@ -1,5 +1,5 @@
 
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 find_package(testLibRequired 2.5 REQUIRED)
 
diff --git a/Tests/ExternalProject/CMakeLists.txt b/Tests/ExternalProject/CMakeLists.txt
index 81d31e7..d1ab7ac 100644
--- a/Tests/ExternalProject/CMakeLists.txt
+++ b/Tests/ExternalProject/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(ExternalProjectTest NONE)
 if(CMAKE_XCODE_BUILD_SYSTEM VERSION_GREATER_EQUAL 12)
   cmake_policy(SET CMP0114 NEW)
diff --git a/Tests/ExternalProject/Example/CMakeLists.txt b/Tests/ExternalProject/Example/CMakeLists.txt
index c3f2614..b6785a6 100644
--- a/Tests/ExternalProject/Example/CMakeLists.txt
+++ b/Tests/ExternalProject/Example/CMakeLists.txt
@@ -1,5 +1,5 @@
 # This is the canonical simplest ExternalProject example CMakeLists.txt file:
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(ExternalProjectExample NONE)
 include(ExternalProject)
 
diff --git a/Tests/ExternalProjectLocal/CMakeLists.txt b/Tests/ExternalProjectLocal/CMakeLists.txt
index 57e8105..2dfc425 100644
--- a/Tests/ExternalProjectLocal/CMakeLists.txt
+++ b/Tests/ExternalProjectLocal/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(ExternalProjectLocalTest NONE)
 if(CMAKE_XCODE_BUILD_SYSTEM VERSION_GREATER_EQUAL 12)
   cmake_policy(SET CMP0114 NEW)
diff --git a/Tests/ExternalProjectUpdate/CMakeLists.txt b/Tests/ExternalProjectUpdate/CMakeLists.txt
index 6f8a7b1..e8c67b8 100644
--- a/Tests/ExternalProjectUpdate/CMakeLists.txt
+++ b/Tests/ExternalProjectUpdate/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(ExternalProjectUpdateTest NONE)
 if(CMAKE_XCODE_BUILD_SYSTEM VERSION_GREATER_EQUAL 12)
   cmake_policy(SET CMP0114 NEW)
@@ -70,7 +70,9 @@
 endif()
 
 if(do_git_tests)
-  set(local_git_repo "../../LocalRepositories/GIT")
+  cmake_path(SET local_git_repo NORMALIZE
+    "${CMAKE_CURRENT_BINARY_DIR}/LocalRepositories/GIT"
+  )
 
   # Unzip/untar the git repository in our source folder so that other
   # projects below may use it to test git args of ExternalProject_Add
@@ -79,6 +81,7 @@
   ExternalProject_Add(${proj}
     SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/LocalRepositories/GIT
     URL ${CMAKE_CURRENT_SOURCE_DIR}/gitrepo.tgz
+    DOWNLOAD_EXTRACT_TIMESTAMP NO
     BUILD_COMMAND ""
     CONFIGURE_COMMAND "${GIT_EXECUTABLE}" --version
     INSTALL_COMMAND ""
diff --git a/Tests/ExternalProjectUpdate/ExternalProjectUpdateTest.cmake b/Tests/ExternalProjectUpdate/ExternalProjectUpdateTest.cmake
index 394df87..08e533b 100644
--- a/Tests/ExternalProjectUpdate/ExternalProjectUpdateTest.cmake
+++ b/Tests/ExternalProjectUpdate/ExternalProjectUpdateTest.cmake
@@ -1,8 +1,14 @@
 # Set the ExternalProject GIT_TAG to desired_tag, and make sure the
 # resulting checked out version is resulting_sha and rebuild.
 # This check's the correct behavior of the ExternalProject UPDATE_COMMAND.
-# Also verify that a fetch only occurs when fetch_expected is 1.
-macro(check_a_tag desired_tag resulting_sha fetch_expected update_strategy)
+# Also verify that a fetch only occurs when fetch_expected_tsX is 1.
+macro(check_a_tag
+  desired_tag
+  resulting_sha
+  fetch_expected_ts1  # TutorialStep1-GIT
+  fetch_expected_ts2  # TutorialStep2-GIT
+  update_strategy
+)
   message( STATUS "Checking ExternalProjectUpdate to tag: ${desired_tag}" )
 
   # Remove the FETCH_HEAD file, so we can check if it gets replaced with a 'git
@@ -12,7 +18,11 @@
 
   # Give ourselves a marker in the output. It is difficult to tell where we
   # are up to without this
-  message(STATUS "===> check_a_tag ${desired_tag} ${resulting_sha} ${fetch_expected} ${update_strategy}")
+  message(STATUS "===> check_a_tag: "
+    "${desired_tag} ${resulting_sha} "
+    "${fetch_expected_ts1} ${fetch_expected_ts2} "
+    "${update_strategy}"
+  )
 
   # Configure
   execute_process(COMMAND ${CMAKE_COMMAND}
@@ -58,10 +68,10 @@
     )
   endif()
 
-  if( NOT EXISTS ${FETCH_HEAD_file} AND ${fetch_expected})
+  if( NOT EXISTS ${FETCH_HEAD_file} AND ${fetch_expected_ts1})
     message( FATAL_ERROR "Fetch did NOT occur when it was expected.")
   endif()
-  if( EXISTS ${FETCH_HEAD_file} AND NOT ${fetch_expected})
+  if( EXISTS ${FETCH_HEAD_file} AND NOT ${fetch_expected_ts1})
     message( FATAL_ERROR "Fetch DID occur when it was not expected.")
   endif()
 
@@ -154,10 +164,10 @@
     )
   endif()
 
-  if( NOT EXISTS ${FETCH_HEAD_file} AND ${fetch_expected})
+  if( NOT EXISTS ${FETCH_HEAD_file} AND ${fetch_expected_ts2})
     message( FATAL_ERROR "Fetch did NOT occur when it was expected.")
   endif()
-  if( EXISTS ${FETCH_HEAD_file} AND NOT ${fetch_expected})
+  if( EXISTS ${FETCH_HEAD_file} AND NOT ${fetch_expected_ts2})
     message( FATAL_ERROR "Fetch DID occur when it was not expected.")
   endif()
 endmacro()
@@ -179,16 +189,16 @@
 file(REMOVE_RECURSE ${ExternalProjectUpdate_BINARY_DIR}/CMakeExternals)
 
 if(do_git_tests)
-  check_a_tag(origin/master b5752a26ae448410926b35c275af3c192a53722e 1 REBASE)
-  check_a_tag(tag1          d1970730310fe8bc07e73f15dc570071f9f9654a 1 REBASE)
+  check_a_tag(origin/master b5752a26ae448410926b35c275af3c192a53722e 1 1 REBASE)
+  check_a_tag(tag1          d1970730310fe8bc07e73f15dc570071f9f9654a 1 0 REBASE)
   # With the Git UPDATE_COMMAND performance patch, this will not require a
   # 'git fetch'
-  check_a_tag(tag1          d1970730310fe8bc07e73f15dc570071f9f9654a 0 REBASE)
-  check_a_tag(tag2          5842b503ba4113976d9bb28d57b5aee1ad2736b7 1 REBASE)
-  check_a_tag(d19707303     d1970730310fe8bc07e73f15dc570071f9f9654a 0 REBASE)
-  check_a_tag(origin/master b5752a26ae448410926b35c275af3c192a53722e 1 REBASE)
+  check_a_tag(tag1          d1970730310fe8bc07e73f15dc570071f9f9654a 0 0 REBASE)
+  check_a_tag(tag2          5842b503ba4113976d9bb28d57b5aee1ad2736b7 1 0 REBASE)
+  check_a_tag(d19707303     d1970730310fe8bc07e73f15dc570071f9f9654a 0 0 REBASE)
+  check_a_tag(origin/master b5752a26ae448410926b35c275af3c192a53722e 1 1 REBASE)
   # This is a remote symbolic ref, so it will always trigger a 'git fetch'
-  check_a_tag(origin/master b5752a26ae448410926b35c275af3c192a53722e 1 REBASE)
+  check_a_tag(origin/master b5752a26ae448410926b35c275af3c192a53722e 1 1 REBASE)
 
   foreach(strategy IN ITEMS CHECKOUT REBASE_CHECKOUT)
     # Move local master back, then apply a change that will cause a conflict
@@ -222,7 +232,7 @@
       message(FATAL_ERROR "Could not commit conflicting change.")
     endif()
     # This should discard our commit but leave behind an annotated tag
-    check_a_tag(origin/master b5752a26ae448410926b35c275af3c192a53722e 1 ${strategy})
+    check_a_tag(origin/master b5752a26ae448410926b35c275af3c192a53722e 1 1 ${strategy})
   endforeach()
 
   # This file matches a .gitignore rule that the last commit defines. We can't
@@ -232,7 +242,7 @@
   # doesn't choke on it.
   set(ignoredFile ${ExternalProjectUpdate_BINARY_DIR}/CMakeExternals/Source/TutorialStep1-GIT/ignored_item)
   file(TOUCH ${ignoredFile})
-  check_a_tag(origin/master b5752a26ae448410926b35c275af3c192a53722e 1 REBASE)
+  check_a_tag(origin/master b5752a26ae448410926b35c275af3c192a53722e 1 1 REBASE)
   if(NOT EXISTS ${ignoredFile})
     message(FATAL_ERROR "Ignored file is missing")
   endif()
diff --git a/Tests/FindBLAS/Test/CMakeLists.txt b/Tests/FindBLAS/Test/CMakeLists.txt
index 14bf19f..9909d2e 100644
--- a/Tests/FindBLAS/Test/CMakeLists.txt
+++ b/Tests/FindBLAS/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.4)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindBLAS C)
 include(CTest)
 
diff --git a/Tests/FindBZip2/Test/CMakeLists.txt b/Tests/FindBZip2/Test/CMakeLists.txt
index e9cb618..136b3fd 100644
--- a/Tests/FindBZip2/Test/CMakeLists.txt
+++ b/Tests/FindBZip2/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.4)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindBZip2 C)
 include(CTest)
 
diff --git a/Tests/FindBoost/Test/CMakeLists.txt b/Tests/FindBoost/Test/CMakeLists.txt
index f60ccfa..39fa532 100644
--- a/Tests/FindBoost/Test/CMakeLists.txt
+++ b/Tests/FindBoost/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindBoost CXX)
 include(CTest)
 
diff --git a/Tests/FindBoost/TestFail/CMakeLists.txt b/Tests/FindBoost/TestFail/CMakeLists.txt
index 7c14a59..3db1a4f 100644
--- a/Tests/FindBoost/TestFail/CMakeLists.txt
+++ b/Tests/FindBoost/TestFail/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindBoost CXX)
 include(CTest)
 
diff --git a/Tests/FindBoost/TestHeaders/CMakeLists.txt b/Tests/FindBoost/TestHeaders/CMakeLists.txt
index d7be327..7dd12e9 100644
--- a/Tests/FindBoost/TestHeaders/CMakeLists.txt
+++ b/Tests/FindBoost/TestHeaders/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindBoostHeaders CXX)
 include(CTest)
 
diff --git a/Tests/FindDevIL/Test/CMakeLists.txt b/Tests/FindDevIL/Test/CMakeLists.txt
index c2c1322..db80ccb 100644
--- a/Tests/FindDevIL/Test/CMakeLists.txt
+++ b/Tests/FindDevIL/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindDevIL C)
 include(CTest)
 
diff --git a/Tests/FindGIF/Test/CMakeLists.txt b/Tests/FindGIF/Test/CMakeLists.txt
index 961e636..eb0291e 100644
--- a/Tests/FindGIF/Test/CMakeLists.txt
+++ b/Tests/FindGIF/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.4)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindGIF C)
 include(CTest)
 
diff --git a/Tests/FindGLEW/Test/CMakeLists.txt b/Tests/FindGLEW/Test/CMakeLists.txt
index 954ee10..de8d97d 100644
--- a/Tests/FindGLEW/Test/CMakeLists.txt
+++ b/Tests/FindGLEW/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindGLEW LANGUAGES CXX)
 include(CTest)
 
diff --git a/Tests/FindGSL/rng/CMakeLists.txt b/Tests/FindGSL/rng/CMakeLists.txt
index b15d6ac..497b6c3 100644
--- a/Tests/FindGSL/rng/CMakeLists.txt
+++ b/Tests/FindGSL/rng/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.5)
 project(FindGSL_rng CXX)
 include(CTest)
 
diff --git a/Tests/FindGTK2/atk/CMakeLists.txt b/Tests/FindGTK2/atk/CMakeLists.txt
index 0392d88..b80c6fc 100644
--- a/Tests/FindGTK2/atk/CMakeLists.txt
+++ b/Tests/FindGTK2/atk/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(atk C)
 
diff --git a/Tests/FindGTK2/atkmm/CMakeLists.txt b/Tests/FindGTK2/atkmm/CMakeLists.txt
index ec838de..cea87cd 100644
--- a/Tests/FindGTK2/atkmm/CMakeLists.txt
+++ b/Tests/FindGTK2/atkmm/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(atkmm CXX)
 
diff --git a/Tests/FindGTK2/cairo/CMakeLists.txt b/Tests/FindGTK2/cairo/CMakeLists.txt
index 3652ad6..42d371a 100644
--- a/Tests/FindGTK2/cairo/CMakeLists.txt
+++ b/Tests/FindGTK2/cairo/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(cairo C)
 
diff --git a/Tests/FindGTK2/cairomm/CMakeLists.txt b/Tests/FindGTK2/cairomm/CMakeLists.txt
index cde0f42..5d957ee 100644
--- a/Tests/FindGTK2/cairomm/CMakeLists.txt
+++ b/Tests/FindGTK2/cairomm/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(cairomm CXX)
 
diff --git a/Tests/FindGTK2/gdk/CMakeLists.txt b/Tests/FindGTK2/gdk/CMakeLists.txt
index 35ef337..2b8f533 100644
--- a/Tests/FindGTK2/gdk/CMakeLists.txt
+++ b/Tests/FindGTK2/gdk/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(gdk C)
 
diff --git a/Tests/FindGTK2/gdk_pixbuf/CMakeLists.txt b/Tests/FindGTK2/gdk_pixbuf/CMakeLists.txt
index ea1b05d..3524f06 100644
--- a/Tests/FindGTK2/gdk_pixbuf/CMakeLists.txt
+++ b/Tests/FindGTK2/gdk_pixbuf/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(gdk_pixbuf C)
 
diff --git a/Tests/FindGTK2/gdkmm/CMakeLists.txt b/Tests/FindGTK2/gdkmm/CMakeLists.txt
index 72fc6f4..be1cceb 100644
--- a/Tests/FindGTK2/gdkmm/CMakeLists.txt
+++ b/Tests/FindGTK2/gdkmm/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(gdkmm CXX)
 
diff --git a/Tests/FindGTK2/gio/CMakeLists.txt b/Tests/FindGTK2/gio/CMakeLists.txt
index 4835afa..b420f48 100644
--- a/Tests/FindGTK2/gio/CMakeLists.txt
+++ b/Tests/FindGTK2/gio/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(gio C)
 
diff --git a/Tests/FindGTK2/giomm/CMakeLists.txt b/Tests/FindGTK2/giomm/CMakeLists.txt
index b639979..bae34ff 100644
--- a/Tests/FindGTK2/giomm/CMakeLists.txt
+++ b/Tests/FindGTK2/giomm/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(giomm CXX)
 
diff --git a/Tests/FindGTK2/glib/CMakeLists.txt b/Tests/FindGTK2/glib/CMakeLists.txt
index 536fc67..8efde3d 100644
--- a/Tests/FindGTK2/glib/CMakeLists.txt
+++ b/Tests/FindGTK2/glib/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(glib C)
 
diff --git a/Tests/FindGTK2/glibmm/CMakeLists.txt b/Tests/FindGTK2/glibmm/CMakeLists.txt
index 25d5518..f0785dc 100644
--- a/Tests/FindGTK2/glibmm/CMakeLists.txt
+++ b/Tests/FindGTK2/glibmm/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(glibmm CXX)
 
diff --git a/Tests/FindGTK2/gmodule/CMakeLists.txt b/Tests/FindGTK2/gmodule/CMakeLists.txt
index 2bfb81e..9c686a6 100644
--- a/Tests/FindGTK2/gmodule/CMakeLists.txt
+++ b/Tests/FindGTK2/gmodule/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(gmodule C)
 
diff --git a/Tests/FindGTK2/gobject/CMakeLists.txt b/Tests/FindGTK2/gobject/CMakeLists.txt
index 11520f8..83d9546 100644
--- a/Tests/FindGTK2/gobject/CMakeLists.txt
+++ b/Tests/FindGTK2/gobject/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(gobject C)
 
diff --git a/Tests/FindGTK2/gthread/CMakeLists.txt b/Tests/FindGTK2/gthread/CMakeLists.txt
index 5ecfd9b..d97585c 100644
--- a/Tests/FindGTK2/gthread/CMakeLists.txt
+++ b/Tests/FindGTK2/gthread/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(gthread C)
 
diff --git a/Tests/FindGTK2/gtk/CMakeLists.txt b/Tests/FindGTK2/gtk/CMakeLists.txt
index 2c67619..2b5a56d 100644
--- a/Tests/FindGTK2/gtk/CMakeLists.txt
+++ b/Tests/FindGTK2/gtk/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(gtk C)
 
diff --git a/Tests/FindGTK2/gtkmm/CMakeLists.txt b/Tests/FindGTK2/gtkmm/CMakeLists.txt
index 3375a55..179f5db 100644
--- a/Tests/FindGTK2/gtkmm/CMakeLists.txt
+++ b/Tests/FindGTK2/gtkmm/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(gtkmm CXX)
 
diff --git a/Tests/FindGTK2/pango/CMakeLists.txt b/Tests/FindGTK2/pango/CMakeLists.txt
index bd6b13a..e9d6b9d 100644
--- a/Tests/FindGTK2/pango/CMakeLists.txt
+++ b/Tests/FindGTK2/pango/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(pango C)
 
diff --git a/Tests/FindGTK2/pangocairo/CMakeLists.txt b/Tests/FindGTK2/pangocairo/CMakeLists.txt
index 157b9c2..bbb1f27 100644
--- a/Tests/FindGTK2/pangocairo/CMakeLists.txt
+++ b/Tests/FindGTK2/pangocairo/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(pangocairo C)
 
diff --git a/Tests/FindGTK2/pangoft2/CMakeLists.txt b/Tests/FindGTK2/pangoft2/CMakeLists.txt
index 76966e7..801629c 100644
--- a/Tests/FindGTK2/pangoft2/CMakeLists.txt
+++ b/Tests/FindGTK2/pangoft2/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(pangoft2 C)
 
diff --git a/Tests/FindGTK2/pangomm/CMakeLists.txt b/Tests/FindGTK2/pangomm/CMakeLists.txt
index 0bb49e2..853a1dd 100644
--- a/Tests/FindGTK2/pangomm/CMakeLists.txt
+++ b/Tests/FindGTK2/pangomm/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(pangomm CXX)
 
diff --git a/Tests/FindGTK2/pangoxft/CMakeLists.txt b/Tests/FindGTK2/pangoxft/CMakeLists.txt
index 7051d35..f267d6c 100644
--- a/Tests/FindGTK2/pangoxft/CMakeLists.txt
+++ b/Tests/FindGTK2/pangoxft/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(pangoxft C)
 
diff --git a/Tests/FindGTK2/sigc++/CMakeLists.txt b/Tests/FindGTK2/sigc++/CMakeLists.txt
index 9c1fff7..f09ea66 100644
--- a/Tests/FindGTK2/sigc++/CMakeLists.txt
+++ b/Tests/FindGTK2/sigc++/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(sigc++ CXX)
 
diff --git a/Tests/FindGTest/Test/CMakeLists.txt b/Tests/FindGTest/Test/CMakeLists.txt
index 9c1eb5e..582cbaa 100644
--- a/Tests/FindGTest/Test/CMakeLists.txt
+++ b/Tests/FindGTest/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindGTest CXX)
 include(CTest)
 
diff --git a/Tests/FindGnuTLS/Test/CMakeLists.txt b/Tests/FindGnuTLS/Test/CMakeLists.txt
index c5a9819..e17987d 100644
--- a/Tests/FindGnuTLS/Test/CMakeLists.txt
+++ b/Tests/FindGnuTLS/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.4)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindGnuTLS C)
 include(CTest)
 
diff --git a/Tests/FindHDF5/CMakeLists.txt b/Tests/FindHDF5/CMakeLists.txt
new file mode 100644
index 0000000..6bc551a
--- /dev/null
+++ b/Tests/FindHDF5/CMakeLists.txt
@@ -0,0 +1,87 @@
+# These tests may be configured by cache entries:
+#
+# CMake_TEST_FindHDF5:BOOL=ON
+#   Enable this directory.
+#
+# CMake_TEST_FindHDF5_<variant>_<lang>_COMPILER:FILEPATH=...
+#   Enable testing for a variant/language combination with the given wrapper.
+#   Variants: Serial, OpenMPI, MPICH
+#   Languages: C, CXX, Fortran
+#
+# CMake_TEST_FindHDF5_<variant>_<lang>_COMPILER_EXPLICIT:BOOL=ON
+#   Pass the above wrapper path to the test as HDF5_<lang>_COMPILER_EXECUTABLE.
+
+set(test_langs C CXX)
+if(CMAKE_Fortran_COMPILER)
+  list(APPEND test_langs Fortran)
+endif()
+
+# Test detection without any special hints.
+add_test(NAME FindHDF5.Default COMMAND
+  ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+  --build-and-test
+  "${CMake_SOURCE_DIR}/Tests/FindHDF5/Test"
+  "${CMake_BINARY_DIR}/Tests/FindHDF5/Test-Default"
+  ${build_generator_args}
+  --build-project TestFindHDF5
+  --build-options ${build_options} -DTEST_SERIAL=1 "-DTEST_LANGS=${test_langs}"
+  --test-command ${CMAKE_CTEST_COMMAND} -V -C $<CONFIGURATION>
+  )
+
+foreach(variant Serial OpenMPI MPICH)
+  set(wrapper "")
+  set(wrapper_langs "")
+  set(wrapper_as_compiler "")
+  foreach(lang IN LISTS test_langs)
+    if(CMake_TEST_FindHDF5_${variant}_${lang}_COMPILER)
+      list(APPEND wrapper_langs ${lang})
+      list(APPEND wrapper_as_compiler -DCMAKE_${lang}_COMPILER=${CMake_TEST_FindHDF5_${variant}_${lang}_COMPILER})
+      if(CMake_TEST_FindHDF5_${variant}_${lang}_COMPILER_EXPLICIT)
+        list(APPEND wrapper -DHDF5_${lang}_COMPILER_EXECUTABLE=${CMake_TEST_FindHDF5_${variant}_${lang}_COMPILER})
+      endif()
+    endif()
+  endforeach()
+
+  if(NOT wrapper_langs)
+    continue()
+  endif()
+
+  if(variant STREQUAL "Serial")
+    set(test_kind -DTEST_SERIAL=1)
+  else()
+    set(test_kind -DTEST_PARALLEL=1)
+  endif()
+
+  # Test detection using the HDF5 compiler wrappers as a reference.
+  add_test(NAME FindHDF5.${variant} COMMAND
+    ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+    --build-and-test
+    "${CMake_SOURCE_DIR}/Tests/FindHDF5/Test"
+    "${CMake_BINARY_DIR}/Tests/FindHDF5/Test-${variant}"
+    ${build_generator_args}
+    --build-project TestFindHDF5
+    --build-options ${build_options} ${test_kind} "-DTEST_LANGS=${wrapper_langs}" ${wrapper}
+    --test-command ${CMAKE_CTEST_COMMAND} -V -C $<CONFIGURATION>
+    )
+  if(CMake_TEST_FindHDF5_${variant}_ENVMOD)
+    set_property(TEST FindHDF5.${variant} PROPERTY ENVIRONMENT_MODIFICATION ${CMake_TEST_FindHDF5_${variant}_ENVMOD})
+  endif()
+
+  # Test detection using the HDF5 compiler wrappers as the compiler.
+  # Skip this if there are spaces in the path.  The HDF5 wrappers do not like them.
+  if(NOT CMAKE_CURRENT_BINARY_DIR MATCHES " ")
+    add_test(NAME FindHDF5.${variant}-Wrapper COMMAND
+      ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+      --build-and-test
+      "${CMake_SOURCE_DIR}/Tests/FindHDF5/Test"
+      "${CMake_BINARY_DIR}/Tests/FindHDF5/Test-${variant}-Wrapper"
+      ${build_generator_args}
+      --build-project TestFindHDF5
+      --build-options ${build_options} ${test_kind} "-DTEST_LANGS=${wrapper_langs}" -D TEST_WRAPPER_AS_COMPILER=1 ${wrapper_as_compiler}
+      --test-command ${CMAKE_CTEST_COMMAND} -V -C $<CONFIGURATION>
+      )
+    if(CMake_TEST_FindHDF5_${variant}_ENVMOD)
+      set_property(TEST FindHDF5.${variant}-Wrapper PROPERTY ENVIRONMENT_MODIFICATION ${CMake_TEST_FindHDF5_${variant}_ENVMOD})
+    endif()
+  endif()
+endforeach()
diff --git a/Tests/FindHDF5/Test/CMakeLists.txt b/Tests/FindHDF5/Test/CMakeLists.txt
new file mode 100644
index 0000000..da1b369
--- /dev/null
+++ b/Tests/FindHDF5/Test/CMakeLists.txt
@@ -0,0 +1,106 @@
+cmake_minimum_required(VERSION 3.26)
+project(TestFindHDF5 LANGUAGES ${TEST_LANGS})
+include(CTest)
+message(STATUS "CTEST_FULL_OUTPUT (Avoid ctest truncation of output)")
+
+if(TEST_PARALLEL)
+  set(HDF5_PREFER_PARALLEL 1)
+endif()
+
+find_package(HDF5 REQUIRED COMPONENTS ${TEST_LANGS} HL)
+
+set(variables HDF5_FOUND HDF5_VERSION)
+if(NOT TEST_WRAPPER_AS_COMPILER)
+   list(APPEND variables HDF5_INCLUDE_DIRS HDF5_LIBRARIES HDF5_HL_LIBRARIES)
+   foreach(lang ${TEST_LANGS})
+     list(APPEND variables
+       HDF5_${lang}_COMPILER_EXECUTABLE
+       HDF5_${lang}_INCLUDE_DIRS
+       HDF5_${lang}_LIBRARIES
+       HDF5_${lang}_HL_LIBRARIES
+       )
+   endforeach()
+ endif()
+foreach(var IN LISTS variables)
+  message(STATUS "${var}='${${var}}'")
+endforeach()
+foreach(var IN LISTS variables)
+  if(NOT DEFINED ${var})
+    message(SEND_ERROR "Variable '${var}' is not defined!")
+  endif()
+endforeach()
+
+set(targets HDF5::HDF5)
+if(CMAKE_C_COMPILER_LOADED)
+  list(APPEND targets hdf5::hdf5 hdf5::hdf5_hl)
+endif()
+if(CMAKE_CXX_COMPILER_LOADED)
+  list(APPEND targets hdf5::hdf5_cpp hdf5::hdf5_hl_cpp)
+endif()
+if(CMAKE_Fortran_COMPILER_LOADED)
+  list(APPEND targets hdf5::hdf5_fortran hdf5::hdf5_hl_fortran)
+endif()
+foreach(target IN LISTS targets)
+  if(NOT TARGET ${target})
+    message(SEND_ERROR "Target '${target}' not defined!")
+  endif()
+endforeach()
+
+message(STATUS "HDF5_IS_PARALLEL='${HDF5_IS_PARALLEL}'")
+if(TEST_SERIAL)
+  if(HDF5_IS_PARALLEL)
+    message(SEND_ERROR "HDF5_IS_PARALLEL is true in serial test.")
+  endif()
+endif()
+if(TEST_PARALLEL)
+  if(NOT HDF5_IS_PARALLEL)
+    message(SEND_ERROR "HDF5_IS_PARALLEL is false in parallel test.")
+  endif()
+endif()
+
+if(TEST_PARALLEL)
+  set(MPI_CXX_SKIP_MPICXX TRUE)
+  set(MPI_CXX_VALIDATE_SKIP_MPICXX TRUE)
+  find_package(MPI REQUIRED)
+  set(mpi_C_tgt MPI::MPI_C)
+  set(mpi_C_inc ${MPI_C_INCLUDE_PATH})
+  set(mpi_C_lib ${MPI_C_LINK_FLAGS} ${MPI_C_LIBRARIES})
+  set(mpi_Fortran_tgt MPI::MPI_Fortran)
+  set(mpi_Fortran_inc ${MPI_Fortran_INCLUDE_PATH})
+  set(mpi_Fortran_lib ${MPI_Fortran_LIBRARIES})
+endif()
+
+if(CMAKE_C_COMPILER_LOADED)
+  add_executable(test_C_tgt test_C.c)
+  target_link_libraries(test_C_tgt PRIVATE hdf5::hdf5 ${mpi_C_tgt})
+  add_test(NAME test_C_tgt COMMAND test_C_tgt test_C_tgt.h5)
+
+  add_executable(test_C_var test_C.c)
+  target_include_directories(test_C_var PRIVATE ${HDF5_C_INCLUDE_DIRS} ${mpi_C_inc})
+  target_link_libraries(test_C_var PRIVATE ${HDF5_C_LIBRARIES} ${mpi_C_lib})
+  add_test(NAME test_C_var COMMAND test_C_var test_C_var.h5)
+endif()
+
+if(CMAKE_CXX_COMPILER_LOADED)
+  add_executable(test_CXX_tgt test_CXX.cxx)
+  target_link_libraries(test_CXX_tgt PRIVATE hdf5::hdf5_cpp
+    hdf5::hdf5 # FIXME: hdf5::hdf5_cpp should link hdf5::hdf5 automatically.
+    ${mpi_C_tgt})
+  add_test(NAME test_CXX_tgt COMMAND test_CXX_tgt test_CXX_tgt.h5)
+
+  add_executable(test_CXX_var test_CXX.cxx)
+  target_include_directories(test_CXX_var PRIVATE ${HDF5_CXX_INCLUDE_DIRS} ${mpi_C_inc})
+  target_link_libraries(test_CXX_var PRIVATE ${HDF5_CXX_LIBRARIES} ${mpi_C_lib})
+  add_test(NAME test_CXX_var COMMAND test_CXX_var test_CXX_var.h5)
+endif()
+
+if(CMAKE_Fortran_COMPILER_LOADED)
+  add_executable(test_Fortran_tgt test_Fortran.f90)
+  target_link_libraries(test_Fortran_tgt PRIVATE hdf5::hdf5_fortran ${mpi_Fortran_tgt})
+  add_test(NAME test_Fortran_tgt COMMAND test_Fortran_tgt)
+
+  add_executable(test_Fortran_var test_Fortran.f90)
+  target_include_directories(test_Fortran_var PRIVATE ${HDF5_Fortran_INCLUDE_DIRS} ${mpi_Fortran_inc})
+  target_link_libraries(test_Fortran_var PRIVATE ${HDF5_Fortran_LIBRARIES} ${mpi_Fortran_lib})
+  add_test(NAME test_Fortran_var COMMAND test_Fortran_var)
+endif()
diff --git a/Tests/FindHDF5/Test/test_C.c b/Tests/FindHDF5/Test/test_C.c
new file mode 100644
index 0000000..4c18364
--- /dev/null
+++ b/Tests/FindHDF5/Test/test_C.c
@@ -0,0 +1,12 @@
+#include <hdf5.h>
+
+int main(int argc, const char* argv[])
+{
+  hid_t fid;
+  if (argc != 2) {
+    return 1;
+  }
+  fid = H5Fcreate(argv[1], H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
+  H5Fclose(fid);
+  return 0;
+}
diff --git a/Tests/FindHDF5/Test/test_CXX.cxx b/Tests/FindHDF5/Test/test_CXX.cxx
new file mode 100644
index 0000000..93fb462
--- /dev/null
+++ b/Tests/FindHDF5/Test/test_CXX.cxx
@@ -0,0 +1,14 @@
+#include <H5Cpp.h>
+
+#ifndef H5_NO_NAMESPACE
+using namespace H5;
+#endif
+
+int main(int argc, const char* argv[])
+{
+  if (argc != 2) {
+    return 1;
+  }
+  H5File f(argv[1], H5F_ACC_TRUNC);
+  return 0;
+}
diff --git a/Tests/FindHDF5/Test/test_Fortran.f90 b/Tests/FindHDF5/Test/test_Fortran.f90
new file mode 100644
index 0000000..4c12c8c
--- /dev/null
+++ b/Tests/FindHDF5/Test/test_Fortran.f90
@@ -0,0 +1,6 @@
+program hdf5_hello
+  use hdf5
+  integer error
+  call h5open_f(error)
+  call h5close_f(error)
+end
diff --git a/Tests/FindICU/Test/CMakeLists.txt b/Tests/FindICU/Test/CMakeLists.txt
index 1247ac7..066d173 100644
--- a/Tests/FindICU/Test/CMakeLists.txt
+++ b/Tests/FindICU/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindICU LANGUAGES CXX)
 include(CTest)
 
diff --git a/Tests/FindImageMagick/Test/CMakeLists.txt b/Tests/FindImageMagick/Test/CMakeLists.txt
index 6182260..4e4f14d 100644
--- a/Tests/FindImageMagick/Test/CMakeLists.txt
+++ b/Tests/FindImageMagick/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.4)
+cmake_minimum_required(VERSION 3.5)
 project(FindImageMagick C CXX)
 include(CTest)
 
diff --git a/Tests/FindJPEG/Test/CMakeLists.txt b/Tests/FindJPEG/Test/CMakeLists.txt
index 912c7a1..390b3a7 100644
--- a/Tests/FindJPEG/Test/CMakeLists.txt
+++ b/Tests/FindJPEG/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindJPEG C)
 include(CTest)
 
diff --git a/Tests/FindJsonCpp/Test/CMakeLists.txt b/Tests/FindJsonCpp/Test/CMakeLists.txt
index d1dc647..5f100ea 100644
--- a/Tests/FindJsonCpp/Test/CMakeLists.txt
+++ b/Tests/FindJsonCpp/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindJsonCpp CXX)
 include(CTest)
 
diff --git a/Tests/FindLAPACK/Test/CMakeLists.txt b/Tests/FindLAPACK/Test/CMakeLists.txt
index f5d5a73..ce9af98 100644
--- a/Tests/FindLAPACK/Test/CMakeLists.txt
+++ b/Tests/FindLAPACK/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.4)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindLAPACK C)
 include(CTest)
 
diff --git a/Tests/FindLibLZMA/Test/CMakeLists.txt b/Tests/FindLibLZMA/Test/CMakeLists.txt
index c59dcdb..438938e 100644
--- a/Tests/FindLibLZMA/Test/CMakeLists.txt
+++ b/Tests/FindLibLZMA/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.4)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindLZMA C)
 include(CTest)
 
diff --git a/Tests/FindLibXml2/Test/CMakeLists.txt b/Tests/FindLibXml2/Test/CMakeLists.txt
index 041cc13..9b866ab 100644
--- a/Tests/FindLibXml2/Test/CMakeLists.txt
+++ b/Tests/FindLibXml2/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.4)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindLibXml2 C)
 include(CTest)
 
diff --git a/Tests/FindLibXslt/Test/CMakeLists.txt b/Tests/FindLibXslt/Test/CMakeLists.txt
index e932661..32a157f 100644
--- a/Tests/FindLibXslt/Test/CMakeLists.txt
+++ b/Tests/FindLibXslt/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.4)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindLibXslt C)
 include(CTest)
 
diff --git a/Tests/FindMPI/CMakeLists.txt b/Tests/FindMPI/CMakeLists.txt
index 121d978..1bc12c8 100644
--- a/Tests/FindMPI/CMakeLists.txt
+++ b/Tests/FindMPI/CMakeLists.txt
@@ -19,3 +19,6 @@
   -DMPI_TEST_Fortran=${CMake_TEST_FindMPI_FLAG_Fortran}
   --test-command ${CMAKE_CTEST_COMMAND} -V -C $<CONFIGURATION>
   )
+if(CMake_TEST_FindMPI_ENVMOD)
+  set_property(TEST FindMPI.Test PROPERTY ENVIRONMENT_MODIFICATION ${CMake_TEST_FindMPI_ENVMOD})
+endif()
diff --git a/Tests/FindMatlab/basic_checks/CMakeLists.txt b/Tests/FindMatlab/basic_checks/CMakeLists.txt
index c0c752a..e9b696c 100644
--- a/Tests/FindMatlab/basic_checks/CMakeLists.txt
+++ b/Tests/FindMatlab/basic_checks/CMakeLists.txt
@@ -1,5 +1,5 @@
 
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 enable_testing()
 project(basic_checks)
 
diff --git a/Tests/FindMatlab/components_checks/CMakeLists.txt b/Tests/FindMatlab/components_checks/CMakeLists.txt
index f5d4880..efb99ae 100644
--- a/Tests/FindMatlab/components_checks/CMakeLists.txt
+++ b/Tests/FindMatlab/components_checks/CMakeLists.txt
@@ -1,5 +1,5 @@
 
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 enable_testing()
 project(component_checks)
 
diff --git a/Tests/FindMatlab/failure_reports/CMakeLists.txt b/Tests/FindMatlab/failure_reports/CMakeLists.txt
index 4b092cd..45e48d7 100644
--- a/Tests/FindMatlab/failure_reports/CMakeLists.txt
+++ b/Tests/FindMatlab/failure_reports/CMakeLists.txt
@@ -1,5 +1,5 @@
 
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 enable_testing()
 project(failure_reports)
 
diff --git a/Tests/FindMatlab/no_implicit_link_checks/CMakeLists.txt b/Tests/FindMatlab/no_implicit_link_checks/CMakeLists.txt
index bceeba1..58db0ec 100644
--- a/Tests/FindMatlab/no_implicit_link_checks/CMakeLists.txt
+++ b/Tests/FindMatlab/no_implicit_link_checks/CMakeLists.txt
@@ -1,5 +1,5 @@
 
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 enable_testing()
 project(no_implicit_links_checks)
 
diff --git a/Tests/FindMatlab/r2018a_check/CMakeLists.txt b/Tests/FindMatlab/r2018a_check/CMakeLists.txt
index c732be1..8b21888 100644
--- a/Tests/FindMatlab/r2018a_check/CMakeLists.txt
+++ b/Tests/FindMatlab/r2018a_check/CMakeLists.txt
@@ -1,5 +1,5 @@
 
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 enable_testing()
 project(r2018a_checks)
 
diff --git a/Tests/FindMatlab/targets_checks/CMakeLists.txt b/Tests/FindMatlab/targets_checks/CMakeLists.txt
index 4af7cc3..c709bbd 100644
--- a/Tests/FindMatlab/targets_checks/CMakeLists.txt
+++ b/Tests/FindMatlab/targets_checks/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 enable_testing()
 project(targets_checks)
 
diff --git a/Tests/FindMatlab/versions_checks/CMakeLists.txt b/Tests/FindMatlab/versions_checks/CMakeLists.txt
index d015730..f203f4b 100644
--- a/Tests/FindMatlab/versions_checks/CMakeLists.txt
+++ b/Tests/FindMatlab/versions_checks/CMakeLists.txt
@@ -1,5 +1,5 @@
 
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 enable_testing()
 project(versions_checks)
 
diff --git a/Tests/FindODBC/Test/CMakeLists.txt b/Tests/FindODBC/Test/CMakeLists.txt
index a20c0f7..1e3a239 100644
--- a/Tests/FindODBC/Test/CMakeLists.txt
+++ b/Tests/FindODBC/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindODBC C)
 include(CTest)
 
diff --git a/Tests/FindOpenAL/Test/CMakeLists.txt b/Tests/FindOpenAL/Test/CMakeLists.txt
index fa3e263..6479df6 100644
--- a/Tests/FindOpenAL/Test/CMakeLists.txt
+++ b/Tests/FindOpenAL/Test/CMakeLists.txt
@@ -12,3 +12,9 @@
 target_include_directories(test_var PRIVATE ${OPENAL_INCLUDE_DIR})
 target_link_libraries(test_var PRIVATE ${OPENAL_LIBRARY})
 add_test(NAME test_var COMMAND test_var)
+
+# OpenAL has been deprecated on macOS since Catalina (10.15)
+if(APPLE)
+  target_compile_options(test_tgt PRIVATE "-Wno-deprecated-declarations")
+  target_compile_options(test_var PRIVATE "-Wno-deprecated-declarations")
+endif()
diff --git a/Tests/FindOpenAL/Test/main.cxx b/Tests/FindOpenAL/Test/main.cxx
index bb45faf..27204fb 100644
--- a/Tests/FindOpenAL/Test/main.cxx
+++ b/Tests/FindOpenAL/Test/main.cxx
@@ -1,13 +1,19 @@
-#include <AL/al.h>
-#include <AL/alc.h>
+#ifdef __APPLE__
+#  include "OpenAL/al.h"
+#  include "OpenAL/alc.h"
+#else
+#  include <AL/al.h>
+#  include <AL/alc.h>
+#endif
 #include <stdio.h>
 
 int main()
 {
   /* Reference an AL symbol without requiring a context at runtime.  */
-  printf("&alGetString = %p\n", &alGetString);
+  printf("AL_VERSION: %s\n", alGetString(AL_VERSION));
 
   /* Reference an ALC symbol without requiring a context at runtime.  */
-  printf("&alcGetString = %p\n", &alcGetString);
+  printf("ALC_DEVICE_SPECIFIER: %s\n",
+         alcGetString(NULL, ALC_DEVICE_SPECIFIER));
   return 0;
 }
diff --git a/Tests/FindOpenCL/Test/CMakeLists.txt b/Tests/FindOpenCL/Test/CMakeLists.txt
index f8a6587..4a1f6bd 100644
--- a/Tests/FindOpenCL/Test/CMakeLists.txt
+++ b/Tests/FindOpenCL/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.4)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindOpenCL C)
 include(CTest)
 
diff --git a/Tests/FindOpenGL/Test/CMakeLists.txt b/Tests/FindOpenGL/Test/CMakeLists.txt
index 9004a98..7c805c0 100644
--- a/Tests/FindOpenGL/Test/CMakeLists.txt
+++ b/Tests/FindOpenGL/Test/CMakeLists.txt
@@ -44,28 +44,115 @@
   add_test(NAME test_comp_glx_novnd COMMAND test_comp_glx_novnd)
 endif()
 
-# EGL is only available on Linux+GLVND at present.
-if(OpenGL_TEST_VND)
-  find_package(OpenGL COMPONENTS OpenGL EGL)
-  if(OpenGL_EGL_FOUND)
-    add_executable(test_comp_egl main.c)
-    target_link_libraries(test_comp_egl PRIVATE OpenGL::OpenGL OpenGL::EGL)
-    add_test(NAME test_comp_egl COMMAND test_comp_egl)
-    # EGL-only code should not link to GLX.
-    execute_process(COMMAND ldd test_comp_egl
-                    OUTPUT_VARIABLE LDD_OUT
-                    ERROR_VARIABLE LDD_ERR)
-    if("${LDD_OUT}" MATCHES "GLX")
-      message(FATAL_ERROR "EGL-only code links to GLX!")
-    endif()
+find_package(OpenGL COMPONENTS OpenGL EGL)
+if(OpenGL_EGL_FOUND)
+  add_executable(test_comp_egl main.c)
+  target_link_libraries(test_comp_egl PRIVATE OpenGL::OpenGL OpenGL::EGL)
+  add_test(NAME test_comp_egl COMMAND test_comp_egl)
+  # EGL-only code should not link to GLX.
+  get_target_property(iface_libs OpenGL::EGL INTERFACE_LINK_LIBRARIES)
+  if(iface_libs MATCHES "GLX")
+    message(FATAL_ERROR "EGL-only code links to GLX!")
   endif()
+endif()
 
-  # all three COMPONENTS together.
-  find_package(OpenGL COMPONENTS OpenGL EGL GLX)
-  if(OpenGL_EGL_FOUND AND OpenGL_GLX_FOUND)
-    add_executable(test_comp_both main.c)
-    target_link_libraries(test_comp_both PRIVATE OpenGL::OpenGL OpenGL::EGL
-                          OpenGL::GLX)
-    add_test(NAME test_comp_both COMMAND test_comp_both)
+# all three COMPONENTS together.
+find_package(OpenGL COMPONENTS OpenGL EGL GLX)
+if(OpenGL_EGL_FOUND AND OpenGL_GLX_FOUND)
+  add_executable(test_comp_both main.c)
+  target_link_libraries(test_comp_both PRIVATE OpenGL::OpenGL OpenGL::EGL
+                        OpenGL::GLX)
+  add_test(NAME test_comp_both COMMAND test_comp_both)
+endif()
+
+find_package(OpenGL COMPONENTS GLES2)
+if(OpenGL_GLES2_FOUND)
+  add_executable(test_comp_gles2 main_gles2.c)
+  target_link_libraries(test_comp_gles2 PRIVATE OpenGL::GLES2)
+  add_test(NAME test_comp_gles2 COMMAND test_comp_gles2)
+  # GLES2-only code should not link to OpenGL
+  get_target_property(iface_libs test_comp_gles2 LINK_LIBRARIES)
+  if(iface_libs MATCHES "OpenGL::OpenGL")
+    message(FATAL_ERROR "GLES2-only code links to OpenGL!")
+  endif()
+endif()
+
+# GLES2 and EGL together.
+find_package(OpenGL COMPONENTS GLES2 EGL)
+if(OpenGL_GLES2_FOUND AND OpenGL_EGL_FOUND)
+  add_executable(test_comp_gles2_egl main_gles2.c)
+  target_link_libraries(test_comp_gles2_egl PRIVATE OpenGL::GLES2
+                        OpenGL::EGL)
+  add_test(NAME test_comp_gles2_egl COMMAND test_comp_gles2_egl)
+  # GLES2-EGL-only code should not link to OpenGL or GLX
+  get_target_property(iface_libs test_comp_gles2_egl LINK_LIBRARIES)
+  if(iface_libs MATCHES "OpenGL::OpenGL")
+    message(FATAL_ERROR "GLES2-only code links to OpenGL!")
+  endif()
+  if(iface_libs MATCHES "GLX")
+    message(FATAL_ERROR "GLES2-EGL-only code links to GLX!")
+  endif()
+endif()
+
+# GLES2 and GLX together.
+find_package(OpenGL COMPONENTS GLES2 GLX)
+if(OpenGL_GLES2_FOUND AND OpenGL_GLX_FOUND)
+  add_executable(test_comp_gles2_glx main_gles2.c)
+  target_link_libraries(test_comp_gles2_glx PRIVATE OpenGL::GLES2
+                        OpenGL::GLX)
+  add_test(NAME test_comp_gles2_glx COMMAND test_comp_gles2_glx)
+  # GLES2-GLX-only code should not link to OpenGL or EGL
+  get_target_property(iface_libs test_comp_gles2_glx LINK_LIBRARIES)
+  if(iface_libs MATCHES "OpenGL::OpenGL")
+    message(FATAL_ERROR "GLES2-only code links to OpenGL!")
+  endif()
+  if(iface_libs MATCHES "EGL")
+    message(FATAL_ERROR "GLES2-GLX-only code links to EGL!")
+  endif()
+endif()
+
+find_package(OpenGL COMPONENTS GLES3)
+if(OpenGL_GLES3_FOUND)
+  add_executable(test_comp_gles3 main_gles3.c)
+  target_link_libraries(test_comp_gles3 PRIVATE OpenGL::GLES3)
+  add_test(NAME test_comp_gles3 COMMAND test_comp_gles3)
+  # GLES3-only code should not link to OpenGL.
+  get_target_property(iface_libs test_comp_gles3 LINK_LIBRARIES)
+  if(iface_libs MATCHES "OpenGL::OpenGL")
+    message(FATAL_ERROR "GLES3-only code links to OpenGL!")
+  endif()
+endif()
+
+# GLES3 and EGL together.
+find_package(OpenGL COMPONENTS GLES3 EGL)
+if(OpenGL_GLES3_FOUND AND OpenGL_EGL_FOUND)
+  add_executable(test_comp_gles3_egl main_gles3.c)
+  target_link_libraries(test_comp_gles3_egl PRIVATE OpenGL::GLES3
+                        OpenGL::EGL)
+  add_test(NAME test_comp_gles3_egl COMMAND test_comp_gles3_egl)
+  # GLES3-EGL-only code should not link to OpenGL or GLX
+  get_target_property(iface_libs test_comp_gles3_egl LINK_LIBRARIES)
+  if(iface_libs MATCHES "OpenGL::OpenGL")
+    message(FATAL_ERROR "GLES3-only code links to OpenGL!")
+  endif()
+  if(iface_libs MATCHES "GLX")
+    message(FATAL_ERROR "GLES3-EGL-only code links to GLX!")
+  endif()
+endif()
+
+# GLES3 and GLX together.
+find_package(OpenGL COMPONENTS GLES3 GLX)
+if(OpenGL_GLES3_FOUND AND OpenGL_GLX_FOUND)
+  add_executable(test_comp_gles3_glx main_gles3.c)
+  target_link_libraries(test_comp_gles3_glx PRIVATE OpenGL::GLES3
+                        OpenGL::GLX)
+  add_test(NAME test_comp_gles3_glx COMMAND test_comp_gles3_glx)
+  # GLESr-GLX-only code should not link to OpenGL or EGL
+  get_target_property(iface_libs test_comp_gles3_glx LINK_LIBRARIES)
+  if(iface_libs MATCHES "OpenGL::OpenGL")
+    message(FATAL_ERROR "GLES3-only code links to OpenGL!")
+  endif()
+  if(iface_libs MATCHES "EGL")
+    message(FATAL_ERROR "GLES3-GLX-only code links to EGL!")
   endif()
 endif()
diff --git a/Tests/FindOpenGL/Test/main_gles2.c b/Tests/FindOpenGL/Test/main_gles2.c
new file mode 100644
index 0000000..52f5936
--- /dev/null
+++ b/Tests/FindOpenGL/Test/main_gles2.c
@@ -0,0 +1,17 @@
+#ifdef _WIN32
+#  error "GLES2 cannot be tested on WIN32 platforms."
+#endif
+#ifdef __APPLE__
+#  error "GLES2 cannot be tested on macOS platform."
+#else
+#  include <GLES2/gl2.h>
+#endif
+
+#include <stdio.h>
+
+int main()
+{
+  /* Reference a GL symbol without requiring a context at runtime.  */
+  printf("&glGetString = %p\n", &glGetString);
+  return 0;
+}
diff --git a/Tests/FindOpenGL/Test/main_gles3.c b/Tests/FindOpenGL/Test/main_gles3.c
new file mode 100644
index 0000000..875f73c
--- /dev/null
+++ b/Tests/FindOpenGL/Test/main_gles3.c
@@ -0,0 +1,17 @@
+#ifdef _WIN32
+#  error "GLES3 cannot be tested on WIN32 platforms."
+#endif
+#ifdef __APPLE__
+#  error "GLES3 cannot be tested on macOS platform."
+#else
+#  include <GLES3/gl3.h>
+#endif
+
+#include <stdio.h>
+
+int main()
+{
+  /* Reference a GL symbol without requiring a context at runtime.  */
+  printf("&glGetString = %p\n", &glGetString);
+  return 0;
+}
diff --git a/Tests/FindOpenSP/Test/CMakeLists.txt b/Tests/FindOpenSP/Test/CMakeLists.txt
index d8992d9..266a459 100644
--- a/Tests/FindOpenSP/Test/CMakeLists.txt
+++ b/Tests/FindOpenSP/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.4)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindOpenSP CXX)
 include(CTest)
 
diff --git a/Tests/FindOpenSSL/rand/CMakeLists.txt b/Tests/FindOpenSSL/rand/CMakeLists.txt
index 520d5d2..c443221 100644
--- a/Tests/FindOpenSSL/rand/CMakeLists.txt
+++ b/Tests/FindOpenSSL/rand/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.5)
 project(FindOpenSSL_rand CXX)
 include(CTest)
 
diff --git a/Tests/FindPNG/Test/CMakeLists.txt b/Tests/FindPNG/Test/CMakeLists.txt
index ad50011..66cdda1 100644
--- a/Tests/FindPNG/Test/CMakeLists.txt
+++ b/Tests/FindPNG/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.4)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindPNG C)
 include(CTest)
 
diff --git a/Tests/FindPatch/CMakeLists.txt b/Tests/FindPatch/CMakeLists.txt
index 541f5bd..65b778b 100644
--- a/Tests/FindPatch/CMakeLists.txt
+++ b/Tests/FindPatch/CMakeLists.txt
@@ -4,5 +4,6 @@
   "${CMake_SOURCE_DIR}/Tests/FindPatch/Test"
   "${CMake_BINARY_DIR}/Tests/FindPatch/Test"
   ${build_generator_args}
+  --build-project TestFindPatch
   --build-options ${build_options}
 )
diff --git a/Tests/FindPython/ArtifactsInteractive/CMakeLists.txt b/Tests/FindPython/ArtifactsInteractive/CMakeLists.txt
index 524be92..99823a6 100644
--- a/Tests/FindPython/ArtifactsInteractive/CMakeLists.txt
+++ b/Tests/FindPython/ArtifactsInteractive/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestArtifactsInteractive LANGUAGES C)
 
diff --git a/Tests/FindPython/CustomFailureMessage/CMakeLists.txt b/Tests/FindPython/CustomFailureMessage/CMakeLists.txt
index a0d8eb2..283aeec 100644
--- a/Tests/FindPython/CustomFailureMessage/CMakeLists.txt
+++ b/Tests/FindPython/CustomFailureMessage/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestCustomFailureMessage LANGUAGES NONE)
 
diff --git a/Tests/FindPython/CustomFailureMessage/Check/CMakeLists.txt b/Tests/FindPython/CustomFailureMessage/Check/CMakeLists.txt
index 2164ac1..0fb3036 100644
--- a/Tests/FindPython/CustomFailureMessage/Check/CMakeLists.txt
+++ b/Tests/FindPython/CustomFailureMessage/Check/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestCustomFailureMessage.Check LANGUAGES NONE)
 
diff --git a/Tests/FindPython/DifferentComponents/CMakeLists.txt b/Tests/FindPython/DifferentComponents/CMakeLists.txt
index 7476632..e3e7b36 100644
--- a/Tests/FindPython/DifferentComponents/CMakeLists.txt
+++ b/Tests/FindPython/DifferentComponents/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestDifferentComponents LANGUAGES C)
 
diff --git a/Tests/FindPython/ExactVersion/CMakeLists.txt b/Tests/FindPython/ExactVersion/CMakeLists.txt
index 60abb26..1bd94c4 100644
--- a/Tests/FindPython/ExactVersion/CMakeLists.txt
+++ b/Tests/FindPython/ExactVersion/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestExactVersion LANGUAGES C)
 
diff --git a/Tests/FindPython/Implementation/CMakeLists.txt b/Tests/FindPython/Implementation/CMakeLists.txt
index 592329b..8086c16 100644
--- a/Tests/FindPython/Implementation/CMakeLists.txt
+++ b/Tests/FindPython/Implementation/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestImplementation${Python_REQUESTED_IMPLEMENTATION} LANGUAGES NONE)
 
diff --git a/Tests/FindPython/IronPython/CMakeLists.txt b/Tests/FindPython/IronPython/CMakeLists.txt
index 47ca022..fd3182e 100644
--- a/Tests/FindPython/IronPython/CMakeLists.txt
+++ b/Tests/FindPython/IronPython/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestIronPython LANGUAGES NONE)
 
diff --git a/Tests/FindPython/IronPython2/CMakeLists.txt b/Tests/FindPython/IronPython2/CMakeLists.txt
index fd9d947..b623972 100644
--- a/Tests/FindPython/IronPython2/CMakeLists.txt
+++ b/Tests/FindPython/IronPython2/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestIronPython2 LANGUAGES NONE)
 
diff --git a/Tests/FindPython/MultiplePackages/CMakeLists.txt b/Tests/FindPython/MultiplePackages/CMakeLists.txt
index 5c85155..4845035 100644
--- a/Tests/FindPython/MultiplePackages/CMakeLists.txt
+++ b/Tests/FindPython/MultiplePackages/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestMultiplePackages C)
 
diff --git a/Tests/FindPython/NumPy/CMakeLists.txt b/Tests/FindPython/NumPy/CMakeLists.txt
index 3e17f68..9920336 100644
--- a/Tests/FindPython/NumPy/CMakeLists.txt
+++ b/Tests/FindPython/NumPy/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestNumPy LANGUAGES C)
 
diff --git a/Tests/FindPython/NumPyOnly/CMakeLists.txt b/Tests/FindPython/NumPyOnly/CMakeLists.txt
index bedc627..9aa1bcf 100644
--- a/Tests/FindPython/NumPyOnly/CMakeLists.txt
+++ b/Tests/FindPython/NumPyOnly/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestNumPyOnly LANGUAGES C)
 
diff --git a/Tests/FindPython/PyPy/CMakeLists.txt b/Tests/FindPython/PyPy/CMakeLists.txt
index 4cf7c0a..dfc22d8 100644
--- a/Tests/FindPython/PyPy/CMakeLists.txt
+++ b/Tests/FindPython/PyPy/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestPyPy LANGUAGES C)
 
diff --git a/Tests/FindPython/PyPy2/CMakeLists.txt b/Tests/FindPython/PyPy2/CMakeLists.txt
index ebfc9ab..5b7ce30 100644
--- a/Tests/FindPython/PyPy2/CMakeLists.txt
+++ b/Tests/FindPython/PyPy2/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestPyPy2 LANGUAGES C)
 
diff --git a/Tests/FindPython/PyPy3/CMakeLists.txt b/Tests/FindPython/PyPy3/CMakeLists.txt
index bf7cd61..b702c99 100644
--- a/Tests/FindPython/PyPy3/CMakeLists.txt
+++ b/Tests/FindPython/PyPy3/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestPyPy3 LANGUAGES C)
 
diff --git a/Tests/FindPython/Python/CMakeLists.txt b/Tests/FindPython/Python/CMakeLists.txt
index 9bec22f..85b1711 100644
--- a/Tests/FindPython/Python/CMakeLists.txt
+++ b/Tests/FindPython/Python/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestPython LANGUAGES C)
 
diff --git a/Tests/FindPython/Python2/CMakeLists.txt b/Tests/FindPython/Python2/CMakeLists.txt
index 39577b2..95ed495 100644
--- a/Tests/FindPython/Python2/CMakeLists.txt
+++ b/Tests/FindPython/Python2/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestPython2 LANGUAGES C)
 
diff --git a/Tests/FindPython/Python2Embedded/CMakeLists.txt b/Tests/FindPython/Python2Embedded/CMakeLists.txt
index a1036ce..d9b2d22 100644
--- a/Tests/FindPython/Python2Embedded/CMakeLists.txt
+++ b/Tests/FindPython/Python2Embedded/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestPython2Embedded LANGUAGES C)
 
diff --git a/Tests/FindPython/Python2Fail/CMakeLists.txt b/Tests/FindPython/Python2Fail/CMakeLists.txt
index 989688f..7a6520d 100644
--- a/Tests/FindPython/Python2Fail/CMakeLists.txt
+++ b/Tests/FindPython/Python2Fail/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestPython2Fail C)
 
diff --git a/Tests/FindPython/Python2Module/CMakeLists.txt b/Tests/FindPython/Python2Module/CMakeLists.txt
index c9d46ac..7334d7a 100644
--- a/Tests/FindPython/Python2Module/CMakeLists.txt
+++ b/Tests/FindPython/Python2Module/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestPython2Module LANGUAGES C)
 
diff --git a/Tests/FindPython/Python2SABIModule/CMakeLists.txt b/Tests/FindPython/Python2SABIModule/CMakeLists.txt
index 4f01197..ffbaa33 100644
--- a/Tests/FindPython/Python2SABIModule/CMakeLists.txt
+++ b/Tests/FindPython/Python2SABIModule/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestPython2SABIModule LANGUAGES C)
 
diff --git a/Tests/FindPython/Python3/CMakeLists.txt b/Tests/FindPython/Python3/CMakeLists.txt
index e40557d..42d31f2 100644
--- a/Tests/FindPython/Python3/CMakeLists.txt
+++ b/Tests/FindPython/Python3/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestPython3 LANGUAGES C)
 
diff --git a/Tests/FindPython/Python3Embedded/CMakeLists.txt b/Tests/FindPython/Python3Embedded/CMakeLists.txt
index c45bd8c..1d362be 100644
--- a/Tests/FindPython/Python3Embedded/CMakeLists.txt
+++ b/Tests/FindPython/Python3Embedded/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestPython3Embedded LANGUAGES C)
 
diff --git a/Tests/FindPython/Python3Fail/CMakeLists.txt b/Tests/FindPython/Python3Fail/CMakeLists.txt
index cd46c88..5eca0cb 100644
--- a/Tests/FindPython/Python3Fail/CMakeLists.txt
+++ b/Tests/FindPython/Python3Fail/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestPython3Fail C)
 
diff --git a/Tests/FindPython/Python3Module/CMakeLists.txt b/Tests/FindPython/Python3Module/CMakeLists.txt
index ccc1fdb..57c0ddf 100644
--- a/Tests/FindPython/Python3Module/CMakeLists.txt
+++ b/Tests/FindPython/Python3Module/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestPython3Module LANGUAGES C)
 
diff --git a/Tests/FindPython/RequiredArtifacts/CMakeLists.txt b/Tests/FindPython/RequiredArtifacts/CMakeLists.txt
index 42d282d..cb9d4d3 100644
--- a/Tests/FindPython/RequiredArtifacts/CMakeLists.txt
+++ b/Tests/FindPython/RequiredArtifacts/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestRequiredArtifacts LANGUAGES C)
 
diff --git a/Tests/FindPython/RequiredArtifacts/Check/CMakeLists.txt b/Tests/FindPython/RequiredArtifacts/Check/CMakeLists.txt
index bb4f67c..4d9c7c8 100644
--- a/Tests/FindPython/RequiredArtifacts/Check/CMakeLists.txt
+++ b/Tests/FindPython/RequiredArtifacts/Check/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestRequiredArtifacts.Check LANGUAGES C)
 
diff --git a/Tests/FindPython/SOABI/CMakeLists.txt b/Tests/FindPython/SOABI/CMakeLists.txt
index 84f7362..60399d3 100644
--- a/Tests/FindPython/SOABI/CMakeLists.txt
+++ b/Tests/FindPython/SOABI/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestSOABI LANGUAGES C)
 
diff --git a/Tests/FindPython/VirtualEnv/CMakeLists.txt b/Tests/FindPython/VirtualEnv/CMakeLists.txt
index dae3282..e2e5bd2 100644
--- a/Tests/FindPython/VirtualEnv/CMakeLists.txt
+++ b/Tests/FindPython/VirtualEnv/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestVirtualEnv LANGUAGES NONE)
 
diff --git a/Tests/FindPython/VirtualEnvConda/CMakeLists.txt b/Tests/FindPython/VirtualEnvConda/CMakeLists.txt
index 23d208d..2f7c0db 100644
--- a/Tests/FindPython/VirtualEnvConda/CMakeLists.txt
+++ b/Tests/FindPython/VirtualEnvConda/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 project(TestVirtualEnvConda LANGUAGES NONE)
 
diff --git a/Tests/FindSDL/Test/CMakeLists.txt b/Tests/FindSDL/Test/CMakeLists.txt
index 61d4f4b..0dd1047 100644
--- a/Tests/FindSDL/Test/CMakeLists.txt
+++ b/Tests/FindSDL/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindSDL C)
 include(CTest)
 
diff --git a/Tests/FindSQLite3/Test/CMakeLists.txt b/Tests/FindSQLite3/Test/CMakeLists.txt
index bcc6ebd..43cdd37 100644
--- a/Tests/FindSQLite3/Test/CMakeLists.txt
+++ b/Tests/FindSQLite3/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.4)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindSQLite3 C)
 include(CTest)
 
diff --git a/Tests/FindTIFF/Test/CMakeLists.txt b/Tests/FindTIFF/Test/CMakeLists.txt
index e235db3..54404d0 100644
--- a/Tests/FindTIFF/Test/CMakeLists.txt
+++ b/Tests/FindTIFF/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindTIFF)
 include(CTest)
 
diff --git a/Tests/FindThreads/C-only/CMakeLists.txt b/Tests/FindThreads/C-only/CMakeLists.txt
index ee2a5f9..9bc50a4 100644
--- a/Tests/FindThreads/C-only/CMakeLists.txt
+++ b/Tests/FindThreads/C-only/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.3 FATAL_ERROR)
+cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
 project(FindThreads_C-only C)
 
 find_package(Threads REQUIRED)
diff --git a/Tests/FindThreads/CXX-only/CMakeLists.txt b/Tests/FindThreads/CXX-only/CMakeLists.txt
index 3c6cc1e..11ae60c 100644
--- a/Tests/FindThreads/CXX-only/CMakeLists.txt
+++ b/Tests/FindThreads/CXX-only/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.3 FATAL_ERROR)
+cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
 project(FindThreads_CXX-only CXX)
 
 find_package(Threads REQUIRED)
diff --git a/Tests/FindVulkan/Test/CMakeLists.txt b/Tests/FindVulkan/Test/CMakeLists.txt
index dfcfc15..7198d22 100644
--- a/Tests/FindVulkan/Test/CMakeLists.txt
+++ b/Tests/FindVulkan/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.4)
+cmake_minimum_required(VERSION 3.5)
 cmake_policy(SET CMP0091 NEW)
 project(TestFindVulkan C CXX)
 include(CTest)
diff --git a/Tests/FindX11/Test/CMakeLists.txt b/Tests/FindX11/Test/CMakeLists.txt
index 18a73a3..3312f6f 100644
--- a/Tests/FindX11/Test/CMakeLists.txt
+++ b/Tests/FindX11/Test/CMakeLists.txt
@@ -32,13 +32,38 @@
 test_x11_component(x11_components Xaw)
 test_x11_component(x11_components xcb)
 test_x11_component(x11_components X11_xcb)
+test_x11_component(x11_components xcb_composite)
+test_x11_component(x11_components xcb_cursor)
+test_x11_component(x11_components xcb_damage)
+test_x11_component(x11_components xcb_dpms)
+test_x11_component(x11_components xcb_dri2)
+test_x11_component(x11_components xcb_dri3)
+test_x11_component(x11_components xcb_errors)
+test_x11_component(x11_components xcb_ewmh)
+test_x11_component(x11_components xcb_glx)
 test_x11_component(x11_components xcb_icccm)
-test_x11_component(x11_components xcb_randr)
-test_x11_component(x11_components xcb_util)
-test_x11_component(x11_components xcb_xfixes)
-test_x11_component(x11_components xcb_xtest)
+test_x11_component(x11_components xcb_image)
 test_x11_component(x11_components xcb_keysyms)
+test_x11_component(x11_components xcb_present)
+test_x11_component(x11_components xcb_randr)
+test_x11_component(x11_components xcb_record)
+test_x11_component(x11_components xcb_render)
+test_x11_component(x11_components xcb_render_util)
+test_x11_component(x11_components xcb_res)
+test_x11_component(x11_components xcb_screensaver)
+test_x11_component(x11_components xcb_shape)
+test_x11_component(x11_components xcb_shm)
+test_x11_component(x11_components xcb_sync)
+test_x11_component(x11_components xcb_util)
+test_x11_component(x11_components xcb_xf86dri)
+test_x11_component(x11_components xcb_xfixes)
+test_x11_component(x11_components xcb_xinerama)
+test_x11_component(x11_components xcb_xinput)
 test_x11_component(x11_components xcb_xkb)
+test_x11_component(x11_components xcb_xrm)
+test_x11_component(x11_components xcb_xtest)
+test_x11_component(x11_components xcb_xvmc)
+test_x11_component(x11_components xcb_xv)
 test_x11_component(x11_components Xcomposite)
 test_x11_component(x11_components Xdamage)
 test_x11_component(x11_components Xdmcp)
@@ -76,10 +101,38 @@
     Xaw
     xcb
     X11_xcb
+    xcb_composite
+    xcb_cursor
+    xcb_damage
+    xcb_dpms
+    xcb_dri2
+    xcb_dri3
+    xcb_errors
+    xcb_ewmh
+    xcb_glx
     xcb_icccm
+    xcb_image
+    xcb_keysyms
+    xcb_present
     xcb_randr
+    xcb_record
+    xcb_render
+    xcb_render_util
+    xcb_res
+    xcb_screensaver
+    xcb_shape
+    xcb_shm
+    xcb_sync
     xcb_util
+    xcb_xf86dri
     xcb_xfixes
+    xcb_xinerama
+    xcb_xinput
+    xcb_xkb
+    xcb_xrm
+    xcb_xtest
+    xcb_xvmc
+    xcb_xv
     Xcomposite
     Xdamage
     Xdmcp
diff --git a/Tests/FindX11/Test/main.c b/Tests/FindX11/Test/main.c
index 653a2be..2542145 100644
--- a/Tests/FindX11/Test/main.c
+++ b/Tests/FindX11/Test/main.c
@@ -326,7 +326,7 @@
 
 #endif
 
-#ifdef HAVE_xcb
+#ifdef HAVE_X11_xcb
 #  include <xcb/xcb.h>
 
 static void test_xcb(void)
@@ -336,61 +336,185 @@
   xcb_disconnect(connection);
 }
 
-#  ifdef HAVE_xcb_randr
-#    include <xcb/randr.h>
+#endif
 
-static void test_xcb_randr(void)
+#ifdef HAVE_X11_xcb_composite
+#  include <xcb/composite.h>
+#  include <xcb/xcb.h>
+
+static void test_xcb_composite(void)
 {
-  int screen_nbr;
-  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
-  xcb_randr_query_version_cookie_t cookie =
-    xcb_randr_query_version(connection, 0, 0);
+  xcb_connection_t* connection = xcb_connect(NULL, NULL);
+  xcb_composite_query_version(connection, 0, 0);
   xcb_disconnect(connection);
 }
 
-#  endif
+#endif
 
-#  ifdef HAVE_xcb_util
-#    include <xcb/xcb_aux.h>
+#ifdef HAVE_X11_xcb_cursor
+#  include <xcb/xcb.h>
+#  include <xcb/xcb_cursor.h>
 
-static void test_xcb_util(void)
+static void test_xcb_cursor(void)
 {
   int screen_nbr;
   xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
-  xcb_screen_t* screen = xcb_aux_get_screen(connection, screen_nbr);
+  xcb_screen_iterator_t screens =
+    xcb_setup_roots_iterator(xcb_get_setup(connection));
+  xcb_cursor_context_t* ctx;
+  xcb_cursor_context_new(connection, screens.data, &ctx);
+  xcb_cursor_context_free(ctx);
   xcb_disconnect(connection);
 }
 
-#  endif
+#endif
 
-#  ifdef HAVE_xcb_xfixes
-#    include <xcb/xcb_xfixes.h>
+#ifdef HAVE_X11_xcb_damage
+#  include <xcb/damage.h>
+#  include <xcb/xcb.h>
 
-static void test_xcb_xfixes(void)
+static void test_xcb_damage(void)
 {
   int screen_nbr;
   xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
-  xcb_xfixes_query_version(connection, 1, 0);
+  xcb_damage_query_version_cookie_t cookie =
+    xcb_damage_query_version(connection, 0, 0);
   xcb_disconnect(connection);
 }
 
-#  endif
+#endif
 
-#  ifdef HAVE_xcb_xtest
-#    include <xcb/xtest.h>
+#ifdef HAVE_X11_xcb_dpms
+#  include <xcb/dpms.h>
+#  include <xcb/xcb.h>
 
-static void test_xcb_xtest(void)
+static void test_xcb_dpms(void)
 {
   int screen_nbr;
   xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
-  xcb_test_get_version_unchecked(connection, 1, 0);
+  xcb_dpms_get_version_cookie_t cookie =
+    xcb_dpms_get_version(connection, 0, 0);
   xcb_disconnect(connection);
 }
 
-#  endif
+#endif
 
-#  ifdef HAVE_xcb_keysyms
-#    include <xcb/xcb_keysyms.h>
+#ifdef HAVE_X11_xcb_dri2
+#  include <xcb/dri2.h>
+#  include <xcb/xcb.h>
+
+static void test_xcb_dri2(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_dri2_query_version_cookie_t cookie =
+    xcb_dri2_query_version(connection, 0, 0);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_dri3
+#  include <xcb/dri3.h>
+#  include <xcb/xcb.h>
+
+static void test_xcb_dri3(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_dri3_query_version_cookie_t cookie =
+    xcb_dri3_query_version(connection, 0, 0);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_errors
+#  include <xcb/xcb.h>
+#  include <xcb/xcb_errors.h>
+
+static void test_xcb_errors(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_errors_context_t* context;
+  xcb_errors_context_new(connection, &context);
+  xcb_errors_context_free(context);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_ewmh
+#  include <xcb/xcb.h>
+#  include <xcb/xcb_ewmh.h>
+
+static void test_xcb_ewmh(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_ewmh_connection_t ewmh_connection;
+  xcb_intern_atom_cookie_t* cookie =
+    xcb_ewmh_init_atoms(connection, &ewmh_connection);
+  xcb_ewmh_init_atoms_replies(&ewmh_connection, cookie, NULL);
+  xcb_ewmh_connection_wipe(&ewmh_connection);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_glx
+#  include <xcb/glx.h>
+#  include <xcb/xcb.h>
+
+static void test_xcb_glx(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_glx_query_version_cookie_t cookie =
+    xcb_glx_query_version(connection, 0, 0);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_icccm
+#  include <xcb/xcb.h>
+#  include <xcb/xcb_icccm.h>
+
+static void test_xcb_icccm(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_window_t root =
+    xcb_setup_roots_iterator(xcb_get_setup(connection)).data->root;
+  xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_name(connection, root);
+  xcb_icccm_get_text_property_reply_t reply;
+  xcb_icccm_get_wm_name_reply(connection, cookie, &reply, NULL);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_image
+#  include <xcb/xcb.h>
+#  include <xcb/xcb_image.h>
+
+static void test_xcb_image(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  // xcb_image is too convoluted/undocumented to make an
+  // actually working example, apologies :)
+  xcb_image_create(0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, 0, 0);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_keysyms
+#  include <xcb/xcb.h>
+#  include <xcb/xcb_keysyms.h>
 
 static void test_xcb_keysyms(void)
 {
@@ -402,7 +526,298 @@
   xcb_disconnect(connection);
 }
 
-#  endif
+#endif
+
+#ifdef HAVE_X11_xcb_present
+#  include <xcb/present.h>
+#  include <xcb/xcb.h>
+
+static void test_xcb_present(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_present_query_version_cookie_t cookie =
+    xcb_present_query_version(connection, 0, 0);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_randr
+#  include <xcb/randr.h>
+#  include <xcb/xcb.h>
+
+static void test_xcb_randr(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_randr_query_version_cookie_t cookie =
+    xcb_randr_query_version(connection, 0, 0);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_record
+#  include <xcb/record.h>
+#  include <xcb/xcb.h>
+
+static void test_xcb_record(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_record_query_version_cookie_t cookie =
+    xcb_record_query_version(connection, 0, 0);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_render
+#  include <xcb/render.h>
+#  include <xcb/xcb.h>
+
+static void test_xcb_render(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_render_query_version_cookie_t cookie =
+    xcb_render_query_version(connection, 0, 0);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_render_util
+#  include <xcb/xcb.h>
+#  include <xcb/xcb_renderutil.h>
+
+static void test_xcb_render_util(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  const xcb_render_query_version_reply_t* cookie =
+    xcb_render_util_query_version(connection);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_res
+#  include <xcb/res.h>
+#  include <xcb/xcb.h>
+
+static void test_xcb_res(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_res_query_version_cookie_t cookie =
+    xcb_res_query_version(connection, 0, 0);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_screensaver
+#  include <xcb/screensaver.h>
+#  include <xcb/xcb.h>
+
+static void test_xcb_screensaver(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_screensaver_query_version_cookie_t cookie =
+    xcb_screensaver_query_version(connection, 0, 0);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_shape
+#  include <xcb/shape.h>
+#  include <xcb/xcb.h>
+
+static void test_xcb_shape(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_shape_query_version_cookie_t cookie =
+    xcb_shape_query_version(connection);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_shm
+#  include <xcb/shm.h>
+#  include <xcb/xcb.h>
+
+static void test_xcb_shm(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_shm_query_version_cookie_t cookie = xcb_shm_query_version(connection);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_sync
+#  include <xcb/sync.h>
+#  include <xcb/xcb.h>
+
+static void test_xcb_sync(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_sync_initialize_cookie_t cookie = xcb_sync_initialize(connection, 0, 0);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_util
+#  include <xcb/xcb.h>
+#  include <xcb/xcb_aux.h>
+
+static void test_xcb_util(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_screen_t* screen = xcb_aux_get_screen(connection, screen_nbr);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_xf86dri
+#  include <xcb/xcb.h>
+#  include <xcb/xf86dri.h>
+
+static void test_xcb_xf86dri(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_xf86dri_query_version_cookie_t cookie =
+    xcb_xf86dri_query_version(connection);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_xfixes
+#  include <xcb/xcb.h>
+#  include <xcb/xfixes.h>
+
+static void test_xcb_xfixes(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_xfixes_query_version(connection, 1, 0);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_xinerama
+#  include <xcb/xcb.h>
+#  include <xcb/xinerama.h>
+
+static void test_xcb_xinerama(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_xinerama_query_version_cookie_t cookie =
+    xcb_xinerama_query_version(connection, 0, 0);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_xinput
+#  include <xcb/xcb.h>
+#  include <xcb/xinput.h>
+
+static void test_xcb_xinput(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_input_xi_query_version_cookie_t cookie =
+    xcb_input_xi_query_version(connection, 0, 0);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_xkb
+#  include <xcb/xcb.h>
+#  include <xcb/xkb.h>
+
+static void test_xcb_xkb(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_xkb_use_extension_cookie_t cookie =
+    xcb_xkb_use_extension(connection, 0, 0);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_xrm
+#  include <xcb/xcb.h>
+#  include <xcb/xcb_xrm.h>
+
+static void test_xcb_xrm(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_xrm_database_t* db = xcb_xrm_database_from_default(connection);
+  xcb_xrm_database_free(db);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_xtest
+#  include <xcb/xcb.h>
+#  include <xcb/xtest.h>
+
+static void test_xcb_xtest(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_test_get_version_unchecked(connection, 1, 0);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_xvmc
+#  include <xcb/xcb.h>
+#  include <xcb/xvmc.h>
+
+static void test_xcb_xvmc(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_xvmc_query_version_cookie_t cookie = xcb_xvmc_query_version(connection);
+  xcb_disconnect(connection);
+}
+
+#endif
+
+#ifdef HAVE_X11_xcb_xv
+#  include <xcb/xcb.h>
+#  include <xcb/xv.h>
+
+static void test_xcb_xv(void)
+{
+  int screen_nbr;
+  xcb_connection_t* connection = xcb_connect(NULL, &screen_nbr);
+  xcb_xv_query_extension_cookie_t cookie = xcb_xv_query_extension(connection);
+  xcb_disconnect(connection);
+}
 
 #endif
 
@@ -493,19 +908,105 @@
 #ifdef HAVE_X11_Xaw
     test_Xaw,
 #endif
-#ifdef HAVE_xcb
+#ifdef HAVE_X11_xcb
     test_xcb,
 #endif
-#ifdef HAVE_xcb_util
+#ifdef HAVE_X11_xcb_composite
+    test_xcb_composite,
+#endif
+#ifdef HAVE_X11_xcb_cursor
+    test_xcb_cursor,
+#endif
+#ifdef HAVE_X11_xcb_damage
+    test_xcb_damage,
+#endif
+#ifdef HAVE_X11_xcb_dpms
+    test_xcb_dpms,
+#endif
+#ifdef HAVE_X11_xcb_dri2
+    test_xcb_dri2,
+#endif
+#ifdef HAVE_X11_xcb_dri3
+    test_xcb_dri3,
+#endif
+#ifdef HAVE_X11_xcb_errors
+    test_xcb_errors,
+#endif
+#ifdef HAVE_X11_xcb_ewmh
+    test_xcb_ewmh,
+#endif
+#ifdef HAVE_X11_xcb_glx
+    test_xcb_glx,
+#endif
+#ifdef HAVE_X11_xcb_icccm
+    test_xcb_icccm,
+#endif
+#ifdef HAVE_X11_xcb_image
+    test_xcb_image,
+#endif
+#ifdef HAVE_X11_xcb_keysyms
+    test_xcb_keysyms,
+#endif
+#ifdef HAVE_X11_xcb_present
+    test_xcb_present,
+#endif
+#ifdef HAVE_X11_xcb_randr
     test_xcb_randr,
 #endif
-#ifdef HAVE_xcb_util
+#ifdef HAVE_X11_xcb_record
+    test_xcb_record,
+#endif
+#ifdef HAVE_X11_xcb_render
+    test_xcb_render,
+#endif
+#ifdef HAVE_X11_xcb_render_util
+    test_xcb_render_util,
+#endif
+#ifdef HAVE_X11_xcb_res
+    test_xcb_res,
+#endif
+#ifdef HAVE_X11_xcb_screensaver
+    test_xcb_screensaver,
+#endif
+#ifdef HAVE_X11_xcb_shape
+    test_xcb_shape,
+#endif
+#ifdef HAVE_X11_xcb_shm
+    test_xcb_shm,
+#endif
+#ifdef HAVE_X11_xcb_sync
+    test_xcb_sync,
+#endif
+#ifdef HAVE_X11_xcb_util
     test_xcb_util,
 #endif
-#ifdef HAVE_xcb_xfixes
+#ifdef HAVE_X11_xcb_xf86dri
+    test_xcb_xf86dri,
+#endif
+#ifdef HAVE_X11_xcb_xfixes
     test_xcb_xfixes,
 #endif
-
+#ifdef HAVE_X11_xcb_xinerama
+    test_xcb_xinerama,
+#endif
+#ifdef HAVE_X11_xcb_xinput
+    test_xcb_xinput,
+#endif
+#ifdef HAVE_X11_xcb_xkb
+    test_xcb_xkb,
+#endif
+#ifdef HAVE_X11_xcb_xrm
+    test_xcb_xrm,
+#endif
+#ifdef HAVE_X11_xcb_xtest
+    test_xcb_xtest,
+#endif
+#ifdef HAVE_X11_xcb_xvmc
+    test_xcb_xvmc,
+#endif
+#ifdef HAVE_X11_xcb_xv
+    test_xcb_xv,
+#endif
     NULL,
   };
 
@@ -514,5 +1015,6 @@
   // always 1 in the test harness which always returns the sentinel at the end
   // of the array. The array logic is there to ensure that the contents of
   // `fptrs` is not optimized out.
+#pragma GCC diagnostic ignored "-Wpointer-to-int-cast"
   return (int)fptrs[(sizeof(fptrs) / sizeof(*fptrs)) - argc];
 }
diff --git a/Tests/FindXalanC/Test/CMakeLists.txt b/Tests/FindXalanC/Test/CMakeLists.txt
index a8c2a0a..eb45802 100644
--- a/Tests/FindXalanC/Test/CMakeLists.txt
+++ b/Tests/FindXalanC/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindXalanC CXX)
 include(CTest)
 
diff --git a/Tests/FindXercesC/Test/CMakeLists.txt b/Tests/FindXercesC/Test/CMakeLists.txt
index 267c6a9..38ae6e4 100644
--- a/Tests/FindXercesC/Test/CMakeLists.txt
+++ b/Tests/FindXercesC/Test/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(TestFindXercesC CXX)
 include(CTest)
 
diff --git a/Tests/FindwxWidgets/CMakeLists.txt b/Tests/FindwxWidgets/CMakeLists.txt
new file mode 100644
index 0000000..b66f838
--- /dev/null
+++ b/Tests/FindwxWidgets/CMakeLists.txt
@@ -0,0 +1,10 @@
+add_test(NAME FindwxWidgets.Test COMMAND
+  ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>
+  --build-and-test
+  "${CMake_SOURCE_DIR}/Tests/FindwxWidgets/Test"
+  "${CMake_BINARY_DIR}/Tests/FindwxWidgets/Test"
+  ${build_generator_args}
+  --build-project TestFindwxWidgets
+  --build-options ${build_options}
+  --test-command ${CMAKE_CTEST_COMMAND} -V -C $<CONFIGURATION>
+  )
diff --git a/Tests/FindwxWidgets/Test/CMakeLists.txt b/Tests/FindwxWidgets/Test/CMakeLists.txt
new file mode 100644
index 0000000..ecc9c36
--- /dev/null
+++ b/Tests/FindwxWidgets/Test/CMakeLists.txt
@@ -0,0 +1,17 @@
+cmake_minimum_required(VERSION 3.26)
+project(TestFindwxWidgets CXX)
+include(CTest)
+
+find_package(wxWidgets REQUIRED)
+
+add_executable(test_tgt main.cpp)
+target_link_libraries(test_tgt wxWidgets::wxWidgets)
+add_test(NAME test_tgt COMMAND test_tgt)
+
+add_executable(test_var main.cpp)
+target_link_libraries(test_var PRIVATE ${wxWidgets_LIBRARIES})
+target_link_directories(test_var PRIVATE ${wxWidgets_LIBRARY_DIRS})
+target_include_directories(test_var PRIVATE ${wxWidgets_INCLUDE_DIRS})
+target_compile_options(test_var PRIVATE ${wxWidgets_CONFIG_OPTIONS})
+target_compile_definitions(test_var PRIVATE ${wxWidgets_DEFINITIONS})
+add_test(NAME test_var COMMAND test_var)
diff --git a/Tests/FindwxWidgets/Test/main.cpp b/Tests/FindwxWidgets/Test/main.cpp
new file mode 100644
index 0000000..0e576cf
--- /dev/null
+++ b/Tests/FindwxWidgets/Test/main.cpp
@@ -0,0 +1,7 @@
+#include <wx/filefn.h>
+
+int main()
+{
+  wxGetCwd();
+  return 0;
+}
diff --git a/Tests/Fortran/CMakeLists.txt b/Tests/Fortran/CMakeLists.txt
index 0fede25..30ab16b 100644
--- a/Tests/Fortran/CMakeLists.txt
+++ b/Tests/Fortran/CMakeLists.txt
@@ -49,7 +49,7 @@
   FortranCInterface_VERIFY()
   FortranCInterface_VERIFY(CXX)
   if(CMAKE_Fortran_COMPILER_SUPPORTS_F90)
-    if(NOT CMAKE_Fortran_COMPILER_ID MATCHES "SunPro|PathScale|Absoft|Fujitsu")
+    if(NOT CMAKE_Fortran_COMPILER_ID MATCHES "SunPro|PathScale|Absoft|Fujitsu|LCC")
       set(module_expected 1)
     endif()
     if(FortranCInterface_MODULE_FOUND OR module_expected)
diff --git a/Tests/FortranC/CMakeLists.txt b/Tests/FortranC/CMakeLists.txt
index 1403aa0..f5e056b 100644
--- a/Tests/FortranC/CMakeLists.txt
+++ b/Tests/FortranC/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(FortranC C Fortran)
 
 # Skip this test for compilers not known to be compatible.
diff --git a/Tests/FortranOnly/CMakeLists.txt b/Tests/FortranOnly/CMakeLists.txt
index fc71a18..ed2a440 100644
--- a/Tests/FortranOnly/CMakeLists.txt
+++ b/Tests/FortranOnly/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(FortranOnly Fortran)
 message("CTEST_FULL_OUTPUT ")
 
@@ -152,13 +152,16 @@
   set_property(SOURCE preprocess3.f PROPERTY Fortran_PREPROCESS ON)
 endif()
 
-# Test that neither the compiler nor CMake performs unnecessary preprocessing.
-add_library(no_preprocess_target_lower STATIC no_preprocess_target_lower.f)
-target_compile_options(no_preprocess_target_lower PRIVATE -DINTEGER=nonsense)
-set_property(TARGET no_preprocess_target_lower PROPERTY Fortran_PREPROCESS OFF)
-add_library(no_preprocess_source_lower STATIC no_preprocess_source_lower.f)
-target_compile_options(no_preprocess_source_lower PRIVATE -DINTEGER=nonsense)
-set_property(SOURCE no_preprocess_source_lower.f PROPERTY Fortran_PREPROCESS OFF)
+# LCC < 1.24 has no way to disable Fortran preprocessor
+if(NOT CMAKE_Fortran_COMPILER_ID STREQUAL "LCC" OR CMAKE_Fortran_COMPILER_VERSION VERSION_GREATER_EQUAL "1.24.00")
+  # Test that neither the compiler nor CMake performs unnecessary preprocessing.
+  add_library(no_preprocess_target_lower STATIC no_preprocess_target_lower.f)
+  target_compile_options(no_preprocess_target_lower PRIVATE -DINTEGER=nonsense)
+  set_property(TARGET no_preprocess_target_lower PROPERTY Fortran_PREPROCESS OFF)
+  add_library(no_preprocess_source_lower STATIC no_preprocess_source_lower.f)
+  target_compile_options(no_preprocess_source_lower PRIVATE -DINTEGER=nonsense)
+  set_property(SOURCE no_preprocess_source_lower.f PROPERTY Fortran_PREPROCESS OFF)
+endif()
 
 # Test that we can explicitly not preprocess a target or source.
 # This will not work on certain compilers due to either missing a
diff --git a/Tests/Framework/CMakeLists.txt b/Tests/Framework/CMakeLists.txt
index 287be94..629deeb 100644
--- a/Tests/Framework/CMakeLists.txt
+++ b/Tests/Framework/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(Framework)
 
 add_library(foo SHARED
diff --git a/Tests/FunctionTest/CMakeLists.txt b/Tests/FunctionTest/CMakeLists.txt
index 0660d0f..a5a8b11 100644
--- a/Tests/FunctionTest/CMakeLists.txt
+++ b/Tests/FunctionTest/CMakeLists.txt
@@ -1,5 +1,5 @@
 # a simple C only test case
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project (FunctionTest)
 
 function(FAILED testname)
diff --git a/Tests/GeneratorExpression/CMakeLists.txt b/Tests/GeneratorExpression/CMakeLists.txt
index 3fb53d1..ef115e6 100644
--- a/Tests/GeneratorExpression/CMakeLists.txt
+++ b/Tests/GeneratorExpression/CMakeLists.txt
@@ -200,6 +200,21 @@
 set_property(TARGET importedFallback PROPERTY MAP_IMPORTED_CONFIG_DEBUG "" DEBUG)
 set_property(TARGET importedFallback PROPERTY MAP_IMPORTED_CONFIG_RELEASE "")
 
+add_library(importedFallback2 SHARED IMPORTED)
+set_property(TARGET importedFallback2 PROPERTY ENABLE_EXPORTS TRUE)
+set_property(TARGET importedFallback2 PROPERTY IMPORTED_LOCATION_NOCONFIG noconfig_loc)
+set_property(TARGET importedFallback2 PROPERTY IMPORTED_IMPLIB_NOCONFIG noconfig_imp)
+set_property(TARGET importedFallback2 PROPERTY IMPORTED_LOCATION_DEBUG debug_loc)
+set_property(TARGET importedFallback2 PROPERTY IMPORTED_IMPLIB_DEBUG debug_imp)
+set_property(TARGET importedFallback2 PROPERTY IMPORTED_LOCATION_RELEASE release_loc)
+set_property(TARGET importedFallback2 PROPERTY IMPORTED_IMPLIB_RELEASE release_imp)
+set_property(TARGET importedFallback2 PROPERTY IMPORTED_LOCATION fallback_loc)
+set_property(TARGET importedFallback2 PROPERTY IMPORTED_IMPLIB fallback_imp)
+set_property(TARGET importedFallback2 PROPERTY IMPORTED_IMPLIB_SPECIAL special_imp)
+set_property(TARGET importedFallback2 PROPERTY MAP_IMPORTED_CONFIG_NOCONFIG SPECIAL "")
+set_property(TARGET importedFallback2 PROPERTY MAP_IMPORTED_CONFIG_DEBUG SPECIAL "")
+set_property(TARGET importedFallback2 PROPERTY MAP_IMPORTED_CONFIG_RELEASE SPECIAL "")
+
 add_library(importedFallback_genex STATIC IMPORTED)
 set_property(TARGET importedFallback_genex PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
 set_property(TARGET importedFallback_genex PROPERTY IMPORTED_LOCATION_RELEASE release_loc)
@@ -217,6 +232,7 @@
     -Dconfig=$<CONFIGURATION>
     -Dtest_imported_includes=$<TARGET_PROPERTY:imported4,INCLUDE_DIRECTORIES>
     -Dtest_imported_fallback=$<STREQUAL:$<TARGET_FILE_NAME:importedFallback>,fallback_loc>
+    -Dtest_imported_fallback2=$<IF:$<OR:$<PLATFORM_ID:Windows,CYGWIN,MSYS>,$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${CMAKE_TAPI}>>>,$<STREQUAL:$<TARGET_LINKER_FILE_NAME:importedFallback2>,special_imp>,$<STREQUAL:$<TARGET_LINKER_FILE_NAME:importedFallback2>,fallback_loc>>
     -Dtest_imported_fallback_genex=$<STREQUAL:$<TARGET_PROPERTY:importedFallback_genex,INTERFACE_COMPILE_DEFINITIONS>,FOOBAR=1>
     -Dtest_alias_file_exe=$<STREQUAL:$<TARGET_FILE:Alias::SomeExe>,$<TARGET_FILE:someexe>>
     -Dtest_alias_file_lib=$<STREQUAL:$<TARGET_FILE:Alias::SomeLib>,$<TARGET_FILE:empty1>>
diff --git a/Tests/GeneratorExpression/check-part3.cmake b/Tests/GeneratorExpression/check-part3.cmake
index e1b1f93..7bb0d85 100644
--- a/Tests/GeneratorExpression/check-part3.cmake
+++ b/Tests/GeneratorExpression/check-part3.cmake
@@ -19,6 +19,7 @@
 endif()
 
 check(test_imported_fallback "1")
+check(test_imported_fallback2 "1")
 check(test_imported_fallback_genex "1")
 
 check(test_alias_file_exe "1")
diff --git a/Tests/ISPC/ChainedStaticLibraries/CMakeLists.txt b/Tests/ISPC/ChainedStaticLibraries/CMakeLists.txt
index 9a255a0..a09f638 100644
--- a/Tests/ISPC/ChainedStaticLibraries/CMakeLists.txt
+++ b/Tests/ISPC/ChainedStaticLibraries/CMakeLists.txt
@@ -11,10 +11,15 @@
 add_library(ispc_objects2 STATIC simple.ispc)
 
 set_target_properties(ispc_objects1 PROPERTIES POSITION_INDEPENDENT_CODE ON)
-set_target_properties(ispc_objects1 PROPERTIES ISPC_INSTRUCTION_SETS "sse2-i32x4;avx1-i32x16;avx2-i32x4")
-
 set_target_properties(ispc_objects2 PROPERTIES POSITION_INDEPENDENT_CODE ON)
-set_target_properties(ispc_objects2 PROPERTIES ISPC_INSTRUCTION_SETS "sse2-i32x4")
+
+if("${CMAKE_SYSTEM_NAME};${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "Darwin;arm64")
+  set_property(TARGET ispc_objects1 PROPERTY ISPC_INSTRUCTION_SETS "neon-i32x4")
+  set_property(TARGET ispc_objects2 PROPERTY ISPC_INSTRUCTION_SETS "neon-i32x4")
+else()
+  set_property(TARGET ispc_objects1 PROPERTY ISPC_INSTRUCTION_SETS "sse2-i32x4;avx1-i32x16;avx2-i32x4")
+  set_property(TARGET ispc_objects2 PROPERTY ISPC_INSTRUCTION_SETS "sse2-i32x4")
+endif()
 
 target_link_libraries(ispc_objects2 PRIVATE ispc_objects1)
 
diff --git a/Tests/ISPC/CustomHeaderSuffix/CMakeLists.txt b/Tests/ISPC/CustomHeaderSuffix/CMakeLists.txt
index d20f88e..4cf5ca1 100644
--- a/Tests/ISPC/CustomHeaderSuffix/CMakeLists.txt
+++ b/Tests/ISPC/CustomHeaderSuffix/CMakeLists.txt
@@ -6,7 +6,11 @@
   set(CMAKE_ISPC_FLAGS "--arch=x86")
 endif()
 
-set(CMAKE_ISPC_INSTRUCTION_SETS "sse2-i32x4;sse4-i8x16")
+if("${CMAKE_SYSTEM_NAME};${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "Darwin;arm64")
+  set(CMAKE_ISPC_INSTRUCTION_SETS "neon-i32x4")
+else()
+  set(CMAKE_ISPC_INSTRUCTION_SETS "sse2-i32x4;sse4-i8x16")
+endif()
 
 set(CMAKE_ISPC_HEADER_SUFFIX ".ispc.h")
 
diff --git a/Tests/ISPC/Defines/CMakeLists.txt b/Tests/ISPC/Defines/CMakeLists.txt
index 7645804..2b99469 100644
--- a/Tests/ISPC/Defines/CMakeLists.txt
+++ b/Tests/ISPC/Defines/CMakeLists.txt
@@ -1,7 +1,12 @@
 cmake_minimum_required(VERSION 3.18)
 project(ISPCDefines CXX ISPC)
 
-set(CMAKE_ISPC_INSTRUCTION_SETS "sse2-i32x4;sse4-i16x8;avx1-i32x16;avx2-i32x4;avx512knl-i32x16;avx512skx-i32x8")
+if("${CMAKE_SYSTEM_NAME};${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "Darwin;arm64")
+  set(CMAKE_ISPC_INSTRUCTION_SETS "neon-i32x4")
+else()
+  set(CMAKE_ISPC_INSTRUCTION_SETS "sse2-i32x4;sse4-i16x8;avx1-i32x16;avx2-i32x4;avx512knl-i32x16;avx512skx-i32x8")
+endif()
+
 set(CMAKE_ISPC_FLAGS -DM_PI=3.1415926535f)
 add_compile_definitions([==[STRUCT_DEFINE=struct{uniform int a]==])
 
diff --git a/Tests/ISPC/DynamicLibrary/CMakeLists.txt b/Tests/ISPC/DynamicLibrary/CMakeLists.txt
index 4655090..516cdc1 100644
--- a/Tests/ISPC/DynamicLibrary/CMakeLists.txt
+++ b/Tests/ISPC/DynamicLibrary/CMakeLists.txt
@@ -13,8 +13,13 @@
 
 set_target_properties(ispc_objects1 PROPERTIES POSITION_INDEPENDENT_CODE ON)
 
-set_target_properties(ispc_objects1 PROPERTIES ISPC_INSTRUCTION_SETS "sse2-i32x4;avx1-i32x16;avx2-i32x4")
-set_target_properties(ispc_objects2 PROPERTIES ISPC_INSTRUCTION_SETS "sse2-i32x4")
+if("${CMAKE_SYSTEM_NAME};${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "Darwin;arm64")
+  set_property(TARGET ispc_objects1 PROPERTY ISPC_INSTRUCTION_SETS "neon-i32x4")
+  set_property(TARGET ispc_objects2 PROPERTY ISPC_INSTRUCTION_SETS "neon-i32x4")
+else()
+  set_property(TARGET ispc_objects1 PROPERTY ISPC_INSTRUCTION_SETS "sse2-i32x4;avx1-i32x16;avx2-i32x4")
+  set_property(TARGET ispc_objects2 PROPERTY ISPC_INSTRUCTION_SETS "sse2-i32x4")
+endif()
 
 target_link_libraries(ispc_objects2 PUBLIC ispc_objects1)
 
diff --git a/Tests/ISPC/ObjectGenex/CMakeLists.txt b/Tests/ISPC/ObjectGenex/CMakeLists.txt
index bc0cbf6..5e64004 100644
--- a/Tests/ISPC/ObjectGenex/CMakeLists.txt
+++ b/Tests/ISPC/ObjectGenex/CMakeLists.txt
@@ -1,7 +1,14 @@
 cmake_minimum_required(VERSION 3.18)
 project(ISPCObjectGenex CXX ISPC)
 
-set(CMAKE_ISPC_INSTRUCTION_SETS "sse2-i32x4;sse4-i16x8;avx1-i32x16;avx2-i32x4;avx512knl-i32x16;avx512skx-i32x8")
+if("${CMAKE_SYSTEM_NAME};${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "Darwin;arm64")
+  set(CMAKE_ISPC_INSTRUCTION_SETS "neon-i32x4")
+  set(numberOfTargets 1)
+else()
+  set(CMAKE_ISPC_INSTRUCTION_SETS "sse2-i32x4;sse4-i16x8;avx1-i32x16;avx2-i32x4;avx512knl-i32x16;avx512skx-i32x8")
+  list(LENGTH CMAKE_ISPC_INSTRUCTION_SETS numberOfTargets)
+  math(EXPR numberOfTargets "${numberOfTargets}+1")
+endif()
 
 add_library(ispc_objects OBJECT
   simple.ispc
@@ -35,8 +42,6 @@
 add_executable(ISPCObjectGenex main.cxx)
 add_dependencies(ISPCObjectGenex ispc_objects)
 
-list(LENGTH CMAKE_ISPC_INSTRUCTION_SETS numberOfTargets)
-math(EXPR numberOfTargets "${numberOfTargets}+1")
 target_compile_definitions(ISPCObjectGenex PRIVATE
     "ExpectedISPCObjects=${numberOfTargets}"
     "CONFIG_TYPE=gen_$<LOWER_CASE:$<CONFIG>>"
diff --git a/Tests/ISPC/ObjectLibrary/CMakeLists.txt b/Tests/ISPC/ObjectLibrary/CMakeLists.txt
index a4c81a9..60ce920 100644
--- a/Tests/ISPC/ObjectLibrary/CMakeLists.txt
+++ b/Tests/ISPC/ObjectLibrary/CMakeLists.txt
@@ -11,8 +11,12 @@
 add_library(ispc_objects OBJECT simple.ispc subdir/extra.ispc)
 
 set_target_properties(ispc_objects PROPERTIES POSITION_INDEPENDENT_CODE ON)
-set_target_properties(ispc_objects PROPERTIES ISPC_INSTRUCTION_SETS "sse2-i32x4;sse4-i8x16")
 
+if("${CMAKE_SYSTEM_NAME};${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "Darwin;arm64")
+  set_property(TARGET ispc_objects PROPERTY ISPC_INSTRUCTION_SETS "neon-i32x4")
+else()
+  set_property(TARGET ispc_objects PROPERTY ISPC_INSTRUCTION_SETS "sse2-i32x4;sse4-i8x16")
+endif()
 
 add_executable(ISPCObjectLibrary main.cxx extra.cxx)
 target_link_libraries(ISPCObjectLibrary PRIVATE ispc_objects)
diff --git a/Tests/ISPC/ResponseAndDefine/CMakeLists.txt b/Tests/ISPC/ResponseAndDefine/CMakeLists.txt
index 7539209..d29cb2a 100644
--- a/Tests/ISPC/ResponseAndDefine/CMakeLists.txt
+++ b/Tests/ISPC/ResponseAndDefine/CMakeLists.txt
@@ -14,13 +14,15 @@
 set_target_properties(ISPCResponseAndDefine PROPERTIES POSITION_INDEPENDENT_CODE ON)
 target_include_directories(ISPCResponseAndDefine PRIVATE  "${CMAKE_CURRENT_BINARY_DIR}")
 
-target_compile_options(ISPCResponseAndDefine PRIVATE "$<$<COMPILE_LANGUAGE:ISPC>:--target=sse2-i32x4>")
-if(CMAKE_SIZEOF_VOID_P EQUAL 4)
-  target_compile_options(ISPCResponseAndDefine PRIVATE "$<$<COMPILE_LANGUAGE:ISPC>:--arch=x86>")
+if("${CMAKE_SYSTEM_NAME};${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "Darwin;arm64")
+  target_compile_options(ISPCResponseAndDefine PRIVATE "$<$<COMPILE_LANGUAGE:ISPC>:--target=neon-i32x4>")
+else()
+  target_compile_options(ISPCResponseAndDefine PRIVATE "$<$<COMPILE_LANGUAGE:ISPC>:--target=sse2-i32x4>")
+  if(CMAKE_SIZEOF_VOID_P EQUAL 4)
+    target_compile_options(ISPCResponseAndDefine PRIVATE "$<$<COMPILE_LANGUAGE:ISPC>:--arch=x86>")
+  endif()
 endif()
 
-
-
 target_compile_definitions(ISPCResponseAndDefine PRIVATE
   "$<$<COMPILE_LANGUAGE:ISPC>:STRUCT_DEFINE=struct{uniform int a>;M_PI=3.14159f")
 target_include_directories(ISPCResponseAndDefine PRIVATE
diff --git a/Tests/ISPC/StaticLibrary/CMakeLists.txt b/Tests/ISPC/StaticLibrary/CMakeLists.txt
index ebe5960..76f78e5 100644
--- a/Tests/ISPC/StaticLibrary/CMakeLists.txt
+++ b/Tests/ISPC/StaticLibrary/CMakeLists.txt
@@ -4,9 +4,13 @@
 
 add_library(ispc_objects STATIC simple.ispc)
 
-target_compile_options(ispc_objects PRIVATE "$<$<COMPILE_LANGUAGE:ISPC>:--target=sse2-i32x4>")
-if(CMAKE_SIZEOF_VOID_P EQUAL 4)
-  target_compile_options(ispc_objects PRIVATE "$<$<COMPILE_LANGUAGE:ISPC>:--arch=x86>")
+if("${CMAKE_SYSTEM_NAME};${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "Darwin;arm64")
+  target_compile_options(ispc_objects PRIVATE "$<$<COMPILE_LANGUAGE:ISPC>:--target=neon-i32x4>")
+else()
+  target_compile_options(ispc_objects PRIVATE "$<$<COMPILE_LANGUAGE:ISPC>:--target=sse2-i32x4>")
+  if(CMAKE_SIZEOF_VOID_P EQUAL 4)
+    target_compile_options(ispc_objects PRIVATE "$<$<COMPILE_LANGUAGE:ISPC>:--arch=x86>")
+  endif()
 endif()
 
 set_target_properties(ispc_objects PROPERTIES POSITION_INDEPENDENT_CODE ON)
diff --git a/Tests/ISPC/SystemIncludes/CMakeLists.txt b/Tests/ISPC/SystemIncludes/CMakeLists.txt
index d94e75e..3b4c289 100644
--- a/Tests/ISPC/SystemIncludes/CMakeLists.txt
+++ b/Tests/ISPC/SystemIncludes/CMakeLists.txt
@@ -7,8 +7,11 @@
 set_target_properties(ISPCSystemIncludes PROPERTIES ISPC_HEADER_SUFFIX ".ispc.h")
 target_include_directories(ISPCSystemIncludes SYSTEM PRIVATE "${CMAKE_CURRENT_BINARY_DIR}")
 
-
-target_compile_options(ISPCSystemIncludes PRIVATE "$<$<COMPILE_LANGUAGE:ISPC>:--target=sse2-i32x4>")
-if(CMAKE_SIZEOF_VOID_P EQUAL 4)
-  target_compile_options(ISPCSystemIncludes PRIVATE "$<$<COMPILE_LANGUAGE:ISPC>:--arch=x86>")
+if("${CMAKE_SYSTEM_NAME};${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "Darwin;arm64")
+  target_compile_options(ISPCSystemIncludes PRIVATE "$<$<COMPILE_LANGUAGE:ISPC>:--target=neon-i32x4>")
+else()
+  target_compile_options(ISPCSystemIncludes PRIVATE "$<$<COMPILE_LANGUAGE:ISPC>:--target=sse2-i32x4>")
+  if(CMAKE_SIZEOF_VOID_P EQUAL 4)
+    target_compile_options(ISPCSystemIncludes PRIVATE "$<$<COMPILE_LANGUAGE:ISPC>:--arch=x86>")
+  endif()
 endif()
diff --git a/Tests/IncludeDirectories/SystemIncludeDirectories/CMakeLists.txt b/Tests/IncludeDirectories/SystemIncludeDirectories/CMakeLists.txt
index e4973b0..d4720be 100644
--- a/Tests/IncludeDirectories/SystemIncludeDirectories/CMakeLists.txt
+++ b/Tests/IncludeDirectories/SystemIncludeDirectories/CMakeLists.txt
@@ -1,5 +1,5 @@
 
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(SystemIncludeDirectories)
 
diff --git a/Tests/IncludeDirectories/TargetIncludeDirectories/CMakeLists.txt b/Tests/IncludeDirectories/TargetIncludeDirectories/CMakeLists.txt
index 3b994a2..6812267 100644
--- a/Tests/IncludeDirectories/TargetIncludeDirectories/CMakeLists.txt
+++ b/Tests/IncludeDirectories/TargetIncludeDirectories/CMakeLists.txt
@@ -1,5 +1,5 @@
 
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(TargetIncludeDirectories)
 
diff --git a/Tests/InterfaceLibrary/CMakeLists.txt b/Tests/InterfaceLibrary/CMakeLists.txt
index a302c7c..d57eccc 100644
--- a/Tests/InterfaceLibrary/CMakeLists.txt
+++ b/Tests/InterfaceLibrary/CMakeLists.txt
@@ -1,5 +1,5 @@
 
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(InterfaceLibrary)
 
diff --git a/Tests/JCTest/CMakeLists.txt b/Tests/JCTest/CMakeLists.txt
index b120640..adbdf9a 100644
--- a/Tests/JCTest/CMakeLists.txt
+++ b/Tests/JCTest/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(TestTime)
 enable_testing()
 add_executable(TestTime TestTime.cxx)
diff --git a/Tests/Java/CMakeLists.txt b/Tests/Java/CMakeLists.txt
index 1d8d7ac..c1c6817 100644
--- a/Tests/Java/CMakeLists.txt
+++ b/Tests/Java/CMakeLists.txt
@@ -1,6 +1,6 @@
 project(hello Java)
 
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 set(CMAKE_VERBOSE_MAKEFILE 1)
 
 include(CTest)
diff --git a/Tests/JavaJavah/CMakeLists.txt b/Tests/JavaJavah/CMakeLists.txt
index b56cc21..06fc06a 100644
--- a/Tests/JavaJavah/CMakeLists.txt
+++ b/Tests/JavaJavah/CMakeLists.txt
@@ -1,6 +1,6 @@
 project(helloJavah Java CXX)
 
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 set(CMAKE_VERBOSE_MAKEFILE 1)
 
 include(CTest)
diff --git a/Tests/JavaNativeHeaders/CMakeLists.txt b/Tests/JavaNativeHeaders/CMakeLists.txt
index 2471e01..8a2e460 100644
--- a/Tests/JavaNativeHeaders/CMakeLists.txt
+++ b/Tests/JavaNativeHeaders/CMakeLists.txt
@@ -1,6 +1,6 @@
 project(helloJavaNativeHeaders Java CXX)
 
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 set(CMAKE_VERBOSE_MAKEFILE 1)
 
 include (CTest)
diff --git a/Tests/LinkDirectory/CMakeLists.txt b/Tests/LinkDirectory/CMakeLists.txt
index d9a8ac8..2c2e488 100644
--- a/Tests/LinkDirectory/CMakeLists.txt
+++ b/Tests/LinkDirectory/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(LinkDirectory C)
 
 # Put the subproject source tree in our build tree so it can refer to
diff --git a/Tests/LinkDirectory/External/CMakeLists.txt b/Tests/LinkDirectory/External/CMakeLists.txt
index e222929..431fd89 100644
--- a/Tests/LinkDirectory/External/CMakeLists.txt
+++ b/Tests/LinkDirectory/External/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(LinkDirectoryExternal C)
 
 
diff --git a/Tests/LinkFlags/CMakeLists.txt b/Tests/LinkFlags/CMakeLists.txt
index 31ff9b5..c25c4b2 100644
--- a/Tests/LinkFlags/CMakeLists.txt
+++ b/Tests/LinkFlags/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(LinkFlags C)
 
 string(TOUPPER "${TEST_CONFIG}" TEST_CONFIG_UPPER)
@@ -32,6 +32,11 @@
 set_property(TARGET LinkFlags_exe_config PROPERTY LINK_FLAGS_${TEST_CONFIG_UPPER} ${pre}BADFLAG_${TEST_CONFIG}${obj})
 
 add_executable(LinkFlags LinkFlags.c)
+if("x${CMAKE_C_COMPILER_ID}" STREQUAL "xMSVC")
+  set_property(TARGET LinkFlags PROPERTY
+    LINK_FLAGS "/NODEFAULTLIB:\"libcdg.lib\" /NODEFAULTLIB:\"libcmtg.lib\" /NODEFAULTLIB:\"foomsvcrt.lib\" /NODEFAULTLIB:\"libbar.lib\" /NODEFAULTLIB:\"libfooba.lib\""
+    )
+endif()
 
 add_subdirectory(LinkerFlags)
 add_subdirectory(LinkerFlagsConfig)
diff --git a/Tests/LoadCommand/CMakeCommands/CMakeLists.txt b/Tests/LoadCommand/CMakeCommands/CMakeLists.txt
index cafa99b..3313c57 100644
--- a/Tests/LoadCommand/CMakeCommands/CMakeLists.txt
+++ b/Tests/LoadCommand/CMakeCommands/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(CMAKE_LOADED_COMMANDS)
 
 if (MUDSLIDE_TYPE MATCHES MUCHO)
diff --git a/Tests/LoadCommand/CMakeLists.txt b/Tests/LoadCommand/CMakeLists.txt
index e1c4998..c0dc247 100644
--- a/Tests/LoadCommand/CMakeLists.txt
+++ b/Tests/LoadCommand/CMakeLists.txt
@@ -1,4 +1,5 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
+cmake_policy(SET CMP0031 OLD) # testing the old behavior
 project(LoadCommand)
 
 # set a definition
diff --git a/Tests/LoadCommandOneConfig/CMakeCommands/CMakeLists.txt b/Tests/LoadCommandOneConfig/CMakeCommands/CMakeLists.txt
index dc029a4..74a1f55 100644
--- a/Tests/LoadCommandOneConfig/CMakeCommands/CMakeLists.txt
+++ b/Tests/LoadCommandOneConfig/CMakeCommands/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(CMAKE_LOADED_COMMANDS)
 
 if (MUDSLIDE_TYPE MATCHES MUCHO)
diff --git a/Tests/LoadCommandOneConfig/CMakeLists.txt b/Tests/LoadCommandOneConfig/CMakeLists.txt
index fef4bb7..35dc0fe 100644
--- a/Tests/LoadCommandOneConfig/CMakeLists.txt
+++ b/Tests/LoadCommandOneConfig/CMakeLists.txt
@@ -1,4 +1,5 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
+cmake_policy(SET CMP0031 OLD) # testing the old behavior
 project(LoadCommand)
 
 # set a definition
diff --git a/Tests/MFC/CMakeLists.txt b/Tests/MFC/CMakeLists.txt
index d17b955..3f78a81 100644
--- a/Tests/MFC/CMakeLists.txt
+++ b/Tests/MFC/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(mfc_driver)
 
 include(CTest)
diff --git a/Tests/MFC/CMakeLists.txt.in b/Tests/MFC/CMakeLists.txt.in
index a600c63..bae3d2f 100644
--- a/Tests/MFC/CMakeLists.txt.in
+++ b/Tests/MFC/CMakeLists.txt.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(mfc1)
 
 macro(replace_flags var these those)
diff --git a/Tests/MFC/try_compile/CMakeLists.txt b/Tests/MFC/try_compile/CMakeLists.txt
index 768d2a6..d22b8b6 100644
--- a/Tests/MFC/try_compile/CMakeLists.txt
+++ b/Tests/MFC/try_compile/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(try_compile_mfc)
 
 set(files
diff --git a/Tests/MSManifest/Subdir/CMakeLists.txt b/Tests/MSManifest/Subdir/CMakeLists.txt
index 3b4fccc..68c66fe 100644
--- a/Tests/MSManifest/Subdir/CMakeLists.txt
+++ b/Tests/MSManifest/Subdir/CMakeLists.txt
@@ -5,6 +5,11 @@
   add_test(NAME MSManifest.Single COMMAND
     ${CMAKE_COMMAND} -Dexe=$<TARGET_FILE:MSManifest>
     -P ${CMAKE_CURRENT_SOURCE_DIR}/check.cmake)
+  add_executable(MSManifestNonIncremental main.c ${CMAKE_CURRENT_BINARY_DIR}/test.manifest)
+  set_property(TARGET MSManifestNonIncremental PROPERTY LINK_FLAGS "/INCREMENTAL:NO")
+  add_test(NAME MSManifest.Single.NonIncremental COMMAND
+    ${CMAKE_COMMAND} -Dexe=$<TARGET_FILE:MSManifestNonIncremental>
+    -P ${CMAKE_CURRENT_SOURCE_DIR}/check.cmake)
   add_executable(MSManifestNone main.c)
   set_property(TARGET MSManifestNone PROPERTY LINK_FLAGS "/MANIFEST:NO")
 elseif(WIN32 AND CMAKE_C_COMPILER_ID MATCHES "Clang")
diff --git a/Tests/MSManifest/Subdir2/CMakeLists.txt b/Tests/MSManifest/Subdir2/CMakeLists.txt
index 0d960ad..bbc70dc 100644
--- a/Tests/MSManifest/Subdir2/CMakeLists.txt
+++ b/Tests/MSManifest/Subdir2/CMakeLists.txt
@@ -10,4 +10,14 @@
   add_test(NAME MSManifest.Multiple COMMAND
     ${CMAKE_COMMAND} -Dexe=$<TARGET_FILE:MSMultipleManifest>
     -P ${CMAKE_CURRENT_SOURCE_DIR}/check.cmake)
+  if(MSVC AND NOT MSVC_VERSION LESS 1400)
+    add_executable(MSMultipleManifestNonIncremental main.c
+      ${CMAKE_CURRENT_BINARY_DIR}/test_manifest1.manifest
+      ${CMAKE_CURRENT_BINARY_DIR}/test_manifest2.manifest
+      ${CMAKE_CURRENT_BINARY_DIR}/test_manifest3.manifest)
+    set_property(TARGET MSMultipleManifestNonIncremental PROPERTY LINK_FLAGS "/INCREMENTAL:NO")
+    add_test(NAME MSManifest.Multiple.NonIncremental COMMAND
+      ${CMAKE_COMMAND} -Dexe=$<TARGET_FILE:MSMultipleManifestNonIncremental>
+      -P ${CMAKE_CURRENT_SOURCE_DIR}/check.cmake)
+  endif()
 endif()
diff --git a/Tests/MacRuntimePath/A/CMakeLists.txt b/Tests/MacRuntimePath/A/CMakeLists.txt
index c9d3f2c..7af746c 100644
--- a/Tests/MacRuntimePath/A/CMakeLists.txt
+++ b/Tests/MacRuntimePath/A/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(MacRuntimePath_A)
 
 # a shared library
diff --git a/Tests/MacRuntimePath/B/CMakeLists.txt b/Tests/MacRuntimePath/B/CMakeLists.txt
index 85598c4..e88433c 100644
--- a/Tests/MacRuntimePath/B/CMakeLists.txt
+++ b/Tests/MacRuntimePath/B/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(MacRuntimePath_B)
 
 include(${MacRuntimePath_B_BINARY_DIR}/../Root/lib/exp.cmake)
diff --git a/Tests/MakeClean/CMakeLists.txt b/Tests/MakeClean/CMakeLists.txt
index 809d65b..b7b9602 100644
--- a/Tests/MakeClean/CMakeLists.txt
+++ b/Tests/MakeClean/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(MakeClean)
 
 # Build the to-clean project.
diff --git a/Tests/MissingSourceFile/CMakeLists.txt b/Tests/MissingSourceFile/CMakeLists.txt
index b4f0033..f4fd8b0 100644
--- a/Tests/MissingSourceFile/CMakeLists.txt
+++ b/Tests/MissingSourceFile/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(MissingSourceFile C)
 add_executable(MissingSourceFile DoesNotExist/MissingSourceFile.c)
diff --git a/Tests/Module/ExternalData/Data1Check.cmake b/Tests/Module/ExternalData/Data1Check.cmake
index f60c209..7fe4389 100644
--- a/Tests/Module/ExternalData/Data1Check.cmake
+++ b/Tests/Module/ExternalData/Data1Check.cmake
@@ -1,24 +1,24 @@
 file(STRINGS "${Data}" lines LIMIT_INPUT 1024)
 if(NOT "x${lines}" STREQUAL "xInput file already transformed.")
-  message(SEND_ERROR "Input file:\n  ${Data}\ndoes not have expected content, but [[${lines}]]")
+  message(SEND_ERROR "Input file:\n  ${Data}\n" "does not have expected content, but [[${lines}]]")
 endif()
 if(DEFINED DataSpace)
   file(STRINGS "${DataSpace}" lines LIMIT_INPUT 1024)
   if(NOT "x${lines}" STREQUAL "xInput file already transformed.")
-    message(SEND_ERROR "Input file:\n  ${DataSpace}\ndoes not have expected content, but [[${lines}]]")
+    message(SEND_ERROR "Input file:\n  ${DataSpace}\n" "does not have expected content, but [[${lines}]]")
   endif()
 endif()
 file(STRINGS "${DataScript}" lines LIMIT_INPUT 1024)
 if(NOT "x${lines}" STREQUAL "xDataScript")
-  message(SEND_ERROR "Input file:\n  ${DataScript}\ndoes not have expected content, but [[${lines}]]")
+  message(SEND_ERROR "Input file:\n  ${DataScript}\n" "does not have expected content, but [[${lines}]]")
 endif()
 file(STRINGS "${DataAlgoMapA}" lines LIMIT_INPUT 1024)
 if(NOT "x${lines}" STREQUAL "xDataAlgoMap")
-  message(SEND_ERROR "Input file:\n  ${DataAlgoMapA}\ndoes not have expected content, but [[${lines}]]")
+  message(SEND_ERROR "Input file:\n  ${DataAlgoMapA}\n" "does not have expected content, but [[${lines}]]")
 endif()
 file(STRINGS "${DataAlgoMapB}" lines LIMIT_INPUT 1024)
 if(NOT "x${lines}" STREQUAL "xDataAlgoMap")
-  message(SEND_ERROR "Input file:\n  ${DataAlgoMapB}\ndoes not have expected content, but [[${lines}]]")
+  message(SEND_ERROR "Input file:\n  ${DataAlgoMapB}\n" "does not have expected content, but [[${lines}]]")
 endif()
 if(DataMissing)
   if(EXISTS "${DataMissing}")
@@ -54,7 +54,7 @@
   foreach(n "" ${Series${s}l})
     string(REGEX REPLACE "\\.dat$" "${n}.dat" file "${Series${s}}")
     if(NOT EXISTS "${file}")
-      message(SEND_ERROR "Input file:\n  ${file}\ndoes not exist!")
+      message(SEND_ERROR "Input file:\n  ${file}\n" "does not exist!")
     endif()
   endforeach()
 endforeach()
@@ -62,45 +62,45 @@
   foreach(n ${Series${s}l})
     string(REGEX REPLACE "${Series${s}n1}$" "${n}.dat" file "${Series${s}n}")
     if(NOT EXISTS "${file}")
-      message(SEND_ERROR "Input file:\n  ${file}\ndoes not exist!")
+      message(SEND_ERROR "Input file:\n  ${file}\n" "does not exist!")
     endif()
   endforeach()
 endforeach()
 foreach(n .1 .2 .3 .4)
   string(REGEX REPLACE "\\.1\\.dat$" "${n}.dat" file "${SeriesMixed}")
   if(NOT EXISTS "${file}")
-    message(SEND_ERROR "Input file:\n  ${file}\ndoes not exist!")
+    message(SEND_ERROR "Input file:\n  ${file}\n" "does not exist!")
   endif()
 endforeach()
 foreach(n A B)
   string(REGEX REPLACE "A\\.dat$" "${n}.dat" file "${Paired}")
   if(NOT EXISTS "${file}")
-    message(SEND_ERROR "Input file:\n  ${file}\ndoes not exist!")
+    message(SEND_ERROR "Input file:\n  ${file}\n" "does not exist!")
   endif()
 endforeach()
 foreach(n Top A B C)
   string(REGEX REPLACE "Top\\.dat$" "${n}.dat" file "${Meta}")
   if(NOT EXISTS "${file}")
-    message(SEND_ERROR "Input file:\n  ${file}\ndoes not exist!")
+    message(SEND_ERROR "Input file:\n  ${file}\n" "does not exist!")
   endif()
 endforeach()
 foreach(n A B C)
   set(file "${Directory}/${n}.dat")
   if(NOT EXISTS "${file}")
-    message(SEND_ERROR "Input file:\n  ${file}\ndoes not exist!")
+    message(SEND_ERROR "Input file:\n  ${file}\n" "does not exist!")
   endif()
 endforeach()
 foreach(n A Sub1/A Sub2/Dir/A B Sub1/B Sub2/Dir/B C Sub1/C Sub2/Dir/C)
   set(file "${DirRecurse}/${n}.dat")
   if(NOT EXISTS "${file}")
-    message(SEND_ERROR "Input file:\n  ${file}\ndoes not exist!")
+    message(SEND_ERROR "Input file:\n  ${file}\n" "does not exist!")
   endif()
 endforeach()
 list(LENGTH Semicolons len)
 if("${len}" EQUAL 2)
   foreach(file ${Semicolons})
     if(NOT EXISTS "${file}")
-      message(SEND_ERROR "Input file:\n  ${file}\ndoes not exist!")
+      message(SEND_ERROR "Input file:\n  ${file}\n" "does not exist!")
     endif()
   endforeach()
 else()
diff --git a/Tests/Module/ExternalData/Data2/Data2Check.cmake b/Tests/Module/ExternalData/Data2/Data2Check.cmake
index d5b0c7b..412593c 100644
--- a/Tests/Module/ExternalData/Data2/Data2Check.cmake
+++ b/Tests/Module/ExternalData/Data2/Data2Check.cmake
@@ -1,12 +1,12 @@
 foreach(d "${Data2}" "${Data2b}")
   file(STRINGS "${d}" lines LIMIT_INPUT 1024)
   if(NOT "x${lines}" STREQUAL "xInput file already transformed.")
-    message(SEND_ERROR "Input file:\n  ${d}\ndoes not have expected content, but [[${lines}]]")
+    message(SEND_ERROR "Input file:\n  ${d}\n" "does not have expected content, but [[${lines}]]")
   endif()
 endforeach()
 foreach(n 1 2 3)
   string(REGEX REPLACE "_1_\\.my\\.dat$" "_${n}_.my.dat" SeriesCFile "${SeriesC}")
   if(NOT EXISTS "${SeriesCFile}")
-    message(SEND_ERROR "Input file:\n  ${SeriesCFile}\ndoes not exist!")
+    message(SEND_ERROR "Input file:\n  ${SeriesCFile}\n" "does not exist!")
   endif()
 endforeach()
diff --git a/Tests/Module/ExternalData/Data3/Data3Check.cmake b/Tests/Module/ExternalData/Data3/Data3Check.cmake
index de98839..da79fdb 100644
--- a/Tests/Module/ExternalData/Data3/Data3Check.cmake
+++ b/Tests/Module/ExternalData/Data3/Data3Check.cmake
@@ -1,8 +1,8 @@
 if(NOT EXISTS "${Data}")
-  message(SEND_ERROR "Input file:\n  ${Data}\ndoes not exist!")
+  message(SEND_ERROR "Input file:\n  ${Data}\n" "does not exist!")
 endif()
 if(NOT EXISTS "${Other}")
-  message(SEND_ERROR "Input file:\n  ${Other}\ndoes not exist!")
+  message(SEND_ERROR "Input file:\n  ${Other}\n" "does not exist!")
 endif()
 # Verify that the 'Data' object was found in the second store location left
 # from Data1 target downloads and that the 'Other' object was downloaded to
diff --git a/Tests/Module/ExternalData/Data4/Data4Check.cmake b/Tests/Module/ExternalData/Data4/Data4Check.cmake
index e614cc4..a1d82d5 100644
--- a/Tests/Module/ExternalData/Data4/Data4Check.cmake
+++ b/Tests/Module/ExternalData/Data4/Data4Check.cmake
@@ -1,8 +1,8 @@
 if(NOT EXISTS "${Data}")
-  message(SEND_ERROR "Input file:\n  ${Data}\ndoes not exist!")
+  message(SEND_ERROR "Input file:\n  ${Data}\n" "does not exist!")
 endif()
 if(NOT EXISTS "${Other}")
-  message(SEND_ERROR "Input file:\n  ${Other}\ndoes not exist!")
+  message(SEND_ERROR "Input file:\n  ${Other}\n" "does not exist!")
 endif()
 # Verify that the 'Data' object was found in the second store location left
 # from Data1 target downloads and that the 'Other' object was found in the
diff --git a/Tests/Module/ExternalData/Data5/Data5Check.cmake b/Tests/Module/ExternalData/Data5/Data5Check.cmake
index 4dea9a4..79c2161 100644
--- a/Tests/Module/ExternalData/Data5/Data5Check.cmake
+++ b/Tests/Module/ExternalData/Data5/Data5Check.cmake
@@ -1,4 +1,4 @@
 file(STRINGS "${Data5}" lines LIMIT_INPUT 1024)
 if(NOT "x${lines}" STREQUAL "xInput file already transformed.")
-  message(SEND_ERROR "Input file:\n  ${Data5}\ndoes not have expected content, but [[${lines}]]")
+  message(SEND_ERROR "Input file:\n  ${Data5}\n" "does not have expected content, but [[${lines}]]")
 endif()
diff --git a/Tests/Module/ExternalData/DataNoSymlinks/DataNoSymlinksCheck.cmake b/Tests/Module/ExternalData/DataNoSymlinks/DataNoSymlinksCheck.cmake
index 2be3571..a73668a 100644
--- a/Tests/Module/ExternalData/DataNoSymlinks/DataNoSymlinksCheck.cmake
+++ b/Tests/Module/ExternalData/DataNoSymlinks/DataNoSymlinksCheck.cmake
@@ -1,5 +1,5 @@
 if(NOT EXISTS "${Data}")
-  message(SEND_ERROR "Input file:\n  ${Data}\ndoes not exist!")
+  message(SEND_ERROR "Input file:\n  ${Data}\n" "does not exist!")
 endif()
 if(IS_SYMLINK "${Data}")
   message(SEND_ERROR "Input file:\n  ${Data}\nis a symlink but should not be!")
diff --git a/Tests/ModuleDefinition/CMakeLists.txt b/Tests/ModuleDefinition/CMakeLists.txt
index 483bd8b..49577a7 100644
--- a/Tests/ModuleDefinition/CMakeLists.txt
+++ b/Tests/ModuleDefinition/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(ModuleDefinition C)
 
 # Test .def file source recognition for DLLs.
diff --git a/Tests/NewlineArgs/CMakeLists.txt b/Tests/NewlineArgs/CMakeLists.txt
index 3e4b436..c822113 100644
--- a/Tests/NewlineArgs/CMakeLists.txt
+++ b/Tests/NewlineArgs/CMakeLists.txt
@@ -1,5 +1,5 @@
 # a simple CXX only test case
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project (NewlineArgs CXX)
 
 add_definitions("-DTEST_FLAG_1
diff --git a/Tests/ObjectLibrary/CMakeLists.txt b/Tests/ObjectLibrary/CMakeLists.txt
index 06167a8..05a35bb 100644
--- a/Tests/ObjectLibrary/CMakeLists.txt
+++ b/Tests/ObjectLibrary/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(ObjectLibrary C)
 
 add_subdirectory(A)
diff --git a/Tests/ObjectLibrary/ExportLanguages/CMakeLists.txt b/Tests/ObjectLibrary/ExportLanguages/CMakeLists.txt
index fb0ebc0..f19d5a4 100644
--- a/Tests/ObjectLibrary/ExportLanguages/CMakeLists.txt
+++ b/Tests/ObjectLibrary/ExportLanguages/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(ExportLanguages CXX)
 add_library(ExportLanguagesA OBJECT a.cxx)
 add_library(ExportLanguagesB STATIC a.c $<TARGET_OBJECTS:ExportLanguagesA>)
diff --git a/Tests/ObjectLibrary/ExportLanguages/ExportLanguagesTest/CMakeLists.txt b/Tests/ObjectLibrary/ExportLanguages/ExportLanguagesTest/CMakeLists.txt
index 8544798..093aca6 100644
--- a/Tests/ObjectLibrary/ExportLanguages/ExportLanguagesTest/CMakeLists.txt
+++ b/Tests/ObjectLibrary/ExportLanguages/ExportLanguagesTest/CMakeLists.txt
@@ -1,5 +1,5 @@
 
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(ExportLanguagesTest)
 
diff --git a/Tests/OutDir/CMakeLists.txt b/Tests/OutDir/CMakeLists.txt
index 8afe036..e7bc3ab 100644
--- a/Tests/OutDir/CMakeLists.txt
+++ b/Tests/OutDir/CMakeLists.txt
@@ -7,7 +7,7 @@
     string(TOUPPER "${config}" CONFIG)
     list(APPEND configs "${CONFIG}")
   endforeach()
-  set(CMAKE_BUILD_TYPE)
+  unset(CMAKE_BUILD_TYPE CACHE)
 elseif(NOT CMAKE_BUILD_TYPE)
   set(CMAKE_BUILD_TYPE Debug)
 endif()
diff --git a/Tests/PDBDirectoryAndName/CMakeLists.txt b/Tests/PDBDirectoryAndName/CMakeLists.txt
index 5aa2459..81207bc 100644
--- a/Tests/PDBDirectoryAndName/CMakeLists.txt
+++ b/Tests/PDBDirectoryAndName/CMakeLists.txt
@@ -1,5 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
-cmake_policy(SET CMP0054 NEW)
+cmake_minimum_required(VERSION 3.5)
 project(PDBDirectoryAndName C)
 
 # Make sure the proper compiler is in use.
diff --git a/Tests/Plugin/CMakeLists.txt b/Tests/Plugin/CMakeLists.txt
index c2f43cd..a62e53f 100644
--- a/Tests/Plugin/CMakeLists.txt
+++ b/Tests/Plugin/CMakeLists.txt
@@ -1,5 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
-cmake_policy(SET CMP0054 NEW)
+cmake_minimum_required (VERSION 3.5)
 project(Plugin)
 
 # Test per-target output directory properties.
diff --git a/Tests/Plugin/PluginTest/CMakeLists.txt b/Tests/Plugin/PluginTest/CMakeLists.txt
index f00122d..4100683 100644
--- a/Tests/Plugin/PluginTest/CMakeLists.txt
+++ b/Tests/Plugin/PluginTest/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(PluginTest)
 
diff --git a/Tests/PositionIndependentTargets/CMakeLists.txt b/Tests/PositionIndependentTargets/CMakeLists.txt
index ff779d3..4f7e285 100644
--- a/Tests/PositionIndependentTargets/CMakeLists.txt
+++ b/Tests/PositionIndependentTargets/CMakeLists.txt
@@ -1,5 +1,5 @@
 
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(PositionIndependentTargets)
 
diff --git a/Tests/Preprocess/CMakeLists.txt b/Tests/Preprocess/CMakeLists.txt
index 84ca5e8..63a7b5e 100644
--- a/Tests/Preprocess/CMakeLists.txt
+++ b/Tests/Preprocess/CMakeLists.txt
@@ -1,4 +1,6 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
+cmake_policy(SET CMP0043 OLD) # testing the old behavior
+
 project(Preprocess)
 
 # This test is meant both as a test and as a reference for supported
diff --git a/Tests/Qt4And5Automoc/CMakeLists.txt b/Tests/Qt4And5Automoc/CMakeLists.txt
index ad74961..12dc99b 100644
--- a/Tests/Qt4And5Automoc/CMakeLists.txt
+++ b/Tests/Qt4And5Automoc/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(Qt4And5Automoc)
 
diff --git a/Tests/Qt4Targets/CMakeLists.txt b/Tests/Qt4Targets/CMakeLists.txt
index 3ddc345..83cd44f 100644
--- a/Tests/Qt4Targets/CMakeLists.txt
+++ b/Tests/Qt4Targets/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(Qt4Targets)
 
diff --git a/Tests/Qt4Targets/IncrementalMoc/CMakeLists.txt b/Tests/Qt4Targets/IncrementalMoc/CMakeLists.txt
index 65e2b64..d0e9617 100644
--- a/Tests/Qt4Targets/IncrementalMoc/CMakeLists.txt
+++ b/Tests/Qt4Targets/IncrementalMoc/CMakeLists.txt
@@ -1,5 +1,5 @@
 
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(IncrementalMoc)
 
 find_package(Qt4 REQUIRED)
diff --git a/Tests/QtAutogen/AutoMocGeneratedFile/CMakeLists.txt b/Tests/QtAutogen/AutoMocGeneratedFile/CMakeLists.txt
new file mode 100644
index 0000000..ce129a9
--- /dev/null
+++ b/Tests/QtAutogen/AutoMocGeneratedFile/CMakeLists.txt
@@ -0,0 +1,23 @@
+cmake_minimum_required(VERSION 3.26)
+project(AutoMocGeneratedFile)
+
+include("../AutogenCoreTest.cmake")
+
+file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/myConfig.h
+    CONTENT
+    "
+#ifndef MYCONFIG_H
+#define MYCONFIG_H
+
+inline void foo() {}
+
+#endif
+
+"
+)
+
+add_executable(testTarget
+  main.cpp
+  ${CMAKE_CURRENT_BINARY_DIR}/myConfig.h)
+target_include_directories(testTarget PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
+set_property(TARGET testTarget PROPERTY AUTOMOC ON)
diff --git a/Tests/QtAutogen/AutoMocGeneratedFile/main.cpp b/Tests/QtAutogen/AutoMocGeneratedFile/main.cpp
new file mode 100644
index 0000000..e30d889
--- /dev/null
+++ b/Tests/QtAutogen/AutoMocGeneratedFile/main.cpp
@@ -0,0 +1,7 @@
+#include "myConfig.h"
+
+int main()
+{
+  foo();
+  return 0;
+}
diff --git a/Tests/QtAutogen/GlobalAutogenExecutable/CMakeLists.txt b/Tests/QtAutogen/GlobalAutogenExecutable/CMakeLists.txt
new file mode 100644
index 0000000..34da744
--- /dev/null
+++ b/Tests/QtAutogen/GlobalAutogenExecutable/CMakeLists.txt
@@ -0,0 +1,30 @@
+cmake_minimum_required(VERSION 3.26)
+project(GlobalAutogenExecutable)
+
+include("../AutogenCoreTest.cmake")
+
+set(test_automoc_path "global_automoc_exe_path")
+set(test_autouic_path "global_autouic_exe_path")
+set(test_autorcc_path "global_autorcc_exe_path")
+
+set(CMAKE_AUTOMOC_EXECUTABLE ${test_automoc_path})
+set(CMAKE_AUTOUIC_EXECUTABLE ${test_autouic_path})
+set(CMAKE_AUTORCC_EXECUTABLE ${test_autorcc_path})
+
+add_executable(autogen_test main.cpp)
+
+get_target_property(target_automoc_path autogen_test AUTOMOC_EXECUTABLE)
+get_target_property(target_autouic_path autogen_test AUTOUIC_EXECUTABLE)
+get_target_property(target_autorcc_path autogen_test AUTORCC_EXECUTABLE)
+
+if(NOT ${target_automoc_path} STREQUAL ${test_automoc_path})
+  message(FATAL_ERROR "CMAKE_AUTOMOC_EXECUTABLE not set")
+endif()
+
+if (NOT ${target_autouic_path} STREQUAL ${test_autouic_path})
+  message(FATAL_ERROR "CMAKE_AUTOUIC_EXECUTABLE not set")
+endif()
+
+if (NOT ${target_autorcc_path} STREQUAL ${test_autorcc_path})
+  message(FATAL_ERROR "CMAKE_AUTORCC_EXECUTABLE not set")
+endif()
diff --git a/Tests/QtAutogen/GlobalAutogenExecutable/main.cpp b/Tests/QtAutogen/GlobalAutogenExecutable/main.cpp
new file mode 100644
index 0000000..f8b643a
--- /dev/null
+++ b/Tests/QtAutogen/GlobalAutogenExecutable/main.cpp
@@ -0,0 +1,4 @@
+int main()
+{
+  return 0;
+}
diff --git a/Tests/QtAutogen/GlobalAutogenSystemUseInclude/CMakeLists.txt b/Tests/QtAutogen/GlobalAutogenSystemUseInclude/CMakeLists.txt
new file mode 100644
index 0000000..1095fb1
--- /dev/null
+++ b/Tests/QtAutogen/GlobalAutogenSystemUseInclude/CMakeLists.txt
@@ -0,0 +1,28 @@
+cmake_minimum_required(VERSION 3.26)
+project(GlobalAutogenSystemUseInclude)
+
+include("../AutogenCoreTest.cmake")
+
+block()
+  set(test_autogen_use_system_include ON)
+  set(CMAKE_AUTOGEN_USE_SYSTEM_INCLUDE ${test_autogen_use_system_include})
+
+  add_executable(autogen_test_on main.cpp)
+  get_target_property(target_autogen_use_system_include autogen_test_on AUTOGEN_USE_SYSTEM_INCLUDE)
+
+  if(NOT ${CMAKE_AUTOGEN_USE_SYSTEM_INCLUDE} STREQUAL ${target_autogen_use_system_include})
+    message(FATAL_ERROR "CMAKE_AUTOGEN_USE_SYSTEM_INCLUDE not set")
+  endif()
+endblock()
+
+block()
+  set(test_autogen_use_system_include OFF)
+  set(CMAKE_AUTOGEN_USE_SYSTEM_INCLUDE ${test_autogen_use_system_include})
+
+  add_executable(autogen_test_off main.cpp)
+  get_target_property(target_autogen_use_system_include autogen_test_off AUTOGEN_USE_SYSTEM_INCLUDE)
+
+  if(NOT ${CMAKE_AUTOGEN_USE_SYSTEM_INCLUDE} STREQUAL ${target_autogen_use_system_include})
+    message(FATAL_ERROR "CMAKE_AUTOGEN_USE_SYSTEM_INCLUDE not set")
+  endif()
+endblock()
diff --git a/Tests/QtAutogen/GlobalAutogenSystemUseInclude/main.cpp b/Tests/QtAutogen/GlobalAutogenSystemUseInclude/main.cpp
new file mode 100644
index 0000000..f8b643a
--- /dev/null
+++ b/Tests/QtAutogen/GlobalAutogenSystemUseInclude/main.cpp
@@ -0,0 +1,4 @@
+int main()
+{
+  return 0;
+}
diff --git a/Tests/QtAutogen/MocInterfaceMacroNames/CMakeLists.txt b/Tests/QtAutogen/MocInterfaceMacroNames/CMakeLists.txt
new file mode 100644
index 0000000..7744d78
--- /dev/null
+++ b/Tests/QtAutogen/MocInterfaceMacroNames/CMakeLists.txt
@@ -0,0 +1,61 @@
+cmake_minimum_required(VERSION 3.16)
+project(MocInterfaceMacroNames)
+
+include("../AutogenCoreTest.cmake")
+
+set(CMAKE_AUTOMOC ON)
+
+add_executable(dummy dummy.cpp)
+target_link_libraries(dummy PRIVATE static_lib interface_lib shared_lib)
+
+add_library(shared_lib SHARED shared_lib.cpp)
+set_target_properties(shared_lib PROPERTIES INTERFACE_AUTOMOC_MACRO_NAMES "SHARED_LIB_MACRO")
+
+add_library(interface_lib INTERFACE)
+set_target_properties(interface_lib PROPERTIES INTERFACE_AUTOMOC_MACRO_NAMES "INTERFACE_LIB_MACRO")
+
+add_library(static_lib STATIC static_lib.cpp)
+set_target_properties(static_lib PROPERTIES INTERFACE_AUTOMOC_MACRO_NAMES "STATIC_LIB_MACRO")
+
+set(AUTOGEN_INFO_FILE "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/dummy_autogen.dir/AutogenInfo.json")
+set(CHECK_AUTOGEN_JSON_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CheckAutogenJson.cmake")
+message(STATUS "AutogenInfo.json: ${AUTOGEN_INFO_FILE}")
+
+add_custom_command(TARGET dummy POST_BUILD
+    COMMAND ${CMAKE_COMMAND} -DFILE_PATH=${AUTOGEN_INFO_FILE} -P ${CHECK_AUTOGEN_JSON_PATH}
+)
+
+install(TARGETS shared_lib EXPORT shared_lib)
+install(TARGETS interface_lib EXPORT interface_lib)
+install(TARGETS static_lib EXPORT static_lib)
+
+install(EXPORT shared_lib FILE shared_libTargets.cmake DESTINATION lib/cmake/shared_lib)
+install(EXPORT interface_lib FILE interface_libTargets.cmake DESTINATION lib/cmake/interface_lib)
+install(EXPORT static_lib FILE static_libTargets.cmake DESTINATION lib/cmake/static_lib)
+
+set(CHECK_EXPORT_TARGETS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CheckExportTargets.cmake")
+set(EXPORT_FOLDER_PATH "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/Export")
+
+add_custom_command(TARGET dummy POST_BUILD
+    COMMAND ${CMAKE_COMMAND} -DFOLDER_PATH=${EXPORT_FOLDER_PATH} -P ${CHECK_EXPORT_TARGETS_PATH}
+)
+
+# check if INTERFACE_AUTOMOC_MACRO_NAMES were transferred to the *_link libraries correctly
+add_executable(dummy_link dummy.cpp)
+target_link_libraries(dummy_link PRIVATE static_link_lib interface_link_lib shared_link_lib)
+
+add_library(shared_link_lib SHARED shared_lib.cpp)
+target_link_libraries(shared_link_lib PUBLIC shared_lib)
+
+add_library(interface_link_lib INTERFACE)
+target_link_libraries(interface_link_lib INTERFACE interface_lib)
+
+add_library(static_link_lib STATIC static_lib.cpp)
+target_link_libraries(static_link_lib PUBLIC static_lib)
+
+set(AUTOGEN_INFO_FILE "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/dummy_link_autogen.dir/AutogenInfo.json")
+message(STATUS "AutogenInfo.json: ${AUTOGEN_INFO_FILE}")
+
+add_custom_command(TARGET dummy_link POST_BUILD
+    COMMAND ${CMAKE_COMMAND} -DFILE_PATH=${AUTOGEN_INFO_FILE} -P ${CHECK_AUTOGEN_JSON_PATH}
+)
diff --git a/Tests/QtAutogen/MocInterfaceMacroNames/CheckAutogenJson.cmake b/Tests/QtAutogen/MocInterfaceMacroNames/CheckAutogenJson.cmake
new file mode 100644
index 0000000..338f345
--- /dev/null
+++ b/Tests/QtAutogen/MocInterfaceMacroNames/CheckAutogenJson.cmake
@@ -0,0 +1,27 @@
+
+
+set(expected_values "SHARED_LIB_MACRO" "INTERFACE_LIB_MACRO" "STATIC_LIB_MACRO")
+function(checkAutoMocMacroNames FILE_PATH)
+  message(STATUS "Checking for auto moc macro names in ${FILE_PATH}")
+  file(READ ${FILE_PATH} FILE_CONTENT)
+  string(JSON MOC_MACRO_NAMES_ARR GET ${FILE_CONTENT} MOC_MACRO_NAMES)
+  # get the length of MOC_MACRO_NAMES in JSON
+  string(JSON MOC_MACRO_NAMES_LENGTH LENGTH ${MOC_MACRO_NAMES_ARR})
+  if(${MOC_MACRO_NAMES_LENGTH} EQUAL 0)
+      message(FATAL_ERROR "MOC_MACRO_NAMES is empty")
+  endif()
+  message(STATUS "MOC_MACRO_NAMES: ${MOC_MACRO_NAMES_ARR}")
+
+  math(EXPR last_index "${MOC_MACRO_NAMES_LENGTH} - 1")
+  set(reverse_index ${last_index})
+  foreach(expected_value IN LISTS expected_values)
+    string(JSON element GET ${MOC_MACRO_NAMES_ARR} ${reverse_index})
+    # check if element equals to expected value
+    if(NOT ${element} STREQUAL ${expected_value})
+      message(FATAL_ERROR "MOC_MACRO_NAMES is expected to contain ${expected_value} but contains ${element}")
+    endif()
+    math(EXPR reverse_index "${reverse_index} - 1")
+  endforeach()
+endfunction()
+
+checkAutoMocMacroNames(${FILE_PATH})
diff --git a/Tests/QtAutogen/MocInterfaceMacroNames/CheckExportTargets.cmake b/Tests/QtAutogen/MocInterfaceMacroNames/CheckExportTargets.cmake
new file mode 100644
index 0000000..9db23f6
--- /dev/null
+++ b/Tests/QtAutogen/MocInterfaceMacroNames/CheckExportTargets.cmake
@@ -0,0 +1,45 @@
+
+set(TARGET_NAMES "static_lib" "shared_lib" "interface_lib")
+set(static_lib_FOUND "0")
+set(shared_lib_FOUND "0")
+set(interface_lib_FOUND "0")
+
+macro(checkExportTargets FOLDER_PATH)
+  message("Checking folder: ${FOLDER_PATH}")
+  file(GLOB sources_list LIST_DIRECTORIES true RELATIVE ${FOLDER_PATH} ${FOLDER_PATH}/*)
+  message("Found files and folders: ${sources_list}")
+  foreach(source ${sources_list})
+    set(SOURCE_ABS "${FOLDER_PATH}/${source}")
+    if(IS_DIRECTORY ${SOURCE_ABS})
+      message("Found subfolder: ${source}")
+      checkExportTargets(${SOURCE_ABS})
+    else()
+      message("Found file: ${source}")
+      foreach(TARGET_NAME ${TARGET_NAMES})
+        set(TARGETS_FILE "${TARGET_NAME}Targets.cmake")
+        if(${source} STREQUAL ${TARGETS_FILE})
+          message("Found ${TARGETS_FILE} in ${FOLDER_PATH}")
+          string(TOUPPER ${TARGET_NAME} TARGET_NAME_UPPER)
+          set(expected_macro "${TARGET_NAME_UPPER}_MACRO")
+          set(expected_string "INTERFACE_AUTOMOC_MACRO_NAMES \"${expected_macro}\"")
+          file(READ ${FOLDER_PATH}/${source} contents)
+          if (NOT contents MATCHES ${expected_string})
+            message(FATAL_ERROR "Expected ${expected_string} in ${FOLDER_PATH}/${source}")
+          else()
+            message("Found ${expected_string} in ${FOLDER_PATH}/${source}")
+            set(${TARGET_NAME}_FOUND "1")
+          endif()
+        endif()
+      endforeach()
+    endif()
+  endforeach()
+endmacro()
+
+checkExportTargets(${FOLDER_PATH})
+
+foreach(TARGET_NAME ${TARGET_NAMES})
+  # check if the target found equals the expected value
+  if(NOT ${TARGET_NAME}_FOUND STREQUAL "1")
+    message(FATAL_ERROR "Did not find ${TARGET_NAME}Targets.cmake")
+  endif()
+endforeach()
diff --git a/Tests/QtAutogen/MocInterfaceMacroNames/dummy.cpp b/Tests/QtAutogen/MocInterfaceMacroNames/dummy.cpp
new file mode 100644
index 0000000..f8b643a
--- /dev/null
+++ b/Tests/QtAutogen/MocInterfaceMacroNames/dummy.cpp
@@ -0,0 +1,4 @@
+int main()
+{
+  return 0;
+}
diff --git a/Tests/QtAutogen/MocInterfaceMacroNames/shared_lib.cpp b/Tests/QtAutogen/MocInterfaceMacroNames/shared_lib.cpp
new file mode 100644
index 0000000..3a5c482
--- /dev/null
+++ b/Tests/QtAutogen/MocInterfaceMacroNames/shared_lib.cpp
@@ -0,0 +1,6 @@
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+  void foo()
+{
+}
diff --git a/Tests/QtAutogen/MocInterfaceMacroNames/static_lib.cpp b/Tests/QtAutogen/MocInterfaceMacroNames/static_lib.cpp
new file mode 100644
index 0000000..3695dc9
--- /dev/null
+++ b/Tests/QtAutogen/MocInterfaceMacroNames/static_lib.cpp
@@ -0,0 +1,3 @@
+void foo()
+{
+}
diff --git a/Tests/QtAutogen/RccAutogenBuildDir/CMakeLists.txt b/Tests/QtAutogen/RccAutogenBuildDir/CMakeLists.txt
new file mode 100644
index 0000000..9bdb689
--- /dev/null
+++ b/Tests/QtAutogen/RccAutogenBuildDir/CMakeLists.txt
@@ -0,0 +1,33 @@
+cmake_minimum_required(VERSION 3.16)
+project(RccAutogenBuildDir)
+include("../AutogenCoreTest.cmake")
+
+set(PROJECTS_ROOT ${CMAKE_BINARY_DIR})
+
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTOUIC ON)
+set(CMAKE_AUTORCC ON)
+
+macro(set_build_type_dependent varName debugValue releaseValue
+      relWithDebInfoValue minSizeRelValue)
+
+  if(CMAKE_BUILD_TYPE MATCHES Debug)
+    set(${varName} ${debugValue})
+  elseif(CMAKE_BUILD_TYPE MATCHES Release)
+    set(${varName} ${releaseValue})
+  elseif(CMAKE_BUILD_TYPE MATCHES RelWithDebInfo)
+    set(${varName} ${relWithDebInfoValue})
+  elseif(CMAKE_BUILD_TYPE MATCHES MinSizeRel)
+    set(${varName} ${minSizeRelValue})
+  endif()
+endmacro()
+
+set_build_type_dependent(AUTOGEN_DIR agd agr ags agm)
+add_library(testlib SHARED lib.h lib.cpp resource.qrc)
+set_target_properties(testlib PROPERTIES AUTOGEN_BUILD_DIR "${PROJECTS_ROOT}/${AUTOGEN_DIR}/testlib_ag")
+target_link_libraries(testlib ${QT_LIBRARIES})
+
+set_build_type_dependent(AUTOGEN_DIR agd agr ags agm)
+add_executable(autorcctest main.cpp lib.h)
+set_target_properties(autorcctest PROPERTIES AUTOGEN_BUILD_DIR "${PROJECTS_ROOT}/${AUTOGEN_DIR}/autorcctest_ag")
+target_link_libraries(autorcctest ${QT_LIBRARIES} testlib)
diff --git a/Tests/QtAutogen/RccAutogenBuildDir/lib.cpp b/Tests/QtAutogen/RccAutogenBuildDir/lib.cpp
new file mode 100644
index 0000000..3a5c482
--- /dev/null
+++ b/Tests/QtAutogen/RccAutogenBuildDir/lib.cpp
@@ -0,0 +1,6 @@
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+  void foo()
+{
+}
diff --git a/Tests/QtAutogen/RccAutogenBuildDir/lib.h b/Tests/QtAutogen/RccAutogenBuildDir/lib.h
new file mode 100644
index 0000000..28138f1
--- /dev/null
+++ b/Tests/QtAutogen/RccAutogenBuildDir/lib.h
@@ -0,0 +1,6 @@
+#ifndef LIB_H
+#define LIB_H
+
+void foo();
+
+#endif
diff --git a/Tests/QtAutogen/RccAutogenBuildDir/main.cpp b/Tests/QtAutogen/RccAutogenBuildDir/main.cpp
new file mode 100644
index 0000000..a211f40
--- /dev/null
+++ b/Tests/QtAutogen/RccAutogenBuildDir/main.cpp
@@ -0,0 +1,7 @@
+#include "lib.h"
+
+int main()
+{
+  foo();
+  return 0;
+}
diff --git a/Tests/QtAutogen/RccAutogenBuildDir/resource.qrc b/Tests/QtAutogen/RccAutogenBuildDir/resource.qrc
new file mode 100644
index 0000000..90f4a83
--- /dev/null
+++ b/Tests/QtAutogen/RccAutogenBuildDir/resource.qrc
@@ -0,0 +1,2 @@
+<!DOCTYPE RCC>
+<RCC version="1.0"/>
diff --git a/Tests/QtAutogen/Tests.cmake b/Tests/QtAutogen/Tests.cmake
index a3c57a5..7dd9c84 100644
--- a/Tests/QtAutogen/Tests.cmake
+++ b/Tests/QtAutogen/Tests.cmake
@@ -1,11 +1,15 @@
-# Qt4 and Qt5 tests
+# Qt4, Qt5 and Qt6 tests
 ADD_AUTOGEN_TEST(AutogenOriginDependsOff autogenOriginDependsOff)
 ADD_AUTOGEN_TEST(AutogenOriginDependsOn)
 ADD_AUTOGEN_TEST(AutogenTargetDepends)
+ADD_AUTOGEN_TEST(AutoMocGeneratedFile)
 ADD_AUTOGEN_TEST(Complex QtAutogen)
+ADD_AUTOGEN_TEST(GlobalAutogenSystemUseInclude)
 ADD_AUTOGEN_TEST(GlobalAutogenTarget)
+ADD_AUTOGEN_TEST(GlobalAutogenExecutable)
 ADD_AUTOGEN_TEST(LowMinimumVersion lowMinimumVersion)
 ADD_AUTOGEN_TEST(ManySources manySources)
+ADD_AUTOGEN_TEST(MocInterfaceMacroNames)
 ADD_AUTOGEN_TEST(MocOnly mocOnly)
 ADD_AUTOGEN_TEST(MocOptions mocOptions)
 ADD_AUTOGEN_TEST(ObjectLibrary someProgram)
@@ -15,6 +19,7 @@
 ADD_AUTOGEN_TEST(Parallel3 parallel3)
 ADD_AUTOGEN_TEST(Parallel4 parallel4)
 ADD_AUTOGEN_TEST(ParallelAUTO parallelAUTO)
+ADD_AUTOGEN_TEST(RccAutogenBuildDir)
 ADD_AUTOGEN_TEST(RccEmpty rccEmpty)
 ADD_AUTOGEN_TEST(RccOffMocLibrary)
 ADD_AUTOGEN_TEST(RccOnly rccOnly)
@@ -44,7 +49,7 @@
   ADD_AUTOGEN_TEST(MocSkipSource)
 endif()
 
-# Qt5 only tests
+# Qt5 and Qt6 only tests
 if(QT_TEST_VERSION GREATER 4)
   ADD_AUTOGEN_TEST(MocMacroName mocMacroName)
   ADD_AUTOGEN_TEST(MocOsMacros)
diff --git a/Tests/QtAutomocNoQt/CMakeLists.txt b/Tests/QtAutomocNoQt/CMakeLists.txt
index 655f12b..4e2ceeb 100644
--- a/Tests/QtAutomocNoQt/CMakeLists.txt
+++ b/Tests/QtAutomocNoQt/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 
 project(QtAutomocNoQt)
 
diff --git a/Tests/ReturnTest/CMakeLists.txt b/Tests/ReturnTest/CMakeLists.txt
index 78e3fc1..03a0f7a 100644
--- a/Tests/ReturnTest/CMakeLists.txt
+++ b/Tests/ReturnTest/CMakeLists.txt
@@ -1,5 +1,5 @@
 # a simple C only test case
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project (ReturnTest)
 
 function (FAILED testname)
diff --git a/Tests/RunCMake/AddRunCMakeTest.cmake b/Tests/RunCMake/AddRunCMakeTest.cmake
new file mode 100644
index 0000000..c0c3bba
--- /dev/null
+++ b/Tests/RunCMake/AddRunCMakeTest.cmake
@@ -0,0 +1,10 @@
+if(NOT DEFINED RunCMake_TEST_SUITE)
+  message("Usage: ${CMAKE_COMMAND} -DRunCMake_TEST_SUITE=<test name> -P Tests/RunCMake/AddRunCMakeTestSuite.cmake")
+else()
+  include("Source/CMakeVersion.cmake")
+  set(RunCMake_TEST_DIR "Tests/RunCMake/${RunCMake_TEST_SUITE}")
+  file(MAKE_DIRECTORY "${RunCMake_TEST_DIR}")
+  file(WRITE "${RunCMake_TEST_DIR}/CMakeLists.txt" "cmake_minimum_required(VERSION ${CMake_VERSION_MAJOR}.${CMake_VERSION_MINOR})\nproject(\${RunCMake_TEST} NONE)\ninclude(\${RunCMake_TEST}.cmake)\n")
+  file(WRITE "${RunCMake_TEST_DIR}/RunCMakeTest.cmake" "include(RunCMake)\n\n# Write your test cases below, like so:\n#\n# run_cmake(TestCaseName)\n")
+  file(APPEND "Tests/RunCMake/CMakeLists.txt" "add_RunCMake_test(${RunCMake_TEST_SUITE})\n")
+endif()
diff --git a/Tests/RunCMake/AppleSilicon/RunCMakeTest.cmake b/Tests/RunCMake/AppleSilicon/RunCMakeTest.cmake
index 39e462e..58c50e0 100644
--- a/Tests/RunCMake/AppleSilicon/RunCMakeTest.cmake
+++ b/Tests/RunCMake/AppleSilicon/RunCMakeTest.cmake
@@ -10,11 +10,17 @@
   run_cmake(${case})
   unset(RunCMake_TEST_OPTIONS)
   set(RunCMake_TEST_NO_CLEAN 1)
-  run_cmake_command(${case}-build ${CMAKE_COMMAND} --build . --config Debug)
+  set(RunCMake_TEST_OUTPUT_MERGE 1)
+  run_cmake_command(${case}-build ${CMAKE_COMMAND} --build . --config Debug --verbose)
 endfunction()
 
 run_arch(default)
 
+if(RunCMake_GENERATOR MATCHES "Makefiles|Ninja")
+  run_arch(default-target-arm64 -DCMAKE_C_COMPILER_TARGET=arm64-apple-macosx)
+  run_arch(default-target-x86_64 -DCMAKE_C_COMPILER_TARGET=x86_64-apple-macosx)
+endif()
+
 run_arch(arm64-var -DCMAKE_APPLE_SILICON_PROCESSOR=arm64)
 run_arch(x86_64-var -DCMAKE_APPLE_SILICON_PROCESSOR=x86_64)
 
diff --git a/Tests/RunCMake/AppleSilicon/default-target-arm64-build-check.cmake b/Tests/RunCMake/AppleSilicon/default-target-arm64-build-check.cmake
new file mode 100644
index 0000000..33ad931
--- /dev/null
+++ b/Tests/RunCMake/AppleSilicon/default-target-arm64-build-check.cmake
@@ -0,0 +1,5 @@
+if(NOT actual_stdout MATCHES "[ -]-target=arm64-apple-macosx ")
+  set(RunCMake_TEST_FAILED "No -target=arm64-apple-macosx flag found!")
+elseif(actual_stdout MATCHES " (-arch +[^ ]*)")
+  set(RunCMake_TEST_FAILED "'${CMAKE_MATCH_1}' flag incorrectly found!")
+endif()
diff --git a/Tests/RunCMake/AppleSilicon/default-target-arm64.cmake b/Tests/RunCMake/AppleSilicon/default-target-arm64.cmake
new file mode 100644
index 0000000..8c94020
--- /dev/null
+++ b/Tests/RunCMake/AppleSilicon/default-target-arm64.cmake
@@ -0,0 +1,5 @@
+enable_language(C)
+if(NOT CMAKE_OSX_ARCHITECTURES STREQUAL "")
+  message(FATAL_ERROR "CMAKE_OSX_ARCHITECTURES is '${CMAKE_OSX_ARCHITECTURES}', not empty ''")
+endif()
+add_library(arm64 arm64.c)
diff --git a/Tests/RunCMake/AppleSilicon/default-target-x86_64-build-check.cmake b/Tests/RunCMake/AppleSilicon/default-target-x86_64-build-check.cmake
new file mode 100644
index 0000000..9116ef4
--- /dev/null
+++ b/Tests/RunCMake/AppleSilicon/default-target-x86_64-build-check.cmake
@@ -0,0 +1,5 @@
+if(NOT actual_stdout MATCHES "[ -]-target=x86_64-apple-macosx ")
+  set(RunCMake_TEST_FAILED "No -target=x86_64-apple-macosx flag found!")
+elseif(actual_stdout MATCHES " (-arch +[^ ]*)")
+  set(RunCMake_TEST_FAILED "'${CMAKE_MATCH_1}' flag incorrectly found!")
+endif()
diff --git a/Tests/RunCMake/AppleSilicon/default-target-x86_64.cmake b/Tests/RunCMake/AppleSilicon/default-target-x86_64.cmake
new file mode 100644
index 0000000..ded46b7
--- /dev/null
+++ b/Tests/RunCMake/AppleSilicon/default-target-x86_64.cmake
@@ -0,0 +1,5 @@
+enable_language(C)
+if(NOT CMAKE_OSX_ARCHITECTURES STREQUAL "")
+  message(FATAL_ERROR "CMAKE_OSX_ARCHITECTURES is '${CMAKE_OSX_ARCHITECTURES}', not empty ''")
+endif()
+add_library(x86_64 x86_64.c)
diff --git a/Tests/RunCMake/AppleTextStubs/CMakeLists.txt b/Tests/RunCMake/AppleTextStubs/CMakeLists.txt
new file mode 100644
index 0000000..93ee9df
--- /dev/null
+++ b/Tests/RunCMake/AppleTextStubs/CMakeLists.txt
@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.5)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/AppleTextStubs/Framework-export.cmake b/Tests/RunCMake/AppleTextStubs/Framework-export.cmake
new file mode 100644
index 0000000..f75c6d1
--- /dev/null
+++ b/Tests/RunCMake/AppleTextStubs/Framework-export.cmake
@@ -0,0 +1,12 @@
+enable_language(C)
+
+add_library(foo SHARED foo.c)
+set_property(TARGET foo PROPERTY FRAMEWORK TRUE)
+set_property(TARGET foo PROPERTY ENABLE_EXPORTS TRUE)
+set_property(TARGET foo PROPERTY LIBRARY_OUTPUT_DIRECTORY $<CONFIG>)
+
+install(TARGETS foo EXPORT foo FRAMEWORK DESTINATION  DESTINATION "${CMAKE_BINARY_DIR}/$<CONFIG>")
+install(EXPORT foo DESTINATION lib/foo NAMESPACE foo-install::)
+install(FILES foo-config.cmake.in RENAME foo-config.cmake DESTINATION lib/foo)
+
+export(TARGETS foo NAMESPACE foo-build:: FILE Release/foo.cmake)
diff --git a/Tests/RunCMake/AppleTextStubs/Framework-import.cmake b/Tests/RunCMake/AppleTextStubs/Framework-import.cmake
new file mode 100644
index 0000000..e0001d0
--- /dev/null
+++ b/Tests/RunCMake/AppleTextStubs/Framework-import.cmake
@@ -0,0 +1,62 @@
+enable_language(C)
+
+find_package(foo REQUIRED CONFIG NO_DEFAULT_PATH)
+
+add_executable(main main.c)
+target_link_libraries(main PRIVATE foo-install::foo)
+
+get_property(is_framework TARGET foo-install::foo PROPERTY FRAMEWORK)
+if (NOT is_framework)
+  message(SEND_ERROR "foo-build::foo: FRAMEWORK not set.")
+endif()
+get_property(enable_exports TARGET foo-install::foo PROPERTY ENABLE_EXPORTS)
+if (CAMKE_TAPI AND NOT enable_exports)
+  message(SEND_ERROR "foo-install::foo: ENABLE_EXPORTS not set.")
+endif()
+
+get_property(implib TARGET foo-install::foo PROPERTY IMPORTED_IMPLIB_RELEASE)
+if (CAMKE_TAPI AND NOT implib)
+  message(SEND_ERROR "foo-install::foo: IMPORTED_IMPLIB_RELEASE not set.")
+endif()
+if (CAMKE_TAPI AND NOT implib MATCHES "foo.framework/Versions/A/foo.tbd$")
+  message(SEND_ERROR "foo-install::foo: ${implib}: wrong value for IMPORTED_IMPLIB_RELEASE.")
+endif()
+
+get_property(location TARGET foo-install::foo PROPERTY IMPORTED_LOCATION_RELEASE)
+if (NOT location)
+  message(SEND_ERROR "foo-install::foo: IMPORTED_LOCATION_RELEASE not set.")
+endif()
+if (NOT location MATCHES "foo.framework/Versions/A/foo$")
+  message(SEND_ERROR "foo-install::foo: ${location}: wrong value for IMPORTED_LOCATION_RELEASE.")
+endif()
+
+
+include(${foo_BUILD}/foo.cmake)
+
+add_executable(main2 main.c)
+target_link_libraries(main2 PRIVATE foo-build::foo)
+
+get_property(is_framework TARGET foo-build::foo PROPERTY FRAMEWORK)
+if (NOT is_framework)
+  message(SEND_ERROR "foo-build::foo: FRAMEWORK not set.")
+endif()
+get_property(enable_exports TARGET foo-build::foo PROPERTY ENABLE_EXPORTS)
+if (CAMKE_TAPI AND NOT enable_exports)
+  message(SEND_ERROR "foo-build::foo: ENABLE_EXPORTS not set.")
+endif()
+
+get_property(implib TARGET foo-build::foo PROPERTY IMPORTED_IMPLIB_RELEASE)
+if (CAMKE_TAPI AND NOT implib)
+  message(SEND_ERROR "foo-build::foo: IMPORTED_IMPLIB_RELEASE not set.")
+endif()
+if (CAMKE_TAPI AND NOT implib STREQUAL "${foo_BUILD}/foo.framework/Versions/A/foo.tbd")
+  message(SEND_ERROR "foo-build::foo: ${implib}: wrong value for IMPORTED_IMPLIB_RELEASE.")
+endif()
+
+get_property(location TARGET foo-build::foo PROPERTY IMPORTED_LOCATION_RELEASE)
+if (NOT location)
+  message(SEND_ERROR "foo-build::foo: IMPORTED_LOCATION_RELEASE not set.")
+endif()
+if (NOT location STREQUAL "${foo_BUILD}/foo.framework/Versions/A/foo")
+  message(SEND_ERROR "foo-build::foo: ${location}: wrong value for IMPORTED_LOCATION_RELEASE.")
+endif()
diff --git a/Tests/RunCMake/AppleTextStubs/Framework-install-check.cmake b/Tests/RunCMake/AppleTextStubs/Framework-install-check.cmake
new file mode 100644
index 0000000..e8a5557
--- /dev/null
+++ b/Tests/RunCMake/AppleTextStubs/Framework-install-check.cmake
@@ -0,0 +1 @@
+include ("${RunCMake_TEST_BINARY_DIR}/Framework-Release-generated.cmake")
diff --git a/Tests/RunCMake/AppleTextStubs/Framework.cmake b/Tests/RunCMake/AppleTextStubs/Framework.cmake
new file mode 100644
index 0000000..f99eb5e
--- /dev/null
+++ b/Tests/RunCMake/AppleTextStubs/Framework.cmake
@@ -0,0 +1,59 @@
+enable_language(C)
+
+add_library(foo SHARED foo.c)
+set_property(TARGET foo PROPERTY ENABLE_EXPORTS TRUE)
+set_property(TARGET foo PROPERTY FRAMEWORK TRUE)
+
+add_executable(main main.c)
+target_link_libraries(main PRIVATE foo)
+
+
+install(TARGETS foo FRAMEWORK DESTINATION "${CMAKE_BINARY_DIR}/INSTALL")
+
+# LIBRARY and ARCHIVE should be ignored
+install(TARGETS foo FRAMEWORK DESTINATION "${CMAKE_BINARY_DIR}/INSTALL2"
+                    LIBRARY DESTINATION "${CMAKE_BINARY_DIR}/INSTALL2/lib"
+                    ARCHIVE DESTINATION "${CMAKE_BINARY_DIR}/INSTALL2/dev")
+
+
+set (GENERATE_CONTENT "if (\"${CMAKE_TAPI}\")
+  set (APPLE_TEXT_STUBS_SUPPORTED TRUE)
+endif()\n\n")
+
+string (APPEND GENERATE_CONTENT [[
+macro (CHECK_FILE test_msg path)
+  if (NOT EXISTS "${path}")
+    string (APPEND RunCMake_TEST_FAILED "${test_msg}: \"${path}\" not found\n")
+  endif()
+endmacro()
+
+macro (CHECK_SYMLINK test_msg path)
+  if(NOT IS_SYMLINK "${path}")
+    string (APPEND RunCMake_TEST_FAILED "${test_msg}: \"${path}\" is not a symbolic link\n")
+  elseif (NOT EXISTS "${path}")
+    string (APPEND RunCMake_TEST_FAILED "${test_msg}: \"${path}\" is not a valid symlink\n")
+  endif()
+endmacro()
+
+check_file("DYLIB file" "$<TARGET_FILE:foo>")
+check_symlink("Public DYLIB file" "$<TARGET_LINKER_LIBRARY_FILE:foo>")
+check_file("executable file" "$<TARGET_FILE:main>")
+
+check_file("Installed DYLIB file" "${RunCMake_TEST_BINARY_DIR}/INSTALL/foo.framework/Versions/A/$<TARGET_FILE_NAME:foo>")
+check_symlink("Installed Public DULIB file" "${RunCMake_TEST_BINARY_DIR}/INSTALL/foo.framework/$<TARGET_FILE_NAME:foo>")
+check_file("Installed DULIB file" "${RunCMake_TEST_BINARY_DIR}/INSTALL2/foo.framework/Versions/A/$<TARGET_FILE_NAME:foo>")
+check_symlink("Installed Public DYLIB file" "${RunCMake_TEST_BINARY_DIR}/INSTALL2/foo.framework/$<TARGET_FILE_NAME:foo>")
+
+if (APPLE_TEXT_STUBS_SUPPORTED)
+  check_file("TBD file" "$<TARGET_IMPORT_FILE:foo>")
+  check_symlink("Public TBD file" "$<TARGET_LINKER_IMPORT_FILE:foo>")
+
+  check_file("Installed TBD file" "${RunCMake_TEST_BINARY_DIR}/INSTALL/foo.framework/Versions/A/$<TARGET_IMPORT_FILE_NAME:foo>")
+  check_symlink("Installed Public TBD file" "${RunCMake_TEST_BINARY_DIR}/INSTALL/foo.framework/$<TARGET_IMPORT_FILE_NAME:foo>")
+  check_file("Installed TBD file" "${RunCMake_TEST_BINARY_DIR}/INSTALL2/foo.framework/Versions/A/$<TARGET_IMPORT_FILE_NAME:foo>")
+  check_symlink("Installed Public TBD file" "${RunCMake_TEST_BINARY_DIR}/INSTALL2/foo.framework/$<TARGET_IMPORT_FILE_NAME:foo>")
+endif()
+]])
+
+file (GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/Framework-$<CONFIG>-generated.cmake"
+  CONTENT "${GENERATE_CONTENT}")
diff --git a/Tests/RunCMake/AppleTextStubs/Library-export.cmake b/Tests/RunCMake/AppleTextStubs/Library-export.cmake
new file mode 100644
index 0000000..d2e09ea
--- /dev/null
+++ b/Tests/RunCMake/AppleTextStubs/Library-export.cmake
@@ -0,0 +1,12 @@
+enable_language(C)
+
+add_library(foo SHARED foo.c)
+set_property(TARGET foo PROPERTY ENABLE_EXPORTS TRUE)
+set_property(TARGET foo PROPERTY LIBRARY_OUTPUT_DIRECTORY $<CONFIG>)
+set_property(TARGET foo PROPERTY ARCHIVE_OUTPUT_DIRECTORY $<CONFIG>)
+
+install(TARGETS foo EXPORT foo DESTINATION "${CMAKE_BINARY_DIR}/$<CONFIG>")
+install(EXPORT foo DESTINATION lib/foo NAMESPACE foo-install::)
+install(FILES foo-config.cmake.in RENAME foo-config.cmake DESTINATION lib/foo)
+
+export(TARGETS foo NAMESPACE foo-build:: FILE Release/foo.cmake)
diff --git a/Tests/RunCMake/AppleTextStubs/Library-import.cmake b/Tests/RunCMake/AppleTextStubs/Library-import.cmake
new file mode 100644
index 0000000..9406aac
--- /dev/null
+++ b/Tests/RunCMake/AppleTextStubs/Library-import.cmake
@@ -0,0 +1,54 @@
+enable_language(C)
+
+find_package(foo REQUIRED CONFIG NO_DEFAULT_PATH)
+
+add_executable(main main.c)
+target_link_libraries(main PRIVATE foo-install::foo)
+
+get_property(enable_exports TARGET foo-install::foo PROPERTY ENABLE_EXPORTS)
+if (CMAKE_TAPI AND NOT enable_exports)
+  message(SEND_ERROR "foo-install::foo: ENABLE_EXPORTS not set.")
+endif()
+
+get_property(implib TARGET foo-install::foo PROPERTY IMPORTED_IMPLIB_RELEASE)
+if (CMAKE_TAPI AND NOT implib)
+  message(SEND_ERROR "foo-install::foo: IMPORTED_IMPLIB_RELEASE not set.")
+endif()
+if (CMAKE_TAPI AND NOT implib MATCHES "Release/libfoo.tbd$")
+  message(SEND_ERROR "foo-install::foo: ${implib}: wrong value for IMPORTED_IMPLIB_RELEASE.")
+endif()
+
+get_property(location TARGET foo-install::foo PROPERTY IMPORTED_LOCATION_RELEASE)
+if (NOT location)
+  message(SEND_ERROR "foo-install::foo: IMPORTED_LOCATION_RELEASE not set.")
+endif()
+if (NOT location MATCHES "Release/libfoo.dylib$")
+  message(SEND_ERROR "foo-install::foo: ${location}: wrong value for IMPORTED_LOCATION_RELEASE.")
+endif()
+
+
+include(${foo_BUILD}/foo.cmake)
+
+add_executable(main2 main.c)
+target_link_libraries(main2 PRIVATE foo-build::foo)
+
+get_property(enable_exports TARGET foo-build::foo PROPERTY ENABLE_EXPORTS)
+if (CMAKE_TAPI AND NOT enable_exports)
+  message(SEND_ERROR "foo-build::foo: ENABLE_EXPORTS not set.")
+endif()
+
+get_property(implib TARGET foo-build::foo PROPERTY IMPORTED_IMPLIB_RELEASE)
+if (CMAKE_TAPI AND NOT implib)
+  message(SEND_ERROR "foo-build::foo: IMPORTED_IMPLIB_RELEASE not set.")
+endif()
+if (CMAKE_TAPI AND NOT implib STREQUAL "${foo_BUILD}/libfoo.tbd")
+  message(SEND_ERROR "foo-build::foo: ${implib}: wrong value for IMPORTED_IMPLIB_RELEASE.")
+endif()
+
+get_property(location TARGET foo-build::foo PROPERTY IMPORTED_LOCATION_RELEASE)
+if (NOT location)
+  message(SEND_ERROR "foo-build::foo: IMPORTED_LOCATION_RELEASE not set.")
+endif()
+if (NOT location STREQUAL "${foo_BUILD}/libfoo.dylib")
+  message(SEND_ERROR "foo-build::foo: ${location}: wrong value for IMPORTED_LOCATION_RELEASE.")
+endif()
diff --git a/Tests/RunCMake/AppleTextStubs/LibraryWithOutputs-install-check.cmake b/Tests/RunCMake/AppleTextStubs/LibraryWithOutputs-install-check.cmake
new file mode 100644
index 0000000..40ec0a6
--- /dev/null
+++ b/Tests/RunCMake/AppleTextStubs/LibraryWithOutputs-install-check.cmake
@@ -0,0 +1 @@
+include ("${RunCMake_TEST_BINARY_DIR}/LibraryWithOutputs-Release-generated.cmake")
diff --git a/Tests/RunCMake/AppleTextStubs/LibraryWithOutputs.cmake b/Tests/RunCMake/AppleTextStubs/LibraryWithOutputs.cmake
new file mode 100644
index 0000000..f61c8f2
--- /dev/null
+++ b/Tests/RunCMake/AppleTextStubs/LibraryWithOutputs.cmake
@@ -0,0 +1,52 @@
+enable_language(C)
+
+add_library(foo SHARED foo.c)
+set_property(TARGET foo PROPERTY ENABLE_EXPORTS TRUE)
+set_property(TARGET foo PROPERTY ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/TBD/$<CONFIG>")
+set_property(TARGET foo PROPERTY ARCHIVE_OUTPUT_NAME "tbd")
+
+add_executable(main main.c)
+target_link_libraries(main PRIVATE foo)
+
+
+set (GENERATE_CONTENT "if (\"${CMAKE_TAPI}\")
+  set (APPLE_TEXT_STUBS_SUPPORTED TRUE)
+endif()\n\n")
+
+string (APPEND GENERATE_CONTENT [[
+macro (CHECK_FILE test_msg path)
+  if (NOT EXISTS "${path}")
+    string (APPEND RunCMake_TEST_FAILED "${test_msg}: \"${path}\" not found\n")
+  endif()
+endmacro()
+
+check_file("DYLIB file" "$<TARGET_FILE:foo>")
+check_file("executable file" "$<TARGET_FILE:main>")
+
+if (APPLE_TEXT_STUBS_SUPPORTED)
+  check_file("TBD file" "$<TARGET_IMPORT_FILE:foo>")
+]])
+
+if (CMAKE_GENERATOR STREQUAL "Xcode")
+  # ARCHIVE outputs are ignored by this generator
+  string (APPEND GENERATE_CONTENT
+    "\n  if (NOT \"$<TARGET_IMPORT_FILE_DIR:foo>\" STREQUAL \"${CMAKE_BINARY_DIR}/$<CONFIG>\")
+    string (APPEND RunCMake_TEST_FAILED \"Wrong directory for TBD file: \\\"$<TARGET_IMPORT_FILE_DIR:foo>\\\"\n\")
+  endif()
+  if (NOT \"$<TARGET_IMPORT_FILE_BASE_NAME:foo>\" STREQUAL \"foo\")
+    string (APPEND RunCMake_TEST_FAILED \"Wrong base name for TBD file: \\\"$<TARGET_IMPORT_FILE_BASE_NAME:foo>\\\"\n\")
+  endif()\n")
+else()
+  string (APPEND GENERATE_CONTENT
+    "\n  if (NOT \"$<TARGET_IMPORT_FILE_DIR:foo>\" STREQUAL \"${CMAKE_BINARY_DIR}/TBD/$<CONFIG>\")
+    string (APPEND RunCMake_TEST_FAILED \"Wrong directory for TBD file: \\\"$<TARGET_IMPORT_FILE_DIR:foo>\\\"\n\")
+  endif()
+  if (NOT \"$<TARGET_IMPORT_FILE_BASE_NAME:foo>\" STREQUAL \"tbd\")
+    string (APPEND RunCMake_TEST_FAILED \"Wrong base name for TBD file: \\\"$<TARGET_IMPORT_FILE_BASE_NAME:foo>\\\"\n\")
+  endif()\n")
+endif()
+string (APPEND GENERATE_CONTENT "endif()\n")
+
+
+file (GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/LibraryWithOutputs-$<CONFIG>-generated.cmake"
+  CONTENT "${GENERATE_CONTENT}")
diff --git a/Tests/RunCMake/AppleTextStubs/LibraryWithVersions-install-check.cmake b/Tests/RunCMake/AppleTextStubs/LibraryWithVersions-install-check.cmake
new file mode 100644
index 0000000..af73286
--- /dev/null
+++ b/Tests/RunCMake/AppleTextStubs/LibraryWithVersions-install-check.cmake
@@ -0,0 +1 @@
+include ("${RunCMake_TEST_BINARY_DIR}/LibraryWithVersions-Release-generated.cmake")
diff --git a/Tests/RunCMake/AppleTextStubs/LibraryWithVersions.cmake b/Tests/RunCMake/AppleTextStubs/LibraryWithVersions.cmake
new file mode 100644
index 0000000..28c175d
--- /dev/null
+++ b/Tests/RunCMake/AppleTextStubs/LibraryWithVersions.cmake
@@ -0,0 +1,96 @@
+enable_language(C)
+
+add_library(foo SHARED foo.c)
+set_property(TARGET foo PROPERTY ENABLE_EXPORTS TRUE)
+set_property (TARGET foo PROPERTY VERSION 2.5.0)
+set_property (TARGET foo PROPERTY SOVERSION 2.0.0)
+
+add_executable(main main.c)
+target_link_libraries(main PRIVATE foo)
+
+
+install(TARGETS foo DESTINATION "${CMAKE_BINARY_DIR}/INSTALL" COMPONENT default)
+
+install(TARGETS foo ARCHIVE DESTINATION "${CMAKE_BINARY_DIR}/INSTALL2/dev1" NAMELINK_SKIP COMPONENT default)
+install(TARGETS foo ARCHIVE DESTINATION "${CMAKE_BINARY_DIR}/INSTALL2/dev2" NAMELINK_ONLY COMPONENT default)
+
+install(TARGETS foo ARCHIVE DESTINATION "${CMAKE_BINARY_DIR}/INSTALL3"
+                            COMPONENT lib3 NAMELINK_COMPONENT dev3)
+install(TARGETS foo ARCHIVE DESTINATION "${CMAKE_BINARY_DIR}/INSTALL4"
+                            COMPONENT lib4 NAMELINK_COMPONENT dev4)
+
+
+set (GENERATE_CONTENT "if (\"${CMAKE_TAPI}\")
+  set (APPLE_TEXT_STUBS_SUPPORTED TRUE)
+endif()\n\n")
+
+string (APPEND GENERATE_CONTENT [[
+cmake_policy (SET CMP0011 NEW)
+cmake_policy (SET CMP0057 NEW)
+
+macro (CHECK_FILE test_msg path)
+  if (NOT EXISTS "${path}")
+    string (APPEND RunCMake_TEST_FAILED "${test_msg}: \"${path}\" not found\n")
+  endif()
+endmacro()
+
+macro (CHECK_SYMLINK test_msg path)
+  if (NOT IS_SYMLINK "${path}")
+    string (APPEND RunCMake_TEST_FAILED "${test_msg}: \"${path}\" is not a symbolic link\n")
+  elseif (NOT EXISTS "${path}")
+    string (APPEND RunCMake_TEST_FAILED "${test_msg}: \"${path}\" not a valid symlink\n")
+  endif()
+endmacro()
+
+macro (CHECK_NOFILE test_msg path)
+  if (EXISTS "${path}")
+    string (APPEND RunCMake_TEST_FAILED "${test_msg}: \"${path}\" was found\n")
+  endif()
+endmacro()
+
+macro (CHECK_INSTALLED test_msg dir file)
+  file(GLOB installed_files LIST_DIRECTORIES FALSE RELATIVE "${dir}" "${dir}/*")
+  if (NOT "${file}" IN_LIST installed_files)
+    string (APPEND RunCMake_TEST_FAILED "${test_msg}: \"${dir}/${file}\" not found\n")
+  endif()
+endmacro()
+
+
+check_file("DYLIB file" "$<TARGET_FILE:foo>")
+check_symlink("Linkable DYLIB file" "$<TARGET_LINKER_LIBRARY_FILE:foo>")
+check_symlink("SONAME DYLIB file" "$<TARGET_SONAME_FILE:foo>")
+check_file("executable file" "$<TARGET_FILE:main>")
+
+check_file("Installed DYLIB file" "${RunCMake_TEST_BINARY_DIR}/INSTALL/$<TARGET_FILE_NAME:foo>")
+check_symlink("Installed Linkable DYLIB file" "${RunCMake_TEST_BINARY_DIR}/INSTALL/$<TARGET_LINKER_LIBRARY_FILE_NAME:foo>")
+check_symlink("Installed SONAME DYLIB file" "${RunCMake_TEST_BINARY_DIR}/INSTALL/$<TARGET_SONAME_FILE_NAME:foo>")
+
+if (APPLE_TEXT_STUBS_SUPPORTED)
+  check_file("TBD file" "$<TARGET_IMPORT_FILE:foo>")
+  check_symlink("Linkable TBD file" "$<TARGET_LINKER_IMPORT_FILE:foo>")
+  check_symlink("SONAME TBD file" "$<TARGET_SONAME_IMPORT_FILE:foo>")
+
+  check_file("Installed TBD file" "${RunCMake_TEST_BINARY_DIR}/INSTALL/$<TARGET_IMPORT_FILE_NAME:foo>")
+  check_symlink("Installed Linkable TBD file" "${RunCMake_TEST_BINARY_DIR}/INSTALL/$<TARGET_LINKER_IMPORT_FILE_NAME:foo>")
+  check_symlink("Installed SONAME TBD file" "${RunCMake_TEST_BINARY_DIR}/INSTALL/$<TARGET_SONAME_IMPORT_FILE_NAME:foo>")
+
+  check_file("Installed TBD file" "${RunCMake_TEST_BINARY_DIR}/INSTALL2/dev1/$<TARGET_IMPORT_FILE_NAME:foo>")
+  check_symlink("Installed SONAME TBD file" "${RunCMake_TEST_BINARY_DIR}/INSTALL2/dev1/$<TARGET_SONAME_IMPORT_FILE_NAME:foo>")
+  check_nofile("Installed Linkable TBD file" "${RunCMake_TEST_BINARY_DIR}/INSTALL2/dev1/$<TARGET_LINKER_IMPORT_FILE_NAME:foo>")
+
+  check_installed("Installed Linkable TBD file" "${RunCMake_TEST_BINARY_DIR}/INSTALL2/dev2" "$<TARGET_LINKER_IMPORT_FILE_NAME:foo>")
+  check_nofile("Installed TBD file" "${RunCMake_TEST_BINARY_DIR}/INSTALL2/dev2/$<TARGET_IMPORT_FILE_NAME:foo>")
+  check_nofile("Installed SONAME TBD file" "${RunCMake_TEST_BINARY_DIR}/INSTALL2/dev2/$<TARGET_SONAME_IMPORT_FILE_NAME:foo>")
+
+  check_file("Installed TBD file" "${RunCMake_TEST_BINARY_DIR}/INSTALL3/$<TARGET_IMPORT_FILE_NAME:foo>")
+  check_symlink("Installed SONAME TBD file" "${RunCMake_TEST_BINARY_DIR}/INSTALL3/$<TARGET_SONAME_IMPORT_FILE_NAME:foo>")
+  check_nofile("Installed Linkable TBD file" "${RunCMake_TEST_BINARY_DIR}/INSTALL3/$<TARGET_LINKER_IMPORT_FILE_NAME:foo>")
+
+  check_file("Installed TBD file" "${RunCMake_TEST_BINARY_DIR}/INSTALL4/$<TARGET_IMPORT_FILE_NAME:foo>")
+  check_symlink("Installed SONAME TBD file" "${RunCMake_TEST_BINARY_DIR}/INSTALL4/$<TARGET_SONAME_IMPORT_FILE_NAME:foo>")
+  check_symlink("Installed Linkable TBD file" "${RunCMake_TEST_BINARY_DIR}/INSTALL4/$<TARGET_LINKER_IMPORT_FILE_NAME:foo>")
+endif()
+]])
+
+file (GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/LibraryWithVersions-$<CONFIG>-generated.cmake"
+  CONTENT "${GENERATE_CONTENT}")
diff --git a/Tests/RunCMake/AppleTextStubs/RunCMakeTest.cmake b/Tests/RunCMake/AppleTextStubs/RunCMakeTest.cmake
new file mode 100644
index 0000000..9ccd685
--- /dev/null
+++ b/Tests/RunCMake/AppleTextStubs/RunCMakeTest.cmake
@@ -0,0 +1,58 @@
+include(RunCMake)
+
+function(build_project test)
+  if (NOT RunCMake_GENERATOR_IS_MULTI_CONFIG)
+    set(RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Release)
+  endif()
+  run_cmake(${test})
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${test}-build)
+  set(RunCMake_TEST_NO_CLEAN 1)
+
+  run_cmake_command(${test}-build ${CMAKE_COMMAND} --build . --config Release)
+  if ("${ARGC}" GREATER "1")
+    # custom install step
+    cmake_language(CALL ${ARGV1})
+  else()
+    run_cmake_command(${test}-install ${CMAKE_COMMAND} --install . --config Release)
+  endif()
+endfunction()
+
+build_project(Simple)
+build_project(Framework)
+build_project(LibraryWithOutputs)
+
+
+function(LibraryWithVersions-install)
+  run_cmake_command(LibraryWithVersions-install-component-lib3 ${CMAKE_COMMAND} --install . --config Release --component lib3)
+  run_cmake_command(LibraryWithVersions-install-component-lib4 ${CMAKE_COMMAND} --install . --config Release --component lib4)
+  run_cmake_command(LibraryWithVersions-install-components-dev4 ${CMAKE_COMMAND} --install . --config Release --component dev4)
+  run_cmake_command(LibraryWithVersions-install ${CMAKE_COMMAND} --install . --config Release  --component default)
+endfunction()
+
+build_project(LibraryWithVersions LibraryWithVersions-install)
+
+
+function(build_ExportImport_project test)
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${test}-export-build)
+  set(CMAKE_INSTALL_PREFIX ${RunCMake_TEST_BINARY_DIR}/root)
+  if (NOT RunCMake_GENERATOR_IS_MULTI_CONFIG)
+    set(RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Release)
+  endif()
+  run_cmake(${test}-export)
+  unset(RunCMake_TEST_OPTIONS)
+  set(RunCMake_TEST_NO_CLEAN 1)
+  run_cmake_command(${test}-export-build ${CMAKE_COMMAND} --build . --config Release)
+  run_cmake_command(${test}-export-install ${CMAKE_COMMAND} --install . --prefix ${CMAKE_INSTALL_PREFIX} --config Release)
+
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${test}-import-build)
+  set (foo_BUILD "${RunCMake_BINARY_DIR}/${test}-export-build")
+  if (RunCMake_GENERATOR_IS_MULTI_CONFIG)
+    string (APPEND foo_BUILD "/Release")
+  endif()
+  run_cmake_with_options(${test}-import -Dfoo_DIR=${CMAKE_INSTALL_PREFIX}/lib/foo
+                                        -Dfoo_BUILD=${RunCMake_BINARY_DIR}/${test}-export-build/Release)
+  run_cmake_command(${test}-import-build ${CMAKE_COMMAND} --build . --config Release)
+endfunction()
+
+build_ExportImport_project(Library)
+build_ExportImport_project(Framework)
diff --git a/Tests/RunCMake/AppleTextStubs/Simple-install-check.cmake b/Tests/RunCMake/AppleTextStubs/Simple-install-check.cmake
new file mode 100644
index 0000000..94054fa
--- /dev/null
+++ b/Tests/RunCMake/AppleTextStubs/Simple-install-check.cmake
@@ -0,0 +1 @@
+include ("${RunCMake_TEST_BINARY_DIR}/Simple-Release-generated.cmake")
diff --git a/Tests/RunCMake/AppleTextStubs/Simple.cmake b/Tests/RunCMake/AppleTextStubs/Simple.cmake
new file mode 100644
index 0000000..9f6318c
--- /dev/null
+++ b/Tests/RunCMake/AppleTextStubs/Simple.cmake
@@ -0,0 +1,41 @@
+enable_language(C)
+
+add_library(foo SHARED foo.c)
+set_property(TARGET foo PROPERTY ENABLE_EXPORTS TRUE)
+
+add_executable(main main.c)
+target_link_libraries(main PRIVATE foo)
+
+
+install(TARGETS foo DESTINATION "${CMAKE_BINARY_DIR}/INSTALL")
+
+install(TARGETS foo LIBRARY DESTINATION "${CMAKE_BINARY_DIR}/INSTALL2/lib"
+                    ARCHIVE DESTINATION "${CMAKE_BINARY_DIR}/INSTALL2/dev")
+
+
+set (GENERATE_CONTENT "if (\"${CMAKE_TAPI}\")
+  set (APPLE_TEXT_STUBS_SUPPORTED TRUE)
+endif()\n\n")
+
+string (APPEND GENERATE_CONTENT [[
+macro (CHECK_FILE test_msg path)
+  if (NOT EXISTS "${path}")
+    string (APPEND RunCMake_TEST_FAILED "${test_msg}: \"${path}\" not found\n")
+  endif()
+endmacro()
+
+check_file("DYLIB file" "$<TARGET_FILE:foo>")
+check_file("executable file" "$<TARGET_FILE:main>")
+
+check_file("Installed DYLIB file" "${RunCMake_TEST_BINARY_DIR}/INSTALL2/lib/$<TARGET_FILE_NAME:foo>")
+
+if (APPLE_TEXT_STUBS_SUPPORTED)
+  check_file("TBD file" "$<TARGET_IMPORT_FILE:foo>")
+
+  check_file("Installed TBD file" "${RunCMake_TEST_BINARY_DIR}/INSTALL/$<TARGET_IMPORT_FILE_NAME:foo>")
+  check_file("Installed TBD file" "${RunCMake_TEST_BINARY_DIR}/INSTALL2/dev/$<TARGET_IMPORT_FILE_NAME:foo>")
+endif()
+]])
+
+file (GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/Simple-$<CONFIG>-generated.cmake"
+  CONTENT "${GENERATE_CONTENT}")
diff --git a/Tests/RunCMake/AppleTextStubs/foo-config.cmake.in b/Tests/RunCMake/AppleTextStubs/foo-config.cmake.in
new file mode 100644
index 0000000..b038138
--- /dev/null
+++ b/Tests/RunCMake/AppleTextStubs/foo-config.cmake.in
@@ -0,0 +1 @@
+include(${CMAKE_CURRENT_LIST_DIR}/foo.cmake)
diff --git a/Tests/RunCMake/AppleTextStubs/foo.c b/Tests/RunCMake/AppleTextStubs/foo.c
new file mode 100644
index 0000000..7f39d71
--- /dev/null
+++ b/Tests/RunCMake/AppleTextStubs/foo.c
@@ -0,0 +1,5 @@
+
+int foo()
+{
+  return 0;
+}
diff --git a/Tests/RunCMake/AppleTextStubs/main.c b/Tests/RunCMake/AppleTextStubs/main.c
new file mode 100644
index 0000000..dc5ce3d
--- /dev/null
+++ b/Tests/RunCMake/AppleTextStubs/main.c
@@ -0,0 +1,7 @@
+
+extern int foo(void);
+
+int main()
+{
+  return foo();
+}
diff --git a/Tests/RunCMake/ArtifactOutputDirs/RunCMakeTest.cmake b/Tests/RunCMake/ArtifactOutputDirs/RunCMakeTest.cmake
deleted file mode 100644
index 1bf8438..0000000
--- a/Tests/RunCMake/ArtifactOutputDirs/RunCMakeTest.cmake
+++ /dev/null
@@ -1,19 +0,0 @@
-include(RunCMake)
-
-function(run_cmake_and_verify_after_build case)
-  set(RunCMake_TEST_BINARY_DIR "${RunCMake_BINARY_DIR}/${case}-build")
-  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
-  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
-  set(RunCMake_TEST_NO_CLEAN 1)
-  if(RunCMake_GENERATOR_IS_MULTI_CONFIG)
-    set(RunCMake_TEST_OPTIONS -DCMAKE_CONFIGURATION_TYPES=Debug)
-  else()
-    set(RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Debug)
-  endif()
-  run_cmake(${case})
-  run_cmake_command("${case}-build" ${CMAKE_COMMAND} --build .)
-  unset(RunCMake_TEST_NO_CLEAN)
-  unset(RunCMake_TEST_BINARY_DIR)
-endfunction()
-
-run_cmake_and_verify_after_build(ArtifactOutputDirs)
diff --git a/Tests/RunCMake/AutoExportDll/CMakeLists.txt b/Tests/RunCMake/AutoExportDll/CMakeLists.txt
index 18dfd26..93ee9df 100644
--- a/Tests/RunCMake/AutoExportDll/CMakeLists.txt
+++ b/Tests/RunCMake/AutoExportDll/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.2)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/AutoExportDll/hello.cxx b/Tests/RunCMake/AutoExportDll/hello.cxx
index 74e7a4e..35ccbb7 100644
--- a/Tests/RunCMake/AutoExportDll/hello.cxx
+++ b/Tests/RunCMake/AutoExportDll/hello.cxx
@@ -12,3 +12,12 @@
 }
 void Hello::operator delete[](void*){};
 void Hello::operator delete(void*){};
+
+#ifdef HELLO_VFTABLE
+HelloVFTable::HelloVFTable()
+{
+}
+HelloVFTable::~HelloVFTable()
+{
+}
+#endif
diff --git a/Tests/RunCMake/AutoExportDll/hello.h b/Tests/RunCMake/AutoExportDll/hello.h
index 7192f65..410ffab 100644
--- a/Tests/RunCMake/AutoExportDll/hello.h
+++ b/Tests/RunCMake/AutoExportDll/hello.h
@@ -16,3 +16,20 @@
   static void operator delete[](void*);
   static void operator delete(void*);
 };
+
+// In the MSVC ABI, a delegating constructor references the vftable.
+#if __cplusplus >= 201103L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201103L)
+#  define HELLO_VFTABLE
+#endif
+#ifdef HELLO_VFTABLE
+class HelloVFTable
+{
+public:
+  HelloVFTable();
+  HelloVFTable(int)
+    : HelloVFTable()
+  {
+  }
+  virtual ~HelloVFTable();
+};
+#endif
diff --git a/Tests/RunCMake/AutoExportDll/say.cxx b/Tests/RunCMake/AutoExportDll/say.cxx
index 8fc768a..a9459a9 100644
--- a/Tests/RunCMake/AutoExportDll/say.cxx
+++ b/Tests/RunCMake/AutoExportDll/say.cxx
@@ -53,5 +53,8 @@
 #ifdef HAS_JUSTNOP
   justnop();
 #endif
+#ifdef HELLO_VFTABLE
+  HelloVFTable helloVFTable(1);
+#endif
   return 0;
 }
diff --git a/Tests/RunCMake/Autogen/AutogenSkipLinting-build-stderr.txt b/Tests/RunCMake/Autogen/AutogenSkipLinting-build-stderr.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/Autogen/AutogenSkipLinting-build-stderr.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/Autogen/AutogenSkipLinting.cmake b/Tests/RunCMake/Autogen/AutogenSkipLinting.cmake
new file mode 100644
index 0000000..3ce2092
--- /dev/null
+++ b/Tests/RunCMake/Autogen/AutogenSkipLinting.cmake
@@ -0,0 +1,16 @@
+enable_language(CXX)
+
+find_package(Qt${with_qt_version} REQUIRED COMPONENTS Core Widgets Gui)
+
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "$<1:${PSEUDO_IWYU}>" -some -args)
+set(CMAKE_CXX_CLANG_TIDY "$<1:${PSEUDO_TIDY}>" -bad)
+set(CMAKE_CXX_CPPLINT "$<1:${PSEUDO_CPPLINT}>" --error)
+set(CMAKE_CXX_CPPCHECK "$<1:${PSEUDO_CPPCHECK}>" -error)
+
+add_executable(SkipLinting SkipLinting.cxx SkipLinting.h)
+set_source_files_properties(SkipLinting.cxx PROPERTIES SKIP_LINTING TRUE)
+
+target_link_libraries(SkipLinting Qt${with_qt_version}::Core
+                                   Qt${with_qt_version}::Widgets
+                                   Qt${with_qt_version}::Gui)
diff --git a/Tests/RunCMake/Autogen/AutogenUseSystemIncludeCommon.cmake b/Tests/RunCMake/Autogen/AutogenUseSystemIncludeCommon.cmake
new file mode 100644
index 0000000..bbefd5f
--- /dev/null
+++ b/Tests/RunCMake/Autogen/AutogenUseSystemIncludeCommon.cmake
@@ -0,0 +1,10 @@
+enable_language(CXX)
+
+find_package(Qt${with_qt_version} REQUIRED COMPONENTS Core Widgets Gui)
+
+set(CMAKE_AUTOMOC ON)
+
+add_library(dummy SHARED empty.cpp)
+target_link_libraries(dummy Qt${with_qt_version}::Core
+                            Qt${with_qt_version}::Widgets
+                            Qt${with_qt_version}::Gui)
diff --git a/Tests/RunCMake/Autogen/AutogenUseSystemIncludeOff.cmake b/Tests/RunCMake/Autogen/AutogenUseSystemIncludeOff.cmake
new file mode 100644
index 0000000..dfdbb98
--- /dev/null
+++ b/Tests/RunCMake/Autogen/AutogenUseSystemIncludeOff.cmake
@@ -0,0 +1,3 @@
+include("${CMAKE_CURRENT_LIST_DIR}/AutogenUseSystemIncludeCommon.cmake")
+
+set_target_properties(dummy PROPERTIES AUTOGEN_USE_SYSTEM_INCLUDE OFF)
diff --git a/Tests/RunCMake/Autogen/AutogenUseSystemIncludeOn.cmake b/Tests/RunCMake/Autogen/AutogenUseSystemIncludeOn.cmake
new file mode 100644
index 0000000..f556ed4
--- /dev/null
+++ b/Tests/RunCMake/Autogen/AutogenUseSystemIncludeOn.cmake
@@ -0,0 +1,3 @@
+include("${CMAKE_CURRENT_LIST_DIR}/AutogenUseSystemIncludeCommon.cmake")
+
+set_target_properties(dummy PROPERTIES AUTOGEN_USE_SYSTEM_INCLUDE ON)
diff --git a/Tests/RunCMake/Autogen/CMP0151-common.cmake b/Tests/RunCMake/Autogen/CMP0151-common.cmake
new file mode 100644
index 0000000..bbefd5f
--- /dev/null
+++ b/Tests/RunCMake/Autogen/CMP0151-common.cmake
@@ -0,0 +1,10 @@
+enable_language(CXX)
+
+find_package(Qt${with_qt_version} REQUIRED COMPONENTS Core Widgets Gui)
+
+set(CMAKE_AUTOMOC ON)
+
+add_library(dummy SHARED empty.cpp)
+target_link_libraries(dummy Qt${with_qt_version}::Core
+                            Qt${with_qt_version}::Widgets
+                            Qt${with_qt_version}::Gui)
diff --git a/Tests/RunCMake/Autogen/CMP0151-new.cmake b/Tests/RunCMake/Autogen/CMP0151-new.cmake
new file mode 100644
index 0000000..9c77e58
--- /dev/null
+++ b/Tests/RunCMake/Autogen/CMP0151-new.cmake
@@ -0,0 +1 @@
+include("${CMAKE_CURRENT_LIST_DIR}/CMP0151-common.cmake")
diff --git a/Tests/RunCMake/Autogen/CMP0151-old.cmake b/Tests/RunCMake/Autogen/CMP0151-old.cmake
new file mode 100644
index 0000000..9c77e58
--- /dev/null
+++ b/Tests/RunCMake/Autogen/CMP0151-old.cmake
@@ -0,0 +1 @@
+include("${CMAKE_CURRENT_LIST_DIR}/CMP0151-common.cmake")
diff --git a/Tests/RunCMake/Autogen/Inspect.cmake b/Tests/RunCMake/Autogen/Inspect.cmake
new file mode 100644
index 0000000..d5dc4b4
--- /dev/null
+++ b/Tests/RunCMake/Autogen/Inspect.cmake
@@ -0,0 +1,13 @@
+enable_language(CXX)
+
+set(info "")
+foreach(var
+    CMAKE_INCLUDE_FLAG_CXX
+    CMAKE_INCLUDE_SYSTEM_FLAG_CXX
+    )
+  if(DEFINED ${var})
+    string(APPEND info "set(${var} \"${${var}}\")\n")
+  endif()
+endforeach()
+
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/info.cmake" "${info}")
diff --git a/Tests/RunCMake/Autogen/MocGeneratedFile.cmake b/Tests/RunCMake/Autogen/MocGeneratedFile.cmake
new file mode 100644
index 0000000..7bb55e9
--- /dev/null
+++ b/Tests/RunCMake/Autogen/MocGeneratedFile.cmake
@@ -0,0 +1,15 @@
+enable_language(CXX)
+
+find_package(Qt${with_qt_version} REQUIRED COMPONENTS Core)
+
+set(CMAKE_AUTOMOC ON)
+
+set(GEN_SRC "class_$<CONFIG>.cpp")
+add_custom_command(
+  OUTPUT "${GEN_SRC}"
+  COMMAND ${CMAKE_COMMAND} -E echo "// cpp src" > "${GEN_SRC}"
+  VERBATIM
+)
+
+add_library(libgen STATIC ${GEN_SRC})
+target_link_libraries(libgen Qt${with_qt_version}::Core)
diff --git a/Tests/RunCMake/Autogen/MocPredefs-build-stderr.txt b/Tests/RunCMake/Autogen/MocPredefs-build-stderr.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/Autogen/MocPredefs-build-stderr.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/Autogen/MocPredefs-check.cxx b/Tests/RunCMake/Autogen/MocPredefs-check.cxx
new file mode 100644
index 0000000..2b4791f
--- /dev/null
+++ b/Tests/RunCMake/Autogen/MocPredefs-check.cxx
@@ -0,0 +1,60 @@
+#include <iostream>
+
+#include "check_predefs.h"
+
+#define TO_STRING(x) TO_STRING0(x)
+#define TO_STRING0(x) #x
+
+int main()
+{
+  int ret = 0;
+#if defined(__STRICT_ANSI__)
+#  if !defined(CHECK___STRICT_ANSI__)
+  std::cout << "__STRICT_ANSI__: Expected " << TO_STRING(__STRICT_ANSI__)
+            << " but it is not defined.\n";
+  ret = 1;
+#  elif __STRICT_ANSI__ != CHECK___STRICT_ANSI__
+  std::cout << "__STRICT_ANSI__: Expected " << TO_STRING(__STRICT_ANSI__)
+            << " but got: " << TO_STRING(CHECK___STRICT_ANSI__) << "\n";
+  ret = 1;
+#  endif
+#elif defined(CHECK___STRICT_ANSI__)
+  std::cout << "__STRICT_ANSI__: Expected undefined but got: "
+            << TO_STRING(CHECK___STRICT_ANSI__) << "\n";
+  ret = 1;
+#endif
+
+#if defined(__cplusplus)
+#  if !defined(CHECK___cplusplus)
+  std::cout << "__cplusplus: Expected " << TO_STRING(__cplusplus)
+            << " but it is not defined.\n";
+  ret = 1;
+#  elif __cplusplus != CHECK___cplusplus
+  std::cout << "__cplusplus: Expected " << TO_STRING(__cplusplus)
+            << " but got: " << TO_STRING(CHECK___cplusplus) << "\n";
+  ret = 1;
+#  endif
+#elif defined(CHECK___cplusplus)
+  std::cout << "__cplusplus: Expected undefined but got: "
+            << TO_STRING(CHECK___cplusplus) << "\n";
+  ret = 1;
+#endif
+
+#if defined(_MSVC_LANG)
+#  if !defined(CHECK__MSVC_LANG)
+  std::cout << "_MSVC_LANG: Expected " << TO_STRING(_MSVC_LANG)
+            << " but it is not defined.\n";
+  ret = 1;
+#  elif _MSVC_LANG != CHECK__MSVC_LANG
+  std::cout << "_MSVC_LANG: Expected " << TO_STRING(_MSVC_LANG)
+            << " but got: " << TO_STRING(CHECK__MSVC_LANG) << "\n";
+  ret = 1;
+#  endif
+#elif defined(CHECK__MSVC_LANG)
+  std::cout << "_MSVC_LANG: Expected undefined but got: "
+            << TO_STRING(CHECK__MSVC_LANG) << "\n";
+  ret = 1;
+#endif
+
+  return ret;
+}
diff --git a/Tests/RunCMake/Autogen/MocPredefs-prefix.cmake b/Tests/RunCMake/Autogen/MocPredefs-prefix.cmake
new file mode 100644
index 0000000..460a05d
--- /dev/null
+++ b/Tests/RunCMake/Autogen/MocPredefs-prefix.cmake
@@ -0,0 +1,3 @@
+file(READ ${in} predefs)
+string(REGEX REPLACE "#define +" "#define CHECK_" predefs "${predefs}")
+file(WRITE ${out} "${predefs}")
diff --git a/Tests/RunCMake/Autogen/MocPredefs.cmake b/Tests/RunCMake/Autogen/MocPredefs.cmake
new file mode 100644
index 0000000..8307e04
--- /dev/null
+++ b/Tests/RunCMake/Autogen/MocPredefs.cmake
@@ -0,0 +1,39 @@
+enable_language(CXX)
+
+find_package(Qt${with_qt_version} REQUIRED COMPONENTS Core)
+
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_EXTENSIONS OFF)
+
+add_library(MocPredefs MocPredefs.cxx)
+
+if(NOT DEFINED CMAKE_CXX_COMPILER_PREDEFINES_COMMAND)
+  return()
+endif()
+
+if(CMAKE_CXX_COMPILER_ID STREQUAL "LCC" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "1.26")
+  return()
+endif()
+
+get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(_isMultiConfig)
+  set(moc_predefs_h "moc_predefs_$<CONFIG>.h")
+  set(check_dir "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>")
+else()
+  set(moc_predefs_h "moc_predefs.h")
+  set(check_dir "${CMAKE_CURRENT_BINARY_DIR}")
+endif()
+
+add_custom_command(TARGET MocPredefs POST_BUILD VERBATIM COMMAND
+  ${CMAKE_COMMAND}
+    -Din=${CMAKE_CURRENT_BINARY_DIR}/MocPredefs_autogen/${moc_predefs_h}
+    -Dout=${check_dir}/check_predefs.h
+    -P${CMAKE_CURRENT_SOURCE_DIR}/MocPredefs-prefix.cmake
+  )
+
+add_executable(MocPredefs-check MocPredefs-check.cxx)
+target_include_directories(MocPredefs-check PRIVATE ${check_dir})
+add_dependencies(MocPredefs-check MocPredefs)
+
+add_custom_target(MocPredefs-run-check ALL VERBATIM COMMAND MocPredefs-check)
diff --git a/Tests/RunCMake/Autogen/MocPredefs.cxx b/Tests/RunCMake/Autogen/MocPredefs.cxx
new file mode 100644
index 0000000..b27cec5
--- /dev/null
+++ b/Tests/RunCMake/Autogen/MocPredefs.cxx
@@ -0,0 +1,3 @@
+void MocPredefs()
+{
+}
diff --git a/Tests/RunCMake/Autogen/RunCMakeTest.cmake b/Tests/RunCMake/Autogen/RunCMakeTest.cmake
index e4c5811..4fe9406 100644
--- a/Tests/RunCMake/Autogen/RunCMakeTest.cmake
+++ b/Tests/RunCMake/Autogen/RunCMakeTest.cmake
@@ -15,4 +15,111 @@
   run_cmake(CMP0111-imported-target-full)
   run_cmake(CMP0111-imported-target-libname)
   run_cmake(CMP0111-imported-target-implib-only)
+
+  block()
+    set(RunCMake_TEST_BINARY_DIR  ${RunCMake_BINARY_DIR}/MocPredefs-build)
+    run_cmake(MocPredefs)
+    set(RunCMake_TEST_NO_CLEAN 1)
+    run_cmake_command(MocPredefs-build ${CMAKE_COMMAND} --build . --config Debug)
+  endblock()
+
+  # Detect information from the toolchain:
+  # - CMAKE_INCLUDE_FLAG_CXX
+  # - CMAKE_INCLUDE_SYSTEM_FLAG_CXX
+  run_cmake(Inspect)
+  include("${RunCMake_BINARY_DIR}/Inspect-build/info.cmake")
+
+  if(CMAKE_INCLUDE_SYSTEM_FLAG_CXX)
+    if(RunCMake_GENERATOR MATCHES "Visual Studio")
+      string(REGEX REPLACE "^-" "/" test_expect_stdout "${CMAKE_INCLUDE_SYSTEM_FLAG_CXX}")
+    else()
+      set(test_expect_stdout "-*${CMAKE_INCLUDE_SYSTEM_FLAG_CXX}")
+    endif()
+    string(APPEND test_expect_stdout " *(\"[^\"]*|([^ ]|\\ )*)[\\/]dummy_autogen[\\/]include")
+    if(RunCMake_GENERATOR_IS_MULTI_CONFIG)
+      string(APPEND test_expect_stdout "_Debug")
+    endif()
+
+    block()
+      set(RunCMake_TEST_BINARY_DIR  ${RunCMake_BINARY_DIR}/CMP0151-new-build)
+      run_cmake_with_options(CMP0151-new ${RunCMake_TEST_OPTIONS} -DCMAKE_POLICY_DEFAULT_CMP0151=NEW)
+      set(RunCMake_TEST_NO_CLEAN 1)
+      set(RunCMake_TEST_EXPECT_stdout "${test_expect_stdout}")
+      message(STATUS "RunCMake_TEST_EXPECT_stdout: ${RunCMake_TEST_EXPECT_stdout}")
+      run_cmake_command(CMP0151-new-build ${CMAKE_COMMAND} --build . --config Debug --verbose)
+    endblock()
+
+    block()
+      set(RunCMake_TEST_BINARY_DIR  ${RunCMake_BINARY_DIR}/AutogenUseSystemIncludeOn-build)
+      run_cmake_with_options(AutogenUseSystemIncludeOn ${RunCMake_TEST_OPTIONS} -DCMAKE_POLICY_DEFAULT_CMP0151=NEW)
+      set(RunCMake_TEST_NO_CLEAN 1)
+      set(RunCMake_TEST_EXPECT_stdout "${test_expect_stdout}")
+      message(STATUS "RunCMake_TEST_EXPECT_stdout: ${RunCMake_TEST_EXPECT_stdout}")
+      run_cmake_command(AutogenUseSystemIncludeOn ${CMAKE_COMMAND} --build . --config Debug --verbose)
+    endblock()
+  endif()
+
+  if(CMAKE_INCLUDE_FLAG_CXX)
+    if(RunCMake_GENERATOR MATCHES "Visual Studio")
+      string(REGEX REPLACE "^-" "/" test_expect_stdout "${CMAKE_INCLUDE_FLAG_CXX}")
+    else()
+      set(test_expect_stdout "-*${CMAKE_INCLUDE_FLAG_CXX}")
+    endif()
+    string(APPEND test_expect_stdout " *(\"[^\"]*|([^ ]|\\ )*)[\\/]dummy_autogen[\\/]include")
+    if(RunCMake_GENERATOR_IS_MULTI_CONFIG)
+      string(APPEND test_expect_stdout "_Debug")
+    endif()
+
+    block()
+      set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/CMP0151-old-build)
+      run_cmake_with_options(CMP0151-old ${RunCMake_TEST_OPTIONS} -DCMAKE_POLICY_DEFAULT_CMP0151=OLD)
+      set(RunCMake_TEST_NO_CLEAN 1)
+      set(RunCMake_TEST_EXPECT_stdout "${test_expect_stdout}")
+      message(STATUS "RunCMake_TEST_EXPECT_stdout: ${RunCMake_TEST_EXPECT_stdout}")
+      run_cmake_command(CMP0151-old-build ${CMAKE_COMMAND} --build . --config Debug --verbose)
+    endblock()
+
+    block()
+      set(RunCMake_TEST_BINARY_DIR  ${RunCMake_BINARY_DIR}/AutogenUseSystemIncludeOff-build)
+      run_cmake_with_options(AutogenUseSystemIncludeOff ${RunCMake_TEST_OPTIONS} -DCMAKE_POLICY_DEFAULT_CMP0151=NEW)
+      set(RunCMake_TEST_NO_CLEAN 1)
+      set(RunCMake_TEST_EXPECT_stdout "${test_expect_stdout}")
+      message(STATUS "RunCMake_TEST_EXPECT_stdout: ${RunCMake_TEST_EXPECT_stdout}")
+      run_cmake_command(AutogenUseSystemIncludeOff ${CMAKE_COMMAND} --build . --config Debug --verbose)
+    endblock()
+
+    if(RunCMake_GENERATOR MATCHES "Make|Ninja")
+      block()
+        set(RunCMake_TEST_BINARY_DIR  ${RunCMake_BINARY_DIR}/AutogenSkipLinting-build)
+        list(APPEND RunCMake_TEST_OPTIONS
+          "-DPSEUDO_CPPCHECK=${PSEUDO_CPPCHECK}"
+          "-DPSEUDO_CPPLINT=${PSEUDO_CPPLINT}"
+          "-DPSEUDO_IWYU=${PSEUDO_IWYU}"
+          "-DPSEUDO_TIDY=${PSEUDO_TIDY}")
+
+        run_cmake(AutogenSkipLinting)
+        set(RunCMake_TEST_NO_CLEAN 1)
+        run_cmake_command(AutogenSkipLinting-build ${CMAKE_COMMAND} --build . --config Debug --verbose)
+      endblock()
+    endif()
+  endif()
+
+  if(RunCMake_GENERATOR_IS_MULTI_CONFIG AND NOT RunCMake_GENERATOR MATCHES "Xcode")
+    block()
+      set(RunCMake_TEST_BINARY_DIR  ${RunCMake_BINARY_DIR}/MocGeneratedFile-build)
+      run_cmake(MocGeneratedFile)
+      set(RunCMake_TEST_NO_CLEAN 1)
+      run_cmake_command(MocGeneratedFile-build ${CMAKE_COMMAND} --build . --config Debug --verbose)
+    endblock()
+    if(RunCMake_GENERATOR MATCHES "Ninja Multi-Config")
+      block()
+        set(RunCMake_TEST_BINARY_DIR  ${RunCMake_BINARY_DIR}/MocGeneratedFile-cross-config-build)
+        list(APPEND RunCMake_TEST_OPTIONS -DCMAKE_CROSS_CONFIGS=all)
+        run_cmake(MocGeneratedFile)
+        set(RunCMake_TEST_NO_CLEAN 1)
+        run_cmake_command(MocGeneratedFile-cross-config-build ${CMAKE_COMMAND} --build . --config Release --target libgen:Debug)
+        run_cmake_command(MocGeneratedFile-cross-config-build ${CMAKE_COMMAND} --build . --config Debug --target libgen:Release)
+      endblock()
+    endif()
+  endif()
 endif ()
diff --git a/Tests/RunCMake/Autogen/SkipLinting.cxx b/Tests/RunCMake/Autogen/SkipLinting.cxx
new file mode 100644
index 0000000..9e09b27
--- /dev/null
+++ b/Tests/RunCMake/Autogen/SkipLinting.cxx
@@ -0,0 +1,6 @@
+#include "SkipLinting.h"
+
+int main()
+{
+  return 0;
+}
diff --git a/Tests/RunCMake/Autogen/SkipLinting.h b/Tests/RunCMake/Autogen/SkipLinting.h
new file mode 100644
index 0000000..def56a0
--- /dev/null
+++ b/Tests/RunCMake/Autogen/SkipLinting.h
@@ -0,0 +1,11 @@
+#ifndef SKIP_LINTING_H
+#define SKIP_LINTING_H
+
+#include <QObject>
+
+class SkipMe : public QObject
+{
+  Q_OBJECT
+};
+
+#endif
diff --git a/Tests/RunCMake/BuildDepends/CMakeLists.txt b/Tests/RunCMake/BuildDepends/CMakeLists.txt
index 99f238b..8eb5748 100644
--- a/Tests/RunCMake/BuildDepends/CMakeLists.txt
+++ b/Tests/RunCMake/BuildDepends/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake NO_POLICY_SCOPE)
diff --git a/Tests/RunCMake/BuildDepends/FortranInclude-build1-stderr.txt b/Tests/RunCMake/BuildDepends/FortranInclude-build1-stderr.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/BuildDepends/FortranInclude-build1-stderr.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/BuildDepends/FortranInclude-build2-stderr.txt b/Tests/RunCMake/BuildDepends/FortranInclude-build2-stderr.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/BuildDepends/FortranInclude-build2-stderr.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/BuildDepends/FortranInclude.cmake b/Tests/RunCMake/BuildDepends/FortranInclude.cmake
new file mode 100644
index 0000000..fa9f399
--- /dev/null
+++ b/Tests/RunCMake/BuildDepends/FortranInclude.cmake
@@ -0,0 +1,20 @@
+enable_language(Fortran)
+
+set(check_pairs "")
+
+add_executable(preprocess FortranIncludePreprocess.F)
+set_property(TARGET preprocess PROPERTY Fortran_PREPROCESS ON)
+target_include_directories(preprocess PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
+list(APPEND check_pairs "$<TARGET_FILE:preprocess>|${CMAKE_CURRENT_BINARY_DIR}/preprocess.inc")
+
+# LCC < 1.24 has no way to disable Fortran preprocessor
+if(NOT CMAKE_Fortran_COMPILER_ID STREQUAL "LCC" OR CMAKE_Fortran_COMPILER_VERSION VERSION_GREATER_EQUAL "1.24.00")
+  add_executable(no_preprocess FortranIncludeNoPreprocess.f)
+  set_property(TARGET no_preprocess PROPERTY Fortran_PREPROCESS OFF)
+  target_include_directories(no_preprocess PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
+  list(APPEND check_pairs "$<TARGET_FILE:no_preprocess>|${CMAKE_CURRENT_BINARY_DIR}/no_preprocess.inc")
+endif()
+
+file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/check-$<LOWER_CASE:$<CONFIG>>.cmake CONTENT "
+set(check_pairs \"${check_pairs}\")
+")
diff --git a/Tests/RunCMake/BuildDepends/FortranInclude.step1.cmake b/Tests/RunCMake/BuildDepends/FortranInclude.step1.cmake
new file mode 100644
index 0000000..53fdc2f
--- /dev/null
+++ b/Tests/RunCMake/BuildDepends/FortranInclude.step1.cmake
@@ -0,0 +1,2 @@
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/preprocess.inc" "\tPRINT *, 'FortranIncludePreprocess 1'\n")
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/no_preprocess.inc" "\tPRINT *, 'FortranIncludeNoPreprocess 1'\n")
diff --git a/Tests/RunCMake/BuildDepends/FortranInclude.step2.cmake b/Tests/RunCMake/BuildDepends/FortranInclude.step2.cmake
new file mode 100644
index 0000000..05a9f11
--- /dev/null
+++ b/Tests/RunCMake/BuildDepends/FortranInclude.step2.cmake
@@ -0,0 +1,2 @@
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/preprocess.inc" "\tPRINT *, 'FortranIncludePreprocess 2'\n")
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/no_preprocess.inc" "\tPRINT *, 'FortranIncludeNoPreprocess 2'\n")
diff --git a/Tests/RunCMake/BuildDepends/FortranIncludeNoPreprocess.f b/Tests/RunCMake/BuildDepends/FortranIncludeNoPreprocess.f
new file mode 100644
index 0000000..00b04dc
--- /dev/null
+++ b/Tests/RunCMake/BuildDepends/FortranIncludeNoPreprocess.f
@@ -0,0 +1,3 @@
+        PROGRAM FortranIncludeNoPreprocess
+        INCLUDE 'no_preprocess.inc'
+        END
diff --git a/Tests/RunCMake/BuildDepends/FortranIncludePreprocess.F b/Tests/RunCMake/BuildDepends/FortranIncludePreprocess.F
new file mode 100644
index 0000000..680313a
--- /dev/null
+++ b/Tests/RunCMake/BuildDepends/FortranIncludePreprocess.F
@@ -0,0 +1,3 @@
+        PROGRAM FortranIncludePreprocess
+#include "preprocess.inc"
+        END
diff --git a/Tests/RunCMake/BuildDepends/LinkDepends.cmake b/Tests/RunCMake/BuildDepends/LinkDepends.cmake
new file mode 100644
index 0000000..a414e03
--- /dev/null
+++ b/Tests/RunCMake/BuildDepends/LinkDepends.cmake
@@ -0,0 +1,22 @@
+
+enable_language(C)
+
+include("${CMAKE_BINARY_DIR}/../LinkDependsExternalLibrary-build/ExternalLibrary-debug.cmake")
+cmake_path(GET EXTERNAL_LIBRARY PARENT_PATH EXTERNAL_DIR)
+
+add_library(LinkDependsLib SHARED "${CMAKE_CURRENT_BINARY_DIR}/lib_depends.c")
+target_link_directories(LinkDependsLib PRIVATE "${EXTERNAL_DIR}")
+target_link_libraries(LinkDependsLib PRIVATE External)
+
+add_executable(LinkDependsExe "${CMAKE_CURRENT_BINARY_DIR}/exe_depends.c")
+target_link_directories(LinkDependsExe PRIVATE "${EXTERNAL_DIR}")
+target_link_libraries(LinkDependsExe PRIVATE External)
+
+
+file(GENERATE  OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/check-$<LOWER_CASE:$<CONFIG>>.cmake"
+  CONTENT "
+set(check_pairs
+  \"$<TARGET_FILE:LinkDependsLib>|${EXTERNAL_LIBRARY}\"
+  \"$<TARGET_FILE:LinkDependsExe>|${EXTERNAL_LIBRARY}\"
+  )
+")
diff --git a/Tests/RunCMake/BuildDepends/LinkDepends.step1.cmake b/Tests/RunCMake/BuildDepends/LinkDepends.step1.cmake
new file mode 100644
index 0000000..5ce55b0
--- /dev/null
+++ b/Tests/RunCMake/BuildDepends/LinkDepends.step1.cmake
@@ -0,0 +1,23 @@
+
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/lib_depends.c" [[
+
+extern void external(void);
+
+void lib_depends(void)
+{
+  external();
+}
+]])
+
+
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/exe_depends.c" [[
+
+extern void external(void);
+
+int main(void)
+{
+  external();
+
+  return 0;
+}
+]])
diff --git a/Tests/RunCMake/BuildDepends/LinkDepends.step2.cmake b/Tests/RunCMake/BuildDepends/LinkDepends.step2.cmake
new file mode 100644
index 0000000..f2c0067
--- /dev/null
+++ b/Tests/RunCMake/BuildDepends/LinkDepends.step2.cmake
@@ -0,0 +1,4 @@
+
+include ("${RunCMake_TEST_BINARY_DIR}/../LinkDependsExternalLibrary-build/ExternalLibrary-debug.cmake")
+
+file(TOUCH "${EXTERNAL_LIBRARY}")
diff --git a/Tests/RunCMake/BuildDepends/LinkDependsCheck.cmake b/Tests/RunCMake/BuildDepends/LinkDependsCheck.cmake
new file mode 100644
index 0000000..a21096b
--- /dev/null
+++ b/Tests/RunCMake/BuildDepends/LinkDependsCheck.cmake
@@ -0,0 +1,11 @@
+
+enable_language(C)
+
+file(WRITE "${CMAKE_BINARY_DIR}/LinkDependsUseLinker.cmake"
+     "set(CMAKE_C_LINK_DEPENDS_USE_LINKER \"${CMAKE_C_LINK_DEPENDS_USE_LINKER}\")\n")
+
+
+file(GENERATE  OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/check-$<LOWER_CASE:$<CONFIG>>.cmake"
+  CONTENT "
+# no required actions
+")
diff --git a/Tests/RunCMake/BuildDepends/LinkDependsExternalLibrary.cmake b/Tests/RunCMake/BuildDepends/LinkDependsExternalLibrary.cmake
new file mode 100644
index 0000000..fe6575c
--- /dev/null
+++ b/Tests/RunCMake/BuildDepends/LinkDependsExternalLibrary.cmake
@@ -0,0 +1,13 @@
+
+enable_language(C)
+
+add_library(External SHARED "${CMAKE_CURRENT_BINARY_DIR}/external.c")
+
+file(GENERATE OUTPUT "${CMAKE_BINARY_DIR}/ExternalLibrary-$<LOWER_CASE:$<CONFIG>>.cmake"
+  CONTENT "set(EXTERNAL_LIBRARY \"$<TARGET_LINKER_FILE:External>\")\n")
+
+
+file(GENERATE  OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/check-$<LOWER_CASE:$<CONFIG>>.cmake"
+  CONTENT "
+# no required actions
+")
diff --git a/Tests/RunCMake/BuildDepends/LinkDependsExternalLibrary.step1.cmake b/Tests/RunCMake/BuildDepends/LinkDependsExternalLibrary.step1.cmake
new file mode 100644
index 0000000..df302f7
--- /dev/null
+++ b/Tests/RunCMake/BuildDepends/LinkDependsExternalLibrary.step1.cmake
@@ -0,0 +1,11 @@
+
+file(WRITE "${RunCMake_TEST_BINARY_DIR}/external.c" [[
+
+
+#if defined(_WIN32)
+__declspec(dllexport)
+#endif
+  void external(void)
+{
+}
+]])
diff --git a/Tests/RunCMake/BuildDepends/RunCMakeTest.cmake b/Tests/RunCMake/BuildDepends/RunCMakeTest.cmake
index 8099079..dfa4f49 100644
--- a/Tests/RunCMake/BuildDepends/RunCMakeTest.cmake
+++ b/Tests/RunCMake/BuildDepends/RunCMakeTest.cmake
@@ -68,6 +68,10 @@
   unset(run_BuildDepends_skip_step_2)
 endif()
 
+if(CMake_TEST_Fortran)
+  run_BuildDepends(FortranInclude)
+endif()
+
 run_BuildDepends(Custom-Symbolic-and-Byproduct)
 run_BuildDepends(Custom-Always)
 
@@ -194,3 +198,15 @@
 endif()
 run_BuildDepends(CustomCommandUnityBuild)
 unset(run_BuildDepends_skip_step_2)
+
+#if (RunCMake_GENERATOR MATCHES "Make|Ninja" AND CMAKE_C_LINK_DEPENDS_USE_LINKER)
+if (RunCMake_GENERATOR MATCHES "Make|Ninja")
+  set(run_BuildDepends_skip_step_2 1)
+  run_BuildDepends(LinkDependsCheck)
+  include("${RunCMake_BINARY_DIR}/LinkDependsCheck-build/LinkDependsUseLinker.cmake")
+  if (CMAKE_C_LINK_DEPENDS_USE_LINKER)
+    run_BuildDepends(LinkDependsExternalLibrary)
+    unset(run_BuildDepends_skip_step_2)
+    run_BuildDepends(LinkDepends)
+  endif()
+endif()
diff --git a/Tests/RunCMake/BundleUtilities/CMakeLists.txt b/Tests/RunCMake/BundleUtilities/CMakeLists.txt
index 6dd8cdf..44025d3 100644
--- a/Tests/RunCMake/BundleUtilities/CMakeLists.txt
+++ b/Tests/RunCMake/BundleUtilities/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.4)
+cmake_minimum_required(VERSION 3.12)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/BundleUtilities/RunCMakeTest.cmake b/Tests/RunCMake/BundleUtilities/RunCMakeTest.cmake
index df28102..a7b05d2 100644
--- a/Tests/RunCMake/BundleUtilities/RunCMakeTest.cmake
+++ b/Tests/RunCMake/BundleUtilities/RunCMakeTest.cmake
@@ -1,4 +1,3 @@
-cmake_minimum_required(VERSION 3.4)
 include(RunCMake)
 
 # TODO Migrate Tests/BundleUtilities here
diff --git a/Tests/RunCMake/Byproducts/CleanByproducts.cmake b/Tests/RunCMake/Byproducts/CleanByproducts.cmake
index 85d9582..961deb9 100644
--- a/Tests/RunCMake/Byproducts/CleanByproducts.cmake
+++ b/Tests/RunCMake/Byproducts/CleanByproducts.cmake
@@ -1,5 +1,5 @@
-cmake_minimum_required(VERSION 3.10)
-project(CleanByproducts)
+enable_language(C)
+enable_language(CXX)
 
 # Configurable parameters
 set(TEST_CLEAN_NO_CUSTOM FALSE CACHE BOOL "Value for the CLEAN_NO_CUSTOM PROPERTY")
diff --git a/Tests/RunCMake/CMP0004/CMP0004-NEW.cmake b/Tests/RunCMake/CMP0004/CMP0004-NEW.cmake
index f42d8e4..3d861fb 100644
--- a/Tests/RunCMake/CMP0004/CMP0004-NEW.cmake
+++ b/Tests/RunCMake/CMP0004/CMP0004-NEW.cmake
@@ -1,6 +1,3 @@
-
-cmake_minimum_required(VERSION 2.8.4)
-
 cmake_policy(SET CMP0004 NEW)
 
 add_library(foo SHARED empty.cpp)
diff --git a/Tests/RunCMake/CMP0004/CMP0004-OLD.cmake b/Tests/RunCMake/CMP0004/CMP0004-OLD.cmake
index 3fa58b6..32c1474 100644
--- a/Tests/RunCMake/CMP0004/CMP0004-OLD.cmake
+++ b/Tests/RunCMake/CMP0004/CMP0004-OLD.cmake
@@ -1,6 +1,3 @@
-
-cmake_minimum_required(VERSION 2.8.4)
-
 cmake_policy(SET CMP0004 OLD)
 
 add_library(foo SHARED empty.cpp)
diff --git a/Tests/RunCMake/CMP0004/CMP0004-policy-genex.cmake b/Tests/RunCMake/CMP0004/CMP0004-policy-genex.cmake
index 2970476..b7cd7ff 100644
--- a/Tests/RunCMake/CMP0004/CMP0004-policy-genex.cmake
+++ b/Tests/RunCMake/CMP0004/CMP0004-policy-genex.cmake
@@ -1,6 +1,3 @@
-
-cmake_minimum_required(VERSION 2.8.4)
-
 cmake_policy(SET CMP0004 NEW)
 
 add_library(foo SHARED empty.cpp)
diff --git a/Tests/RunCMake/CMP0004/CMakeLists.txt b/Tests/RunCMake/CMP0004/CMakeLists.txt
index 12cd3c7..93ee9df 100644
--- a/Tests/RunCMake/CMP0004/CMakeLists.txt
+++ b/Tests/RunCMake/CMP0004/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 2.8.4)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/CMP0019/CMP0019-NEW-stderr.txt b/Tests/RunCMake/CMP0019/CMP0019-NEW-stderr.txt
deleted file mode 100644
index 66a58fb..0000000
--- a/Tests/RunCMake/CMP0019/CMP0019-NEW-stderr.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-^CMake Deprecation Warning at CMakeLists.txt:1 \(cmake_minimum_required\):
-  Compatibility with CMake < 2.8.12 will be removed from a future version of
-  CMake.
-
-  Update the VERSION argument <min> value or use a ...<max> suffix to tell
-  CMake that the project does not need compatibility with older versions.$
diff --git a/Tests/RunCMake/CMP0019/CMP0019-OLD-stderr.txt b/Tests/RunCMake/CMP0019/CMP0019-OLD-stderr.txt
index a446211..dc03414 100644
--- a/Tests/RunCMake/CMP0019/CMP0019-OLD-stderr.txt
+++ b/Tests/RunCMake/CMP0019/CMP0019-OLD-stderr.txt
@@ -1,13 +1,6 @@
-^CMake Deprecation Warning at CMakeLists.txt:1 \(cmake_minimum_required\):
-  Compatibility with CMake < 2.8.12 will be removed from a future version of
-  CMake.
-
-  Update the VERSION argument <min> value or use a ...<max> suffix to tell
-  CMake that the project does not need compatibility with older versions.
-+
-CMake Deprecation Warning at CMP0019-OLD.cmake:[0-9]+ \(cmake_policy\):
+^CMake Deprecation Warning at CMP0019-OLD\.cmake:[0-9]+ \(cmake_policy\):
   The OLD behavior for policy CMP0019 will be removed from a future version
-  of CMake.
+  of CMake\.
 
   The cmake-policies\(7\) manual explains that the OLD behaviors of all
   policies are deprecated and that a policy should be set to OLD only under
diff --git a/Tests/RunCMake/CMP0019/CMP0019-WARN-stderr.txt b/Tests/RunCMake/CMP0019/CMP0019-WARN-stderr.txt
index f7b9c0e..6eee437 100644
--- a/Tests/RunCMake/CMP0019/CMP0019-WARN-stderr.txt
+++ b/Tests/RunCMake/CMP0019/CMP0019-WARN-stderr.txt
@@ -1,11 +1,4 @@
-^CMake Deprecation Warning at CMakeLists.txt:1 \(cmake_minimum_required\):
-  Compatibility with CMake < 2.8.12 will be removed from a future version of
-  CMake.
-
-  Update the VERSION argument <min> value or use a ...<max> suffix to tell
-  CMake that the project does not need compatibility with older versions.
-+
-CMake Warning \(dev\) in CMakeLists.txt:
+^CMake Warning \(dev\) in CMakeLists\.txt:
   Policy CMP0019 is not set: Do not re-expand variables in include and link
   information.  Run "cmake --help-policy CMP0019" for policy details.  Use
   the cmake_policy command to set the policy and suppress this warning.
diff --git a/Tests/RunCMake/CMP0019/RunCMakeTest.cmake b/Tests/RunCMake/CMP0019/RunCMakeTest.cmake
index 119fc2b..fcd080f 100644
--- a/Tests/RunCMake/CMP0019/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMP0019/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 run_cmake(CMP0019-WARN)
 run_cmake(CMP0019-OLD)
diff --git a/Tests/RunCMake/CMP0022/CMP0022-NOWARN-exe-stderr.txt b/Tests/RunCMake/CMP0022/CMP0022-NOWARN-exe-stderr.txt
deleted file mode 100644
index 66a58fb..0000000
--- a/Tests/RunCMake/CMP0022/CMP0022-NOWARN-exe-stderr.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-^CMake Deprecation Warning at CMakeLists.txt:1 \(cmake_minimum_required\):
-  Compatibility with CMake < 2.8.12 will be removed from a future version of
-  CMake.
-
-  Update the VERSION argument <min> value or use a ...<max> suffix to tell
-  CMake that the project does not need compatibility with older versions.$
diff --git a/Tests/RunCMake/CMP0022/CMP0022-NOWARN-shared-stderr.txt b/Tests/RunCMake/CMP0022/CMP0022-NOWARN-shared-stderr.txt
deleted file mode 100644
index 66a58fb..0000000
--- a/Tests/RunCMake/CMP0022/CMP0022-NOWARN-shared-stderr.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-^CMake Deprecation Warning at CMakeLists.txt:1 \(cmake_minimum_required\):
-  Compatibility with CMake < 2.8.12 will be removed from a future version of
-  CMake.
-
-  Update the VERSION argument <min> value or use a ...<max> suffix to tell
-  CMake that the project does not need compatibility with older versions.$
diff --git a/Tests/RunCMake/CMP0022/CMP0022-NOWARN-static-NEW-stderr.txt b/Tests/RunCMake/CMP0022/CMP0022-NOWARN-static-NEW-stderr.txt
deleted file mode 100644
index 66a58fb..0000000
--- a/Tests/RunCMake/CMP0022/CMP0022-NOWARN-static-NEW-stderr.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-^CMake Deprecation Warning at CMakeLists.txt:1 \(cmake_minimum_required\):
-  Compatibility with CMake < 2.8.12 will be removed from a future version of
-  CMake.
-
-  Update the VERSION argument <min> value or use a ...<max> suffix to tell
-  CMake that the project does not need compatibility with older versions.$
diff --git a/Tests/RunCMake/CMP0022/CMP0022-NOWARN-static-link_libraries-stderr.txt b/Tests/RunCMake/CMP0022/CMP0022-NOWARN-static-link_libraries-stderr.txt
deleted file mode 100644
index 66a58fb..0000000
--- a/Tests/RunCMake/CMP0022/CMP0022-NOWARN-static-link_libraries-stderr.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-^CMake Deprecation Warning at CMakeLists.txt:1 \(cmake_minimum_required\):
-  Compatibility with CMake < 2.8.12 will be removed from a future version of
-  CMake.
-
-  Update the VERSION argument <min> value or use a ...<max> suffix to tell
-  CMake that the project does not need compatibility with older versions.$
diff --git a/Tests/RunCMake/CMP0022/CMP0022-NOWARN-static-stderr.txt b/Tests/RunCMake/CMP0022/CMP0022-NOWARN-static-stderr.txt
deleted file mode 100644
index 66a58fb..0000000
--- a/Tests/RunCMake/CMP0022/CMP0022-NOWARN-static-stderr.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-^CMake Deprecation Warning at CMakeLists.txt:1 \(cmake_minimum_required\):
-  Compatibility with CMake < 2.8.12 will be removed from a future version of
-  CMake.
-
-  Update the VERSION argument <min> value or use a ...<max> suffix to tell
-  CMake that the project does not need compatibility with older versions.$
diff --git a/Tests/RunCMake/CMP0022/CMP0022-WARN-empty-old-stderr.txt b/Tests/RunCMake/CMP0022/CMP0022-WARN-empty-old-stderr.txt
index 87404d3..c84a289 100644
--- a/Tests/RunCMake/CMP0022/CMP0022-WARN-empty-old-stderr.txt
+++ b/Tests/RunCMake/CMP0022/CMP0022-WARN-empty-old-stderr.txt
@@ -1,11 +1,4 @@
-^CMake Deprecation Warning at CMakeLists.txt:1 \(cmake_minimum_required\):
-  Compatibility with CMake < 2.8.12 will be removed from a future version of
-  CMake.
-
-  Update the VERSION argument <min> value or use a ...<max> suffix to tell
-  CMake that the project does not need compatibility with older versions.
-+
-CMake Warning \(dev\) in CMakeLists.txt:
+^CMake Warning \(dev\) in CMakeLists\.txt:
   Policy CMP0022 is not set: INTERFACE_LINK_LIBRARIES defines the link
   interface.  Run "cmake --help-policy CMP0022" for policy details.  Use the
   cmake_policy command to set the policy and suppress this warning.
diff --git a/Tests/RunCMake/CMP0022/CMP0022-WARN-stderr.txt b/Tests/RunCMake/CMP0022/CMP0022-WARN-stderr.txt
index 5d75720..39a9511 100644
--- a/Tests/RunCMake/CMP0022/CMP0022-WARN-stderr.txt
+++ b/Tests/RunCMake/CMP0022/CMP0022-WARN-stderr.txt
@@ -1,11 +1,4 @@
-^CMake Deprecation Warning at CMakeLists.txt:1 \(cmake_minimum_required\):
-  Compatibility with CMake < 2.8.12 will be removed from a future version of
-  CMake.
-
-  Update the VERSION argument <min> value or use a ...<max> suffix to tell
-  CMake that the project does not need compatibility with older versions.
-+
-CMake Warning \(dev\) in CMakeLists.txt:
+^CMake Warning \(dev\) in CMakeLists\.txt:
   Policy CMP0022 is not set: INTERFACE_LINK_LIBRARIES defines the link
   interface.  Run "cmake --help-policy CMP0022" for policy details.  Use the
   cmake_policy command to set the policy and suppress this warning.
diff --git a/Tests/RunCMake/CMP0022/CMP0022-export-exe-stderr.txt b/Tests/RunCMake/CMP0022/CMP0022-export-exe-stderr.txt
deleted file mode 100644
index 66a58fb..0000000
--- a/Tests/RunCMake/CMP0022/CMP0022-export-exe-stderr.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-^CMake Deprecation Warning at CMakeLists.txt:1 \(cmake_minimum_required\):
-  Compatibility with CMake < 2.8.12 will be removed from a future version of
-  CMake.
-
-  Update the VERSION argument <min> value or use a ...<max> suffix to tell
-  CMake that the project does not need compatibility with older versions.$
diff --git a/Tests/RunCMake/CMP0022/RunCMakeTest.cmake b/Tests/RunCMake/CMP0022/RunCMakeTest.cmake
index 4c10996..ea956fc 100644
--- a/Tests/RunCMake/CMP0022/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMP0022/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 run_cmake(CMP0022-WARN)
 run_cmake(CMP0022-WARN-tll)
diff --git a/Tests/RunCMake/CMP0026/CMP0026-IMPORTED-stderr.txt b/Tests/RunCMake/CMP0026/CMP0026-IMPORTED-stderr.txt
new file mode 100644
index 0000000..259eabd
--- /dev/null
+++ b/Tests/RunCMake/CMP0026/CMP0026-IMPORTED-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at CMP0026-IMPORTED.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0111 will be removed from a future version
+  of CMake.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/CMP0026/RunCMakeTest.cmake b/Tests/RunCMake/CMP0026/RunCMakeTest.cmake
index 047da28..6476176 100644
--- a/Tests/RunCMake/CMP0026/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMP0026/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 run_cmake(CMP0026-WARN)
 run_cmake(CMP0026-OLD)
diff --git a/Tests/RunCMake/CMP0037/RunCMakeTest.cmake b/Tests/RunCMake/CMP0037/RunCMakeTest.cmake
index 5952279..558fba3 100644
--- a/Tests/RunCMake/CMP0037/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMP0037/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 if(RunCMake_GENERATOR MATCHES "^Ninja")
   # Detect ninja version so we know what tests can be supported.
diff --git a/Tests/RunCMake/CMP0038/RunCMakeTest.cmake b/Tests/RunCMake/CMP0038/RunCMakeTest.cmake
index fc3500a..3e7b5f3 100644
--- a/Tests/RunCMake/CMP0038/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMP0038/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 run_cmake(CMP0038-WARN)
 run_cmake(CMP0038-NEW)
diff --git a/Tests/RunCMake/CMP0039/RunCMakeTest.cmake b/Tests/RunCMake/CMP0039/RunCMakeTest.cmake
index 58e8ea9..ce7541a 100644
--- a/Tests/RunCMake/CMP0039/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMP0039/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 run_cmake(CMP0039-WARN)
 run_cmake(CMP0039-NEW)
diff --git a/Tests/RunCMake/CMP0040/RunCMakeTest.cmake b/Tests/RunCMake/CMP0040/RunCMakeTest.cmake
index 13160e3..e5e6c37 100644
--- a/Tests/RunCMake/CMP0040/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMP0040/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 run_cmake(CMP0040-OLD-missing-target)
 run_cmake(CMP0040-NEW-missing-target)
diff --git a/Tests/RunCMake/CMP0041/RunCMakeTest.cmake b/Tests/RunCMake/CMP0041/RunCMakeTest.cmake
index f47bb2e..93378c2 100644
--- a/Tests/RunCMake/CMP0041/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMP0041/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 # Protect tests from running inside the default install prefix.
 set(RunCMake_TEST_OPTIONS "-DCMAKE_INSTALL_PREFIX=${RunCMake_BINARY_DIR}/NotDefaultPrefix")
diff --git a/Tests/RunCMake/CMP0042/RunCMakeTest.cmake b/Tests/RunCMake/CMP0042/RunCMakeTest.cmake
index 3b226d7..6b23145 100644
--- a/Tests/RunCMake/CMP0042/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMP0042/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 run_cmake(CMP0042-OLD)
 run_cmake(CMP0042-NEW)
diff --git a/Tests/RunCMake/CMP0043/RunCMakeTest.cmake b/Tests/RunCMake/CMP0043/RunCMakeTest.cmake
index 7f9572e..b940528 100644
--- a/Tests/RunCMake/CMP0043/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMP0043/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 list(APPEND RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Debug)
 
diff --git a/Tests/RunCMake/CMP0045/RunCMakeTest.cmake b/Tests/RunCMake/CMP0045/RunCMakeTest.cmake
index 7c0e8a2..009d455 100644
--- a/Tests/RunCMake/CMP0045/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMP0045/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 run_cmake(CMP0045-OLD)
 run_cmake(CMP0045-NEW)
diff --git a/Tests/RunCMake/CMP0046/RunCMakeTest.cmake b/Tests/RunCMake/CMP0046/RunCMakeTest.cmake
index 0a39c76..86b749a 100644
--- a/Tests/RunCMake/CMP0046/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMP0046/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 run_cmake(CMP0046-OLD-missing-dependency)
 run_cmake(CMP0046-NEW-missing-dependency)
diff --git a/Tests/RunCMake/CMP0049/RunCMakeTest.cmake b/Tests/RunCMake/CMP0049/RunCMakeTest.cmake
index a8aa9d9..e71f31e 100644
--- a/Tests/RunCMake/CMP0049/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMP0049/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 run_cmake(CMP0049-OLD)
 run_cmake(CMP0049-NEW)
diff --git a/Tests/RunCMake/CMP0050/RunCMakeTest.cmake b/Tests/RunCMake/CMP0050/RunCMakeTest.cmake
index b7de284..526a9aa 100644
--- a/Tests/RunCMake/CMP0050/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMP0050/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 run_cmake(CMP0050-OLD)
 run_cmake(CMP0050-NEW)
diff --git a/Tests/RunCMake/CMP0051/RunCMakeTest.cmake b/Tests/RunCMake/CMP0051/RunCMakeTest.cmake
index 621192d..955d898 100644
--- a/Tests/RunCMake/CMP0051/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMP0051/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 run_cmake(CMP0051-OLD)
 run_cmake(CMP0051-NEW)
diff --git a/Tests/RunCMake/CMP0053/RunCMakeTest.cmake b/Tests/RunCMake/CMP0053/RunCMakeTest.cmake
index 6521ac0..de58c25 100644
--- a/Tests/RunCMake/CMP0053/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMP0053/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 run_cmake(CMP0053-OLD)
 run_cmake(CMP0053-NEW)
diff --git a/Tests/RunCMake/CMP0054/RunCMakeTest.cmake b/Tests/RunCMake/CMP0054/RunCMakeTest.cmake
index 2f2fb76..fc031de 100644
--- a/Tests/RunCMake/CMP0054/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMP0054/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 run_cmake(CMP0054-OLD)
 run_cmake(CMP0054-NEW)
diff --git a/Tests/RunCMake/CMP0055/RunCMakeTest.cmake b/Tests/RunCMake/CMP0055/RunCMakeTest.cmake
index efcfcab..33a5b4b 100644
--- a/Tests/RunCMake/CMP0055/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMP0055/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 run_cmake(CMP0055-OLD-Out-of-Scope)
 run_cmake(CMP0055-NEW-Out-of-Scope)
diff --git a/Tests/RunCMake/CMP0057/RunCMakeTest.cmake b/Tests/RunCMake/CMP0057/RunCMakeTest.cmake
index 719e054..76eaca6 100644
--- a/Tests/RunCMake/CMP0057/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMP0057/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 run_cmake(CMP0057-OLD)
 run_cmake(CMP0057-WARN)
diff --git a/Tests/RunCMake/CMP0060/CMP0060-WARN-ON-stderr.txt b/Tests/RunCMake/CMP0060/CMP0060-WARN-ON-stderr.txt
index e2c280e..7230a07 100644
--- a/Tests/RunCMake/CMP0060/CMP0060-WARN-ON-stderr.txt
+++ b/Tests/RunCMake/CMP0060/CMP0060-WARN-ON-stderr.txt
@@ -12,5 +12,5 @@
   will ask the linker to search for these by library name.
 Call Stack \(most recent call first\):
   CMP0060-WARN-ON.cmake:[0-9]+ \(include\)
-  CMakeLists.txt:4 \(include\)
+  CMakeLists.txt:[0-9]+ \(include\)
 This warning is for project developers.  Use -Wno-dev to suppress it.$
diff --git a/Tests/RunCMake/CMP0060/CMakeLists.txt b/Tests/RunCMake/CMP0060/CMakeLists.txt
index 291d34d..db6b701 100644
--- a/Tests/RunCMake/CMP0060/CMakeLists.txt
+++ b/Tests/RunCMake/CMP0060/CMakeLists.txt
@@ -1,4 +1,3 @@
-cmake_minimum_required(VERSION 3.9)
-cmake_policy(VERSION 3.2)
+cmake_minimum_required(VERSION 3.2)
 project(${RunCMake_TEST} C)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/CMP0060/RunCMakeTest.cmake b/Tests/RunCMake/CMP0060/RunCMakeTest.cmake
index 445156f..b7eae5a 100644
--- a/Tests/RunCMake/CMP0060/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMP0060/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 function(run_cmake_CMP0060 CASE)
   set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/CMP0060-${CASE}-build)
diff --git a/Tests/RunCMake/CMP0064/RunCMakeTest.cmake b/Tests/RunCMake/CMP0064/RunCMakeTest.cmake
index 26e0a91..4c68510 100644
--- a/Tests/RunCMake/CMP0064/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMP0064/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 run_cmake(CMP0064-OLD)
 run_cmake(CMP0064-WARN)
diff --git a/Tests/RunCMake/CMP0065/RunCMakeTest.cmake b/Tests/RunCMake/CMP0065/RunCMakeTest.cmake
index e86b50e..1ca4605 100644
--- a/Tests/RunCMake/CMP0065/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMP0065/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 run_cmake(OLDBad1)
 if(NOT CMAKE_SYSTEM_NAME STREQUAL "AIX")
diff --git a/Tests/RunCMake/CMP0081/CMakeLists.txt b/Tests/RunCMake/CMP0081/CMakeLists.txt
index ef2163c..44025d3 100644
--- a/Tests/RunCMake/CMP0081/CMakeLists.txt
+++ b/Tests/RunCMake/CMP0081/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.12)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/CMP0102/CMakeLists.txt b/Tests/RunCMake/CMP0102/CMakeLists.txt
index ef2163c..2632ffa 100644
--- a/Tests/RunCMake/CMP0102/CMakeLists.txt
+++ b/Tests/RunCMake/CMP0102/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.16)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/CMP0106/CMakeLists.txt b/Tests/RunCMake/CMP0106/CMakeLists.txt
index eafa642..0a96a26 100644
--- a/Tests/RunCMake/CMP0106/CMakeLists.txt
+++ b/Tests/RunCMake/CMP0106/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.17)
 if (RunCMake_TEST STREQUAL "CMP0106-WARN-VTK")
   project(VTK NONE)
 else ()
diff --git a/Tests/RunCMake/CMP0111/CMP0111-OLD-stderr.txt b/Tests/RunCMake/CMP0111/CMP0111-OLD-stderr.txt
new file mode 100644
index 0000000..bf7fb08
--- /dev/null
+++ b/Tests/RunCMake/CMP0111/CMP0111-OLD-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at CMP0111-OLD.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0111 will be removed from a future version
+  of CMake.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/CMP0115/CMP0115-OLD-stderr.txt b/Tests/RunCMake/CMP0115/CMP0115-OLD-stderr.txt
index 8b90311..67d00f7 100644
--- a/Tests/RunCMake/CMP0115/CMP0115-OLD-stderr.txt
+++ b/Tests/RunCMake/CMP0115/CMP0115-OLD-stderr.txt
@@ -3,11 +3,9 @@
 
     noexist
 
-  Tried extensions [^
-]*
-  [^
-]*
-Call Stack \(most recent call first\):
+  Tried extensions ([^
+]+
+)+Call Stack \(most recent call first\):
   CMP0115-OLD\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 
diff --git a/Tests/RunCMake/CMP0115/CMP0115-WARN-stderr.txt b/Tests/RunCMake/CMP0115/CMP0115-WARN-stderr.txt
index 7b100b6..e79ca97 100644
--- a/Tests/RunCMake/CMP0115/CMP0115-WARN-stderr.txt
+++ b/Tests/RunCMake/CMP0115/CMP0115-WARN-stderr.txt
@@ -17,11 +17,9 @@
 
     noexist
 
-  Tried extensions [^
-]*
-  [^
-]*
-Call Stack \(most recent call first\):
+  Tried extensions ([^
+]+
+)+Call Stack \(most recent call first\):
   CMP0115-WARN\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 
diff --git a/Tests/RunCMake/CMP0118/CMP0118-NEW-Test1-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-NEW-Test1-stderr.txt
index ec777f7..acd145e 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-NEW-Test1-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-NEW-Test1-stderr.txt
@@ -1,5 +1,5 @@
 ^prop: `0`
-CMake Error at CMP0118-Common-Test1\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test1\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-NEW-Test1-build/GeneratedMain\.txt
diff --git a/Tests/RunCMake/CMP0118/CMP0118-NEW-Test3-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-NEW-Test3-stderr.txt
index f7d9f6b..929dafd 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-NEW-Test3-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-NEW-Test3-stderr.txt
@@ -52,7 +52,7 @@
 Generated_with_full_source_path3\.txt: # 2b # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3a # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test3\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test3\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test3-build/Generated_with_full_path3\.txt)
@@ -60,7 +60,7 @@
   CMP0118-NEW-Test3\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test3\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test3\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test3-build/Generated_with_full_path3\.txt)
@@ -68,7 +68,7 @@
   CMP0118-NEW-Test3\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test3\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test3\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test3-build/Generated_with_full_path3\.txt)
@@ -76,7 +76,7 @@
   CMP0118-NEW-Test3\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test3\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test3\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test3-build/Generated_with_full_path3\.txt)
diff --git a/Tests/RunCMake/CMP0118/CMP0118-NEW-Test3b-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-NEW-Test3b-stderr.txt
index a876390..6a096b8 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-NEW-Test3b-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-NEW-Test3b-stderr.txt
@@ -52,7 +52,7 @@
 Generated_with_full_source_path3\.txt: # 2b # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3a # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test3b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test3b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test3b-build/Generated_with_full_path3\.txt)
@@ -60,7 +60,7 @@
   CMP0118-NEW-Test3b\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test3b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test3b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test3b-build/Generated_with_full_path3\.txt)
@@ -68,7 +68,7 @@
   CMP0118-NEW-Test3b\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test3b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test3b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test3b-build/Generated_with_full_path3\.txt)
@@ -76,7 +76,7 @@
   CMP0118-NEW-Test3b\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test3b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test3b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test3b-build/Generated_with_full_path3\.txt)
diff --git a/Tests/RunCMake/CMP0118/CMP0118-NEW-Test4-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-NEW-Test4-stderr.txt
index b750ae7..e721876 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-NEW-Test4-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-NEW-Test4-stderr.txt
@@ -124,7 +124,7 @@
 Generated_with_full_source_path3\.txt: # 2b # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3a # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test4-build/Generated_with_full_path1\.txt|CMP0118-NEW-Test4-build/Generated_with_relative_path1\.txt)
@@ -132,7 +132,7 @@
   CMP0118-NEW-Test4\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test4-build/Generated_with_full_path1\.txt|CMP0118-NEW-Test4-build/Generated_with_relative_path1\.txt)
@@ -140,7 +140,7 @@
   CMP0118-NEW-Test4\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test4-build/Generated_with_full_path1\.txt|CMP0118-NEW-Test4-build/Generated_with_relative_path1\.txt)
@@ -148,7 +148,7 @@
   CMP0118-NEW-Test4\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test4-build/Generated_with_full_path1\.txt|CMP0118-NEW-Test4-build/Generated_with_relative_path1\.txt)
@@ -156,7 +156,7 @@
   CMP0118-NEW-Test4\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test4-build/Generated_with_full_path1\.txt|CMP0118-NEW-Test4-build/Generated_with_relative_path1\.txt)
diff --git a/Tests/RunCMake/CMP0118/CMP0118-NEW-Test4b-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-NEW-Test4b-stderr.txt
index 580f04f..f358297 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-NEW-Test4b-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-NEW-Test4b-stderr.txt
@@ -124,7 +124,7 @@
 Generated_with_full_source_path3\.txt: # 2b # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3a # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test4b-build/Generated_with_full_path1\.txt|CMP0118-NEW-Test4b-build/Generated_with_relative_path1\.txt)
@@ -132,7 +132,7 @@
   CMP0118-NEW-Test4b\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test4b-build/Generated_with_full_path1\.txt|CMP0118-NEW-Test4b-build/Generated_with_relative_path1\.txt)
@@ -140,7 +140,7 @@
   CMP0118-NEW-Test4b\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test4b-build/Generated_with_full_path1\.txt|CMP0118-NEW-Test4b-build/Generated_with_relative_path1\.txt)
@@ -148,7 +148,7 @@
   CMP0118-NEW-Test4b\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test4b-build/Generated_with_full_path1\.txt|CMP0118-NEW-Test4b-build/Generated_with_relative_path1\.txt)
@@ -156,7 +156,7 @@
   CMP0118-NEW-Test4b\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test4b-build/Generated_with_full_path1\.txt|CMP0118-NEW-Test4b-build/Generated_with_relative_path1\.txt)
diff --git a/Tests/RunCMake/CMP0118/CMP0118-NEW-Test5-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-NEW-Test5-stderr.txt
index e268a7a..0e27c1d 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-NEW-Test5-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-NEW-Test5-stderr.txt
@@ -115,7 +115,7 @@
 Generated_with_full_source_path3\.txt: # 2b # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3a # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test5-build/Generated_with_relative_path[2-3]\.txt|CMP0118-NEW-Test5-build/Generated_with_full_path[2-3]\.txt)
@@ -123,7 +123,7 @@
   CMP0118-NEW-Test5\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test5-build/Generated_with_relative_path[2-3]\.txt|CMP0118-NEW-Test5-build/Generated_with_full_path[2-3]\.txt)
@@ -131,7 +131,7 @@
   CMP0118-NEW-Test5\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test5-build/Generated_with_relative_path[2-3]\.txt|CMP0118-NEW-Test5-build/Generated_with_full_path[2-3]\.txt)
@@ -139,7 +139,7 @@
   CMP0118-NEW-Test5\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test5-build/Generated_with_relative_path[2-3]\.txt|CMP0118-NEW-Test5-build/Generated_with_full_path[2-3]\.txt)
@@ -147,7 +147,7 @@
   CMP0118-NEW-Test5\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test5-build/Generated_with_relative_path[2-3]\.txt|CMP0118-NEW-Test5-build/Generated_with_full_path[2-3]\.txt)
@@ -155,7 +155,7 @@
   CMP0118-NEW-Test5\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test5-build/Generated_with_relative_path[2-3]\.txt|CMP0118-NEW-Test5-build/Generated_with_full_path[2-3]\.txt)
@@ -163,7 +163,7 @@
   CMP0118-NEW-Test5\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-NEW-Test5-build/Generated_with_relative_path[2-3]\.txt|CMP0118-NEW-Test5-build/Generated_with_full_path[2-3]\.txt)
diff --git a/Tests/RunCMake/CMP0118/CMP0118-NEW-Test7-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-NEW-Test7-stderr.txt
index 08eb682..4de6e76 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-NEW-Test7-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-NEW-Test7-stderr.txt
@@ -47,7 +47,7 @@
 Generated_source6\.txt: # 2b # GENERATED = `0`
 Generated_source6\.txt: # 3a # GENERATED = `0`
 Generated_source6\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-NEW-Test7-build/Generated_source[4-6]\.txt
@@ -55,7 +55,7 @@
   CMP0118-NEW-Test7\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-NEW-Test7-build/Generated_source[4-6]\.txt
@@ -63,7 +63,7 @@
   CMP0118-NEW-Test7\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-NEW-Test7-build/Generated_source[4-6]\.txt
diff --git a/Tests/RunCMake/CMP0118/CMP0118-NEW-Test9-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-NEW-Test9-stderr.txt
index b7c496c..b7a170b 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-NEW-Test9-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-NEW-Test9-stderr.txt
@@ -47,7 +47,7 @@
 Generated_source6\.txt: # 2b # GENERATED = `0`
 Generated_source6\.txt: # 3a # GENERATED = `0`
 Generated_source6\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test9\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test9\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-NEW-Test9-build/Generated_source[4-6]\.txt
@@ -55,7 +55,7 @@
   CMP0118-NEW-Test9\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test9\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test9\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-NEW-Test9-build/Generated_source[4-6]\.txt
@@ -63,7 +63,7 @@
   CMP0118-NEW-Test9\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test9\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test9\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-NEW-Test9-build/Generated_source[4-6]\.txt
diff --git a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test1-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test1-stderr.txt
index 58144c8..2af72a4 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test1-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test1-stderr.txt
@@ -1,5 +1,5 @@
 ^prop: `0`
-CMake Error at CMP0118-Common-Test1\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test1\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-OLD-Test1-build/GeneratedMain\.txt
diff --git a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test10-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test10-stderr.txt
index 1f1bc90..6109f65 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test10-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test10-stderr.txt
@@ -40,7 +40,7 @@
 Generated_source6\.txt: # 2b # GENERATED = `1`
 Generated_source6\.txt: # 3a # GENERATED = `0`
 Generated_source6\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test10\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test10\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-OLD-Test10-build/Generated_source4\.txt
diff --git a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test11-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test11-stderr.txt
index 5c15f12..e5e97de 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test11-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test11-stderr.txt
@@ -40,7 +40,7 @@
 Generated_source6\.txt: # 2b # GENERATED = `0`
 Generated_source6\.txt: # 3a # GENERATED = `0`
 Generated_source6\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test11\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test11\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-OLD-Test11-build/Generated_source[4-6]\.txt
@@ -48,7 +48,7 @@
   CMP0118-OLD-Test11\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test11\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test11\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-OLD-Test11-build/Generated_source[4-6]\.txt
@@ -56,7 +56,7 @@
   CMP0118-OLD-Test11\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test11\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test11\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-OLD-Test11-build/Generated_source[4-6]\.txt
diff --git a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test14-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test14-stderr.txt
index 12a913a..f5b3d1a 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test14-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test14-stderr.txt
@@ -40,7 +40,7 @@
 Generated_source6\.txt: # 2b # GENERATED = `1`
 Generated_source6\.txt: # 3a # GENERATED = `0`
 Generated_source6\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test14\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test14\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-OLD-Test14-build/Generated_source4\.txt
diff --git a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test15-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test15-stderr.txt
index 62db7ee..a30bc84 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test15-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test15-stderr.txt
@@ -40,7 +40,7 @@
 Generated_source6\.txt: # 2b # GENERATED = `0`
 Generated_source6\.txt: # 3a # GENERATED = `0`
 Generated_source6\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test15\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test15\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-OLD-Test15-build/Generated_source[4-6]\.txt
@@ -48,7 +48,7 @@
   CMP0118-OLD-Test15\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test15\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test15\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-OLD-Test15-build/Generated_source[4-6]\.txt
@@ -56,7 +56,7 @@
   CMP0118-OLD-Test15\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test15\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test15\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-OLD-Test15-build/Generated_source[4-6]\.txt
diff --git a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test3-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test3-stderr.txt
index 7f86d38..4f4fea3 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test3-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test3-stderr.txt
@@ -52,7 +52,7 @@
 Generated_with_full_source_path3\.txt: # 2b # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3a # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test3\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test3\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test3-build/Generated_with_full_path3\.txt)
@@ -60,7 +60,7 @@
   CMP0118-OLD-Test3\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test3\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test3\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test3-build/Generated_with_full_path3\.txt)
@@ -68,7 +68,7 @@
   CMP0118-OLD-Test3\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test3\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test3\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test3-build/Generated_with_full_path3\.txt)
@@ -76,7 +76,7 @@
   CMP0118-OLD-Test3\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test3\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test3\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test3-build/Generated_with_full_path3\.txt)
diff --git a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test3b-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test3b-stderr.txt
index 4104fc0..3c80531 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test3b-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test3b-stderr.txt
@@ -52,7 +52,7 @@
 Generated_with_full_source_path3\.txt: # 2b # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3a # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test3b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test3b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test3b-build/Generated_with_full_path3\.txt)
@@ -60,7 +60,7 @@
   CMP0118-OLD-Test3b\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test3b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test3b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test3b-build/Generated_with_full_path3\.txt)
@@ -68,7 +68,7 @@
   CMP0118-OLD-Test3b\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test3b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test3b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test3b-build/Generated_with_full_path3\.txt)
@@ -76,7 +76,7 @@
   CMP0118-OLD-Test3b\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test3b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test3b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test3b-build/Generated_with_full_path3\.txt)
diff --git a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test4-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test4-stderr.txt
index 7a16d0b..9600fee 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test4-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test4-stderr.txt
@@ -52,7 +52,7 @@
 Generated_with_full_source_path3\.txt: # 2b # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3a # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test4-build/Generated_with_full_path1\.txt|CMP0118-OLD-Test4-build/Generated_with_relative_path1\.txt)
@@ -60,7 +60,7 @@
   CMP0118-OLD-Test4\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test4-build/Generated_with_full_path1\.txt|CMP0118-OLD-Test4-build/Generated_with_relative_path1\.txt)
@@ -68,7 +68,7 @@
   CMP0118-OLD-Test4\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test4-build/Generated_with_full_path1\.txt|CMP0118-OLD-Test4-build/Generated_with_relative_path1\.txt)
@@ -76,7 +76,7 @@
   CMP0118-OLD-Test4\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test4-build/Generated_with_full_path1\.txt|CMP0118-OLD-Test4-build/Generated_with_relative_path1\.txt)
@@ -84,7 +84,7 @@
   CMP0118-OLD-Test4\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test4-build/Generated_with_full_path1\.txt|CMP0118-OLD-Test4-build/Generated_with_relative_path1\.txt)
diff --git a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test4b-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test4b-stderr.txt
index 5a5c4ec..e638660 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test4b-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test4b-stderr.txt
@@ -52,7 +52,7 @@
 Generated_with_full_source_path3\.txt: # 2b # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3a # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test4b-build/Generated_with_full_path1\.txt|CMP0118-OLD-Test4b-build/Generated_with_relative_path1\.txt)
@@ -60,7 +60,7 @@
   CMP0118-OLD-Test4b\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test4b-build/Generated_with_full_path1\.txt|CMP0118-OLD-Test4b-build/Generated_with_relative_path1\.txt)
@@ -68,7 +68,7 @@
   CMP0118-OLD-Test4b\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test4b-build/Generated_with_full_path1\.txt|CMP0118-OLD-Test4b-build/Generated_with_relative_path1\.txt)
@@ -76,7 +76,7 @@
   CMP0118-OLD-Test4b\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test4b-build/Generated_with_full_path1\.txt|CMP0118-OLD-Test4b-build/Generated_with_relative_path1\.txt)
@@ -84,7 +84,7 @@
   CMP0118-OLD-Test4b\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test4b-build/Generated_with_full_path1\.txt|CMP0118-OLD-Test4b-build/Generated_with_relative_path1\.txt)
diff --git a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test5-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test5-stderr.txt
index 12fa617..18e6a8c 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test5-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test5-stderr.txt
@@ -52,7 +52,7 @@
 Generated_with_full_source_path3\.txt: # 2b # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3a # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test5-build/Generated_with_relative_path[2-3]\.txt|CMP0118-OLD-Test5-build/Generated_with_full_path[2-3]\.txt)
@@ -60,7 +60,7 @@
   CMP0118-OLD-Test5\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test5-build/Generated_with_relative_path[2-3]\.txt|CMP0118-OLD-Test5-build/Generated_with_full_path[2-3]\.txt)
@@ -68,7 +68,7 @@
   CMP0118-OLD-Test5\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test5-build/Generated_with_relative_path[2-3]\.txt|CMP0118-OLD-Test5-build/Generated_with_full_path[2-3]\.txt)
@@ -76,7 +76,7 @@
   CMP0118-OLD-Test5\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test5-build/Generated_with_relative_path[2-3]\.txt|CMP0118-OLD-Test5-build/Generated_with_full_path[2-3]\.txt)
@@ -84,7 +84,7 @@
   CMP0118-OLD-Test5\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test5-build/Generated_with_relative_path[2-3]\.txt|CMP0118-OLD-Test5-build/Generated_with_full_path[2-3]\.txt)
@@ -92,7 +92,7 @@
   CMP0118-OLD-Test5\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test5-build/Generated_with_relative_path[2-3]\.txt|CMP0118-OLD-Test5-build/Generated_with_full_path[2-3]\.txt)
@@ -100,7 +100,7 @@
   CMP0118-OLD-Test5\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-OLD-Test5-build/Generated_with_relative_path[2-3]\.txt|CMP0118-OLD-Test5-build/Generated_with_full_path[2-3]\.txt)
diff --git a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test6-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test6-stderr.txt
index 7199f04..a60545f 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test6-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test6-stderr.txt
@@ -34,7 +34,7 @@
 Generated_source6\.txt: # 2b # GENERATED = `1`
 Generated_source6\.txt: # 3a # GENERATED = `0`
 Generated_source6\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test6\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test6\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-OLD-Test6-build/Generated_source4\.txt
diff --git a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test7-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test7-stderr.txt
index 233fd8b..fd496cb 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test7-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test7-stderr.txt
@@ -34,7 +34,7 @@
 Generated_source6\.txt: # 2b # GENERATED = `0`
 Generated_source6\.txt: # 3a # GENERATED = `0`
 Generated_source6\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-OLD-Test7-build/Generated_source[2-6]\.txt
@@ -42,7 +42,7 @@
   CMP0118-OLD-Test7\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-OLD-Test7-build/Generated_source[2-6]\.txt
@@ -50,7 +50,7 @@
   CMP0118-OLD-Test7\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-OLD-Test7-build/Generated_source[2-6]\.txt
@@ -58,7 +58,7 @@
   CMP0118-OLD-Test7\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-OLD-Test7-build/Generated_source[2-6]\.txt
@@ -66,7 +66,7 @@
   CMP0118-OLD-Test7\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-OLD-Test7-build/Generated_source[2-6]\.txt
diff --git a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test8-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test8-stderr.txt
index 4aed2ed..3505242 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test8-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test8-stderr.txt
@@ -34,7 +34,7 @@
 Generated_source6\.txt: # 2b # GENERATED = `1`
 Generated_source6\.txt: # 3a # GENERATED = `0`
 Generated_source6\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test8\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test8\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-OLD-Test8-build/Generated_source4\.txt
diff --git a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test9-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test9-stderr.txt
index cea8c22..63a9341 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-OLD-Test9-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-OLD-Test9-stderr.txt
@@ -34,7 +34,7 @@
 Generated_source6\.txt: # 2b # GENERATED = `0`
 Generated_source6\.txt: # 3a # GENERATED = `0`
 Generated_source6\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test9\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test9\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-OLD-Test9-build/Generated_source[4-6]\.txt
@@ -42,7 +42,7 @@
   CMP0118-OLD-Test9\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test9\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test9\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-OLD-Test9-build/Generated_source[4-6]\.txt
@@ -50,7 +50,7 @@
   CMP0118-OLD-Test9\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test9\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test9\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-OLD-Test9-build/Generated_source[4-6]\.txt
diff --git a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test1-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test1-stderr.txt
index e2a2cf5..1d7cbde 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test1-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test1-stderr.txt
@@ -1,5 +1,5 @@
 ^prop: `0`
-CMake Error at CMP0118-Common-Test1\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test1\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-WARN-Test1-build/GeneratedMain\.txt
diff --git a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test10-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test10-stderr.txt
index bce7681..f4c7d00 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test10-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test10-stderr.txt
@@ -40,7 +40,7 @@
 Generated_source6\.txt: # 2b # GENERATED = `1`
 Generated_source6\.txt: # 3a # GENERATED = `0`
 Generated_source6\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test10\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test10\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-WARN-Test10-build/Generated_source4\.txt
diff --git a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test11-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test11-stderr.txt
index 00c47e9..93d8b83 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test11-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test11-stderr.txt
@@ -63,7 +63,7 @@
 Generated_source6\.txt: # 2b # GENERATED = `0`
 Generated_source6\.txt: # 3a # GENERATED = `0`
 Generated_source6\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test11\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test11\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-WARN-Test11-build/Generated_source[4-6]\.txt
@@ -71,7 +71,7 @@
   CMP0118-WARN-Test11\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test11\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test11\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-WARN-Test11-build/Generated_source[4-6]\.txt
@@ -79,7 +79,7 @@
   CMP0118-WARN-Test11\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test11\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test11\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-WARN-Test11-build/Generated_source[4-6]\.txt
diff --git a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test14-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test14-stderr.txt
index 5b7994c..fdde792 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test14-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test14-stderr.txt
@@ -40,7 +40,7 @@
 Generated_source6\.txt: # 2b # GENERATED = `1`
 Generated_source6\.txt: # 3a # GENERATED = `0`
 Generated_source6\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test14\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test14\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-WARN-Test14-build/Generated_source4\.txt
diff --git a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test15-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test15-stderr.txt
index c975c23..9e6a4a5 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test15-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test15-stderr.txt
@@ -63,7 +63,7 @@
 Generated_source6\.txt: # 2b # GENERATED = `0`
 Generated_source6\.txt: # 3a # GENERATED = `0`
 Generated_source6\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test15\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test15\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-WARN-Test15-build/Generated_source[4-6]\.txt
@@ -71,7 +71,7 @@
   CMP0118-WARN-Test15\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test15\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test15\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-WARN-Test15-build/Generated_source[4-6]\.txt
@@ -79,7 +79,7 @@
   CMP0118-WARN-Test15\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test15\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test15\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-WARN-Test15-build/Generated_source[4-6]\.txt
diff --git a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test3-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test3-stderr.txt
index 142d8a0..58ba793 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test3-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test3-stderr.txt
@@ -52,7 +52,7 @@
 Generated_with_full_source_path3\.txt: # 2b # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3a # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test3\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test3\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test3-build/Generated_with_full_path3\.txt)
@@ -60,7 +60,7 @@
   CMP0118-WARN-Test3\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test3\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test3\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test3-build/Generated_with_full_path3\.txt)
@@ -68,7 +68,7 @@
   CMP0118-WARN-Test3\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test3\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test3\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test3-build/Generated_with_full_path3\.txt)
@@ -76,7 +76,7 @@
   CMP0118-WARN-Test3\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test3\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test3\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test3-build/Generated_with_full_path3\.txt)
diff --git a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test3b-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test3b-stderr.txt
index d4ef667..cdb7cb4 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test3b-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test3b-stderr.txt
@@ -52,7 +52,7 @@
 Generated_with_full_source_path3\.txt: # 2b # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3a # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test3b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test3b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test3b-build/Generated_with_full_path3\.txt)
@@ -60,7 +60,7 @@
   CMP0118-WARN-Test3b\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test3b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test3b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test3b-build/Generated_with_full_path3\.txt)
@@ -68,7 +68,7 @@
   CMP0118-WARN-Test3b\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test3b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test3b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test3b-build/Generated_with_full_path3\.txt)
@@ -76,7 +76,7 @@
   CMP0118-WARN-Test3b\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test3b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test3b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test3b-build/Generated_with_full_path3\.txt)
diff --git a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test4-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test4-stderr.txt
index ceeb570..4cd401c 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test4-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test4-stderr.txt
@@ -169,7 +169,7 @@
 Generated_with_full_source_path3\.txt: # 2b # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3a # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test4-build/Generated_with_full_path1\.txt|CMP0118-WARN-Test4-build/Generated_with_relative_path1\.txt)
@@ -177,7 +177,7 @@
   CMP0118-WARN-Test4\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test4-build/Generated_with_full_path1\.txt|CMP0118-WARN-Test4-build/Generated_with_relative_path1\.txt)
@@ -185,7 +185,7 @@
   CMP0118-WARN-Test4\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test4-build/Generated_with_full_path1\.txt|CMP0118-WARN-Test4-build/Generated_with_relative_path1\.txt)
@@ -193,7 +193,7 @@
   CMP0118-WARN-Test4\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test4-build/Generated_with_full_path1\.txt|CMP0118-WARN-Test4-build/Generated_with_relative_path1\.txt)
@@ -201,7 +201,7 @@
   CMP0118-WARN-Test4\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test4-build/Generated_with_full_path1\.txt|CMP0118-WARN-Test4-build/Generated_with_relative_path1\.txt)
diff --git a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test4b-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test4b-stderr.txt
index f8484d0..6acd3df 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test4b-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test4b-stderr.txt
@@ -169,7 +169,7 @@
 Generated_with_full_source_path3\.txt: # 2b # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3a # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test4b-build/Generated_with_full_path1\.txt|CMP0118-WARN-Test4b-build/Generated_with_relative_path1\.txt)
@@ -177,7 +177,7 @@
   CMP0118-WARN-Test4b\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test4b-build/Generated_with_full_path1\.txt|CMP0118-WARN-Test4b-build/Generated_with_relative_path1\.txt)
@@ -185,7 +185,7 @@
   CMP0118-WARN-Test4b\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test4b-build/Generated_with_full_path1\.txt|CMP0118-WARN-Test4b-build/Generated_with_relative_path1\.txt)
@@ -193,7 +193,7 @@
   CMP0118-WARN-Test4b\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test4b-build/Generated_with_full_path1\.txt|CMP0118-WARN-Test4b-build/Generated_with_relative_path1\.txt)
@@ -201,7 +201,7 @@
   CMP0118-WARN-Test4b\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test4b\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test4b-build/Generated_with_full_path1\.txt|CMP0118-WARN-Test4b-build/Generated_with_relative_path1\.txt)
diff --git a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test5-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test5-stderr.txt
index 0556391..2805cbf 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test5-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test5-stderr.txt
@@ -154,7 +154,7 @@
 Generated_with_full_source_path3\.txt: # 2b # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3a # GENERATED = `0`
 Generated_with_full_source_path3\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test5-build/Generated_with_relative_path[2-3]\.txt|CMP0118-WARN-Test5-build/Generated_with_full_path[2-3]\.txt)
@@ -162,7 +162,7 @@
   CMP0118-WARN-Test5\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test5-build/Generated_with_relative_path[2-3]\.txt|CMP0118-WARN-Test5-build/Generated_with_full_path[2-3]\.txt)
@@ -170,7 +170,7 @@
   CMP0118-WARN-Test5\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test5-build/Generated_with_relative_path[2-3]\.txt|CMP0118-WARN-Test5-build/Generated_with_full_path[2-3]\.txt)
@@ -178,7 +178,7 @@
   CMP0118-WARN-Test5\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test5-build/Generated_with_relative_path[2-3]\.txt|CMP0118-WARN-Test5-build/Generated_with_full_path[2-3]\.txt)
@@ -186,7 +186,7 @@
   CMP0118-WARN-Test5\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test5-build/Generated_with_relative_path[2-3]\.txt|CMP0118-WARN-Test5-build/Generated_with_full_path[2-3]\.txt)
@@ -194,7 +194,7 @@
   CMP0118-WARN-Test5\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test5-build/Generated_with_relative_path[2-3]\.txt|CMP0118-WARN-Test5-build/Generated_with_full_path[2-3]\.txt)
@@ -202,7 +202,7 @@
   CMP0118-WARN-Test5\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test5\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/(Generated_with_full_source_path[1-3]\.txt|CMP0118-WARN-Test5-build/Generated_with_relative_path[2-3]\.txt|CMP0118-WARN-Test5-build/Generated_with_full_path[2-3]\.txt)
diff --git a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test6-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test6-stderr.txt
index 7d588a2..6f29170 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test6-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test6-stderr.txt
@@ -34,7 +34,7 @@
 Generated_source6\.txt: # 2b # GENERATED = `1`
 Generated_source6\.txt: # 3a # GENERATED = `0`
 Generated_source6\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test6\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test6\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-WARN-Test6-build/Generated_source4\.txt
diff --git a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test7-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test7-stderr.txt
index 8421061..cde1164 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test7-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test7-stderr.txt
@@ -57,7 +57,7 @@
 Generated_source6\.txt: # 2b # GENERATED = `0`
 Generated_source6\.txt: # 3a # GENERATED = `0`
 Generated_source6\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-WARN-Test7-build/Generated_source[2-6]\.txt
@@ -65,7 +65,7 @@
   CMP0118-WARN-Test7\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-WARN-Test7-build/Generated_source[2-6]\.txt
@@ -73,7 +73,7 @@
   CMP0118-WARN-Test7\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-WARN-Test7-build/Generated_source[2-6]\.txt
@@ -81,7 +81,7 @@
   CMP0118-WARN-Test7\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-WARN-Test7-build/Generated_source[2-6]\.txt
@@ -89,7 +89,7 @@
   CMP0118-WARN-Test7\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test7\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-WARN-Test7-build/Generated_source[2-6]\.txt
diff --git a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test8-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test8-stderr.txt
index e0f17e6..91c4c4c 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test8-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test8-stderr.txt
@@ -34,7 +34,7 @@
 Generated_source6\.txt: # 2b # GENERATED = `1`
 Generated_source6\.txt: # 3a # GENERATED = `0`
 Generated_source6\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test8\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test8\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-WARN-Test8-build/Generated_source4\.txt
diff --git a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test9-stderr.txt b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test9-stderr.txt
index 80f3edf..547e820 100644
--- a/Tests/RunCMake/CMP0118/CMP0118-WARN-Test9-stderr.txt
+++ b/Tests/RunCMake/CMP0118/CMP0118-WARN-Test9-stderr.txt
@@ -57,7 +57,7 @@
 Generated_source6\.txt: # 2b # GENERATED = `0`
 Generated_source6\.txt: # 3a # GENERATED = `0`
 Generated_source6\.txt: # 3b # GENERATED = `0`
-CMake Error at CMP0118-Common-Test9\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test9\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-WARN-Test9-build/Generated_source[4-6]\.txt
@@ -65,7 +65,7 @@
   CMP0118-WARN-Test9\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test9\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test9\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-WARN-Test9-build/Generated_source[4-6]\.txt
@@ -73,7 +73,7 @@
   CMP0118-WARN-Test9\.cmake:[0-9]+ \(include\)
   CMakeLists\.txt:[0-9]+ \(include\)
 +
-CMake Error at CMP0118-Common-Test9\.cmake:[0-9]+ \(add_custom_target\):
+CMake Error at CMP0118-Common-Test9\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 [ \t]*.*Tests/RunCMake/CMP0118/CMP0118-WARN-Test9-build/Generated_source[4-6]\.txt
diff --git a/Tests/RunCMake/CMP0118/GenInSubdir-OLD-stderr.txt b/Tests/RunCMake/CMP0118/GenInSubdir-OLD-stderr.txt
index d3aa546..5e9cf6c 100644
--- a/Tests/RunCMake/CMP0118/GenInSubdir-OLD-stderr.txt
+++ b/Tests/RunCMake/CMP0118/GenInSubdir-OLD-stderr.txt
@@ -1,8 +1,5 @@
-^CMake Error at GenInSubdir-Common.cmake:[0-9]+ \(add_custom_target\):
+^CMake Error at GenInSubdir/CMakeLists\.txt:[0-9]+ \(target_sources\):
   Cannot find source file:
 
     [^
 ]*/Tests/RunCMake/CMP0118/GenInSubdir-OLD-build/GenInSubdir/sub.txt
-Call Stack \(most recent call first\):
-  GenInSubdir-OLD.cmake:[0-9]+ \(include\)
-  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/CMP0118/GenInSubdir-WARN-stderr.txt b/Tests/RunCMake/CMP0118/GenInSubdir-WARN-stderr.txt
index 5eb8a34..2820ba3 100644
--- a/Tests/RunCMake/CMP0118/GenInSubdir-WARN-stderr.txt
+++ b/Tests/RunCMake/CMP0118/GenInSubdir-WARN-stderr.txt
@@ -1,8 +1,5 @@
-^CMake Error at GenInSubdir-Common.cmake:[0-9]+ \(add_custom_target\):
+^CMake Error at GenInSubdir/CMakeLists\.txt:[0-9]+ \(target_sources\):
   Cannot find source file:
 
     [^
 ]*/Tests/RunCMake/CMP0118/GenInSubdir-WARN-build/GenInSubdir/sub.txt
-Call Stack \(most recent call first\):
-  GenInSubdir-WARN.cmake:[0-9]+ \(include\)
-  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/CMP0121/CMP0121-ERANGE-OLD-stderr.txt b/Tests/RunCMake/CMP0121/CMP0121-ERANGE-OLD-stderr.txt
index 5a03559..ac01c80 100644
--- a/Tests/RunCMake/CMP0121/CMP0121-ERANGE-OLD-stderr.txt
+++ b/Tests/RunCMake/CMP0121/CMP0121-ERANGE-OLD-stderr.txt
@@ -1,5 +1,5 @@
 CMake Error at CMP0121-ERANGE-Common.cmake:3 \(list\):
-  list index: (-2147483643|2147483647) out of range \(-5, 4\)
+  list index: (-2147483648|2147483647) out of range \(-5, 4\)
 Call Stack \(most recent call first\):
   CMP0121-ERANGE-OLD.cmake:2 \(include\)
   CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/CMP0121/CMP0121-ERANGE-WARN-stderr.txt b/Tests/RunCMake/CMP0121/CMP0121-ERANGE-WARN-stderr.txt
index 1e7b127..c644dd3 100644
--- a/Tests/RunCMake/CMP0121/CMP0121-ERANGE-WARN-stderr.txt
+++ b/Tests/RunCMake/CMP0121/CMP0121-ERANGE-WARN-stderr.txt
@@ -7,9 +7,9 @@
   CMP0121-ERANGE-WARN.cmake:2 \(include\)
   CMakeLists.txt:3 \(include\)
 This warning is for project developers.  Use -Wno-dev to suppress it.
-
+.*
 CMake Error at CMP0121-ERANGE-Common.cmake:3 \(list\):
-  list index: (-2147483643|2147483647) out of range \(-5, 4\)
+  list index: (-2147483648|2147483647) out of range \(-5, 4\)
 Call Stack \(most recent call first\):
   CMP0121-ERANGE-WARN.cmake:2 \(include\)
   CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/CMP0139/CMakeLists.txt b/Tests/RunCMake/CMP0139/CMakeLists.txt
index 18dfd26..5ff8d3e 100644
--- a/Tests/RunCMake/CMP0139/CMakeLists.txt
+++ b/Tests/RunCMake/CMP0139/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.2)
+cmake_minimum_required(VERSION 3.23)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/CMP0150/CMP0150-NEW-build-stdout.txt b/Tests/RunCMake/CMP0150/CMP0150-NEW-build-stdout.txt
new file mode 100644
index 0000000..9e71b73
--- /dev/null
+++ b/Tests/RunCMake/CMP0150/CMP0150-NEW-build-stdout.txt
@@ -0,0 +1,7 @@
+.*-- Configured bottom project
+.*ExternalProject for ep-Y
+.*-- Configured bottom project
+[^\n]*-- Completed configuring project middle
+.*-- Configured bottom project
+.*ExternalProject for ep-X
+.*Non-ep top project
diff --git a/Tests/RunCMake/CMP0150/CMP0150-NEW-resolve.cmake b/Tests/RunCMake/CMP0150/CMP0150-NEW-resolve.cmake
new file mode 100644
index 0000000..f43f3d5
--- /dev/null
+++ b/Tests/RunCMake/CMP0150/CMP0150-NEW-resolve.cmake
@@ -0,0 +1,107 @@
+include(ExternalProject/shared_internal_commands)
+
+function(test_resolve parentUrl relativeUrl expectedResult)
+  _ep_resolve_relative_git_remote(result "${parentUrl}" "${relativeUrl}")
+  if(NOT result STREQUAL expectedResult)
+    message(SEND_ERROR "URL resolved to unexpected result:\n"
+      "  Expected: ${expectedResult}\n"
+      "  Actual  : ${result}"
+    )
+  endif()
+endfunction()
+
+test_resolve(
+  "https://example.com/group/parent"
+  "../other"
+  "https://example.com/group/other"
+)
+test_resolve(
+  "https://example.com/group/parent"
+  "../../alt/other"
+  "https://example.com/alt/other"
+)
+
+test_resolve(
+  "git@example.com:group/parent"
+  "../other"
+  "git@example.com:group/other"
+)
+test_resolve(
+  "git@example.com:group/parent"
+  "../../alt/other"
+  "git@example.com:alt/other"
+)
+test_resolve(
+  "git@example.com:/group/parent"
+  "../other"
+  "git@example.com:/group/other"
+)
+test_resolve(
+  "git@example.com:/group/parent"
+  "../../alt/other"
+  "git@example.com:/alt/other"
+)
+test_resolve(
+  "git+ssh://git@example.com:group/parent"
+  "../other"
+  "git+ssh://git@example.com:group/other"
+)
+test_resolve(
+  "ssh://git@example.com:1234/group/parent"
+  "../../alt/other"
+  "ssh://git@example.com:1234/alt/other"
+)
+
+test_resolve(
+  "file:///group/parent"
+  "../other"
+  "file:///group/other"
+)
+test_resolve(
+  "file:///group/parent"
+  "../../alt/other"
+  "file:///alt/other"
+)
+test_resolve(
+  "file:///~/group/parent"
+  "../../other"
+  "file:///~/other"
+)
+test_resolve(
+  "/group/parent"
+  "../other"
+  "/group/other"
+)
+test_resolve(
+  "/group/parent"
+  "../../alt/other"
+  "/alt/other"
+)
+test_resolve(
+  "C:/group/parent"
+  "../other"
+  "C:/group/other"
+)
+test_resolve(
+  "C:/group/parent"
+  "../../alt/other"
+  "C:/alt/other"
+)
+
+test_resolve(
+  "x-Test+v1.0://example.com/group/parent"
+  "../other"
+  "x-Test+v1.0://example.com/group/other"
+)
+
+# IPv6 literals
+test_resolve(
+  "http://[::1]/group/parent"
+  "../../alt/other"
+  "http://[::1]/alt/other"
+)
+test_resolve(
+  "git@[::1]:group/parent"
+  "../../alt/other"
+  "git@[::1]:alt/other"
+)
diff --git a/Tests/RunCMake/CMP0150/CMP0150-NEW-stdout.txt b/Tests/RunCMake/CMP0150/CMP0150-NEW-stdout.txt
new file mode 100644
index 0000000..0f25fab
--- /dev/null
+++ b/Tests/RunCMake/CMP0150/CMP0150-NEW-stdout.txt
@@ -0,0 +1,4 @@
+-- Configured bottom project
+-- Completed configuring project middle
+-- Completed configuring project top
+-- Configuring done
diff --git a/Tests/RunCMake/CMP0150/CMP0150-NEW.cmake b/Tests/RunCMake/CMP0150/CMP0150-NEW.cmake
new file mode 100644
index 0000000..c1c5607
--- /dev/null
+++ b/Tests/RunCMake/CMP0150/CMP0150-NEW.cmake
@@ -0,0 +1,45 @@
+set(policyCommand "cmake_policy(SET CMP0150 NEW)")
+
+# Need to keep paths and file names short to avoid hitting limits on Windows.
+# Directory names "a" through to "g" are used here according to the following:
+#   a   = Top project
+#   b/c = Middle project
+#   d   = Bottom project
+#   e/f = Cloned Top project
+#   g   = Build directory for cloned Top project
+#
+# Dependency names map as follows:
+#   X = middle dependency
+#   Y = bottom dependency
+
+set(projName top)
+set(depName X)
+set(epRelativeGitRepo ../b/c)
+set(fcRelativeGitRepo ../b/c)
+configure_file(CMakeLists.txt.in a/CMakeLists.txt @ONLY)
+initGitRepo("${CMAKE_CURRENT_BINARY_DIR}/a")
+
+set(projName middle)
+set(depName Y)
+set(epRelativeGitRepo ../../d)
+set(fcRelativeGitRepo ../../d)
+configure_file(CMakeLists.txt.in b/c/CMakeLists.txt @ONLY)
+initGitRepo("${CMAKE_CURRENT_BINARY_DIR}/b/c")
+
+file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/d")
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/d/CMakeLists.txt" [[
+cmake_minimum_required(VERSION 3.26)
+project(bottom LANGUAGES NONE)
+message(STATUS "Configured bottom project")
+]])
+initGitRepo("${CMAKE_CURRENT_BINARY_DIR}/d")
+
+set(clonedTopDir "${CMAKE_CURRENT_BINARY_DIR}/e/f")
+file(MAKE_DIRECTORY "${clonedTopDir}")
+execGitCommand(${CMAKE_CURRENT_BINARY_DIR} clone --quiet "file://${CMAKE_CURRENT_BINARY_DIR}/a" "${clonedTopDir}")
+add_subdirectory("${clonedTopDir}" "${CMAKE_CURRENT_BINARY_DIR}/g")
+
+# Ensure build order is predictable
+add_custom_target(non-ep-top ALL COMMAND ${CMAKE_COMMAND} -E echo "Non-ep top project")
+add_dependencies(non-ep-top ep-X)
+add_dependencies(ep-X ep-Y)
diff --git a/Tests/RunCMake/CMP0150/CMP0150-OLD-build-stdout.txt b/Tests/RunCMake/CMP0150/CMP0150-OLD-build-stdout.txt
new file mode 100644
index 0000000..0150af7
--- /dev/null
+++ b/Tests/RunCMake/CMP0150/CMP0150-OLD-build-stdout.txt
@@ -0,0 +1,3 @@
+.*-- Configured bottom project
+.*ExternalProject for ep-bottom
+.*Non-ep top project
diff --git a/Tests/RunCMake/CMP0150/CMP0150-OLD-common.cmake b/Tests/RunCMake/CMP0150/CMP0150-OLD-common.cmake
new file mode 100644
index 0000000..69748f7
--- /dev/null
+++ b/Tests/RunCMake/CMP0150/CMP0150-OLD-common.cmake
@@ -0,0 +1,21 @@
+# There's no point testing more than one level for OLD, since the behavior only
+# depends on the current build, not anything about the parent git repo, etc.
+set(projName top)
+set(depName bottom)
+set(epRelativeGitRepo ../../../Bottom)
+set(fcRelativeGitRepo ../Bottom)
+configure_file(CMakeLists.txt.in Top/CMakeLists.txt @ONLY)
+
+file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/Bottom")
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/Bottom/CMakeLists.txt" [[
+cmake_minimum_required(VERSION 3.26)
+project(bottom LANGUAGES NONE)
+message(STATUS "Configured bottom project")
+]])
+initGitRepo("${CMAKE_CURRENT_BINARY_DIR}/Bottom")
+
+add_subdirectory("${CMAKE_CURRENT_BINARY_DIR}/Top" "${CMAKE_CURRENT_BINARY_DIR}/Top-build")
+
+# Ensure build order is predictable
+add_custom_target(non-ep-top ALL COMMAND ${CMAKE_COMMAND} -E echo "Non-ep top project")
+add_dependencies(non-ep-top ep-bottom)
diff --git a/Tests/RunCMake/CMP0150/CMP0150-OLD-stdout.txt b/Tests/RunCMake/CMP0150/CMP0150-OLD-stdout.txt
new file mode 100644
index 0000000..680f9c1
--- /dev/null
+++ b/Tests/RunCMake/CMP0150/CMP0150-OLD-stdout.txt
@@ -0,0 +1,3 @@
+-- Configured bottom project
+-- Completed configuring project top
+-- Configuring done
diff --git a/Tests/RunCMake/CMP0150/CMP0150-OLD.cmake b/Tests/RunCMake/CMP0150/CMP0150-OLD.cmake
new file mode 100644
index 0000000..6b58e70
--- /dev/null
+++ b/Tests/RunCMake/CMP0150/CMP0150-OLD.cmake
@@ -0,0 +1,2 @@
+set(policyCommand "cmake_policy(SET CMP0150 OLD)")
+include(CMP0150-OLD-common.cmake)
diff --git a/Tests/RunCMake/CMP0150/CMP0150-WARN-build-stdout.txt b/Tests/RunCMake/CMP0150/CMP0150-WARN-build-stdout.txt
new file mode 100644
index 0000000..0150af7
--- /dev/null
+++ b/Tests/RunCMake/CMP0150/CMP0150-WARN-build-stdout.txt
@@ -0,0 +1,3 @@
+.*-- Configured bottom project
+.*ExternalProject for ep-bottom
+.*Non-ep top project
diff --git a/Tests/RunCMake/CMP0150/CMP0150-WARN-stderr.txt b/Tests/RunCMake/CMP0150/CMP0150-WARN-stderr.txt
new file mode 100644
index 0000000..74c932a
--- /dev/null
+++ b/Tests/RunCMake/CMP0150/CMP0150-WARN-stderr.txt
@@ -0,0 +1,25 @@
+CMake Warning \(dev\) at .*/Modules/ExternalProject/shared_internal_commands\.cmake:[0-9]+ \(message\):
+  Policy CMP0150 is not set: ExternalProject_Add and FetchContent_Declare
+  commands treat relative GIT_REPOSITORY paths as being relative to the
+  parent project's remote\.  Run "cmake --help-policy CMP0150" for policy
+  details\.  Use the cmake_policy command to set the policy and suppress this
+  warning\.
+
+  A relative GIT_REPOSITORY path was detected\.  This will be interpreted as a
+  local path to where the project is being cloned\.  Set GIT_REPOSITORY to an
+  absolute path or set policy CMP0150 to NEW to avoid this warning\.
+Call Stack \(most recent call first\):
+  .*/Modules/ExternalProject\.cmake:[0-9]+ \(_ep_resolve_git_remote\)
+.*
+CMake Warning \(dev\) at .*/Modules/ExternalProject/shared_internal_commands\.cmake:[0-9]+ \(message\):
+  Policy CMP0150 is not set: ExternalProject_Add and FetchContent_Declare
+  commands treat relative GIT_REPOSITORY paths as being relative to the
+  parent project's remote\.  Run "cmake --help-policy CMP0150" for policy
+  details\.  Use the cmake_policy command to set the policy and suppress this
+  warning\.
+
+  A relative GIT_REPOSITORY path was detected\.  This will be interpreted as a
+  local path to where the project is being cloned\.  Set GIT_REPOSITORY to an
+  absolute path or set policy CMP0150 to NEW to avoid this warning\.
+Call Stack \(most recent call first\):
+  .*/Modules/FetchContent\.cmake:[0-9]+ \(_ep_resolve_git_remote\)
diff --git a/Tests/RunCMake/CMP0150/CMP0150-WARN-stdout.txt b/Tests/RunCMake/CMP0150/CMP0150-WARN-stdout.txt
new file mode 100644
index 0000000..680f9c1
--- /dev/null
+++ b/Tests/RunCMake/CMP0150/CMP0150-WARN-stdout.txt
@@ -0,0 +1,3 @@
+-- Configured bottom project
+-- Completed configuring project top
+-- Configuring done
diff --git a/Tests/RunCMake/CMP0150/CMP0150-WARN.cmake b/Tests/RunCMake/CMP0150/CMP0150-WARN.cmake
new file mode 100644
index 0000000..20fd45d
--- /dev/null
+++ b/Tests/RunCMake/CMP0150/CMP0150-WARN.cmake
@@ -0,0 +1,2 @@
+set(policyCommand "")
+include(CMP0150-OLD-common.cmake)
diff --git a/Tests/RunCMake/CMP0150/CMakeLists.txt b/Tests/RunCMake/CMP0150/CMakeLists.txt
new file mode 100644
index 0000000..371dccc
--- /dev/null
+++ b/Tests/RunCMake/CMP0150/CMakeLists.txt
@@ -0,0 +1,27 @@
+cmake_minimum_required(VERSION 3.25)
+project(${RunCMake_TEST} NONE)
+
+find_package(Git REQUIRED)
+
+function(execGitCommand workDir)
+  execute_process(
+    WORKING_DIRECTORY "${workDir}"
+    COMMAND "${GIT_EXECUTABLE}" ${ARGN}
+    COMMAND_ECHO STDOUT
+    COMMAND_ERROR_IS_FATAL ANY
+  )
+endfunction()
+
+function(initGitRepo workDir)
+  # init.defaultBranch only works with git 2.28 or later, so we must use the
+  # historical default branch name "master". Force the old default in case test
+  # sites have overridden the default to something else.
+  execGitCommand("${workDir}" -c init.defaultBranch=master init)
+  execGitCommand("${workDir}" config user.email "testauthor@cmake.org")
+  execGitCommand("${workDir}" config user.name testauthor)
+  execGitCommand("${workDir}" config core.autocrlf false)
+  execGitCommand("${workDir}" add CMakeLists.txt)
+  execGitCommand("${workDir}" commit -m "Initial commit")
+endfunction()
+
+include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/CMP0150/CMakeLists.txt.in b/Tests/RunCMake/CMP0150/CMakeLists.txt.in
new file mode 100644
index 0000000..db6cfc7
--- /dev/null
+++ b/Tests/RunCMake/CMP0150/CMakeLists.txt.in
@@ -0,0 +1,23 @@
+cmake_minimum_required(VERSION 3.25)
+project(@projName@ LANGUAGES NONE)
+
+@policyCommand@
+
+include(ExternalProject)
+ExternalProject_Add(ep-@depName@
+  GIT_REPOSITORY @epRelativeGitRepo@
+  GIT_TAG master
+  GIT_CONFIG init.defaultBranch=master
+  TEST_COMMAND ""
+  INSTALL_COMMAND "${CMAKE_COMMAND}" -E echo "ExternalProject for ep-@depName@"
+)
+
+include(FetchContent)
+FetchContent_Declare(@depName@
+  GIT_REPOSITORY @fcRelativeGitRepo@
+  GIT_TAG master
+  GIT_CONFIG init.defaultBranch=master
+)
+FetchContent_MakeAvailable(@depName@)
+
+message(STATUS "Completed configuring project @projName@")
diff --git a/Tests/RunCMake/CMP0150/RunCMakeTest.cmake b/Tests/RunCMake/CMP0150/RunCMakeTest.cmake
new file mode 100644
index 0000000..940c33e
--- /dev/null
+++ b/Tests/RunCMake/CMP0150/RunCMakeTest.cmake
@@ -0,0 +1,17 @@
+include(RunCMake)
+
+function(test_CMP0150 val)
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${val}-build)
+  run_cmake(CMP0150-${val})
+  set(RunCMake_TEST_NO_CLEAN TRUE)
+  # Some git versions write clone messages to stderr. These would cause the
+  # test to fail, so we need to merge them into stdout.
+  set(RunCMake_TEST_OUTPUT_MERGE TRUE)
+  run_cmake_command(CMP0150-${val}-build ${CMAKE_COMMAND} --build .)
+endfunction()
+
+test_CMP0150(WARN)
+test_CMP0150(OLD)
+test_CMP0150(NEW)
+
+run_cmake_script(CMP0150-NEW-resolve)
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index 930122c..3997a74 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -15,6 +15,9 @@
   else()
     set(Test_Dir ${test})
   endif()
+  if(CMAKE_C_COMPILER_ID STREQUAL "LCC")
+    list(APPEND TEST_ARGS -DRunCMake_TEST_LCC=1)
+  endif()
   add_test(NAME RunCMake.${test} COMMAND ${CMAKE_CMAKE_COMMAND}
     -DCMAKE_MODULE_PATH=${CMAKE_CURRENT_SOURCE_DIR}
     -DRunCMake_GENERATOR_IS_MULTI_CONFIG=${_isMultiConfig}
@@ -41,6 +44,11 @@
   file(REMOVE_RECURSE "${TEST_CONFIG_DIR}")
   file(MAKE_DIRECTORY "${TEST_CONFIG_DIR}")
 
+  set(TEST_ARGS "")
+  if(CMAKE_C_COMPILER_ID STREQUAL "LCC")
+    list(APPEND TEST_ARGS -DRunCMake_TEST_LCC=1)
+  endif()
+
   foreach(type IN LISTS types)
     # generate prerequirements config file in cmake as ctest doesn't have as
     # much system information so it is easier to set programs and environment
@@ -68,6 +76,7 @@
         -DRunCMake_MAKE_PROGRAM=${CMake_TEST_EXPLICIT_MAKE_PROGRAM}
         -DRunCMake_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}/${test}
         -DRunCMake_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}/${type}/${test}
+        ${TEST_ARGS}
         -Dconfig_file=${TEST_CONFIG_DIR}/${type}_config.cmake
         -P "${CMAKE_CURRENT_SOURCE_DIR}/${test}/RunCMakeTest.cmake"
         )
@@ -151,6 +160,7 @@
 add_RunCMake_test(CMP0132)
 add_RunCMake_test(CMP0135)
 add_RunCMake_test(CMP0139)
+add_RunCMake_test(CMP0150)
 
 # The test for Policy 65 requires the use of the
 # CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS variable, which both the VS and Xcode
@@ -256,6 +266,10 @@
     -Dwith_qt_version=6
     "-DQt6_DIR:PATH=${Qt6_DIR}"
     "-DCMAKE_PREFIX_PATH:STRING=${base_dir}"
+    -DPSEUDO_TIDY=$<TARGET_FILE:pseudo_tidy>
+    -DPSEUDO_IWYU=$<TARGET_FILE:pseudo_iwyu>
+    -DPSEUDO_CPPLINT=$<TARGET_FILE:pseudo_cpplint>
+    -DPSEUDO_CPPCHECK=$<TARGET_FILE:pseudo_cppcheck>
   )
   set(want_NoQt_test FALSE)
 endif ()
@@ -270,8 +284,6 @@
   add_RunCMake_test(AutogenNoQt TEST_DIR Autogen)
 endif()
 
-add_RunCMake_test(ArtifactOutputDirs)
-
 if(NOT DEFINED CMake_TEST_BuildDepends_GNU_AS
     AND (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "LCC")
     AND CMAKE_GENERATOR MATCHES "^Ninja"
@@ -284,9 +296,14 @@
   endif()
 endif()
 
+if(CMAKE_Fortran_COMPILER)
+  list(APPEND BuildDepends_ARGS -DCMake_TEST_Fortran=1)
+endif()
+
 add_RunCMake_test(BuildDepends
   -DMSVC_VERSION=${MSVC_VERSION}
   -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID}
+  -DCMAKE_C_LINK_DEPENDS_USE_COMPILER=${CMAKE_C_LINK_DEPENDS_USE_COMPILER}
   -DCMake_TEST_BuildDepends_GNU_AS=${CMake_TEST_BuildDepends_GNU_AS}
   )
 if(UNIX AND "${CMAKE_GENERATOR}" MATCHES "Unix Makefiles|Ninja")
@@ -323,6 +340,10 @@
 add_RunCMake_test(ExcludeFromAll)
 add_RunCMake_test(ExportImport)
 add_RunCMake_test(ExternalData)
+if(CMAKE_GENERATOR MATCHES "^(Unix Makefiles|Ninja)$"
+    AND NOT "${CMAKE_CURRENT_BINARY_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}")
+  add_RunCMake_test(ExtraGenerators)
+endif()
 add_RunCMake_test(FeatureSummary)
 add_RunCMake_test(FPHSA)
 if(CMAKE_USE_SYSTEM_JSONCPP)
@@ -353,11 +374,13 @@
 add_RunCMake_test(GenEx-LINK_LIBRARY)
 add_RunCMake_test(GenEx-LINK_GROUP)
 add_RunCMake_test(GenEx-TARGET_FILE -DLINKER_SUPPORTS_PDB=${LINKER_SUPPORTS_PDB})
+add_RunCMake_test(GenEx-TARGET_IMPORT_FILE)
 add_RunCMake_test(GenEx-GENEX_EVAL)
 add_RunCMake_test(GenEx-TARGET_PROPERTY)
 add_RunCMake_test(GenEx-TARGET_RUNTIME_DLLS)
 add_RunCMake_test(GenEx-PATH)
 add_RunCMake_test(GenEx-PATH_EQUAL)
+add_RunCMake_test(GenEx-LIST)
 add_RunCMake_test(GeneratorExpression)
 add_RunCMake_test(GeneratorInstance)
 add_RunCMake_test(GeneratorPlatform)
@@ -392,6 +415,7 @@
 endif()
 add_RunCMake_test(ScriptMode)
 add_RunCMake_test(Swift -DCMAKE_Swift_COMPILER=${CMAKE_Swift_COMPILER} -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME})
+add_RunCMake_test(TargetArtifacts -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME})
 add_RunCMake_test(TargetObjects)
 add_RunCMake_test(TargetProperties)
 add_RunCMake_test(ToolchainFile)
@@ -511,6 +535,7 @@
 if(APPLE)
   add_RunCMake_test(INSTALL_NAME_DIR)
   add_RunCMake_test(MacOSVersions)
+  add_RunCMake_test(AppleTextStubs)
 endif()
 
 function(add_RunCMake_test_try_compile)
@@ -545,7 +570,10 @@
 add_RunCMake_test(InterfaceLibrary)
 add_RunCMake_test(no_install_prefix)
 add_RunCMake_test(configure_file)
-add_RunCMake_test(CTestTimeout -DTIMEOUT=${CTestTestTimeout_TIME})
+if(CTestTestTimeout_TIME)
+  set(CTestTimeout_ARGS -DTIMEOUT=${CTestTestTimeout_TIME})
+endif()
+add_RunCMake_test(CTestTimeout)
 add_RunCMake_test(CTestTimeoutAfterMatch)
 if(CMake_TEST_CUDA)
   add_RunCMake_test(CUDA_architectures)
@@ -648,7 +676,9 @@
 
 if(XCODE_VERSION)
   add_RunCMake_test(XcodeProject -DXCODE_VERSION=${XCODE_VERSION}
+                                 -DCMAKE_HOST_SYSTEM_PROCESSOR=${CMAKE_HOST_SYSTEM_PROCESSOR}
                                  -DCMake_TEST_Swift=${CMake_TEST_Swift})
+  add_RunCMake_test(XcodeProject-Device -DXCODE_VERSION=${XCODE_VERSION})
   add_RunCMake_test(XcodeProject-Embed -DXCODE_VERSION=${XCODE_VERSION})
 
   # This test can take a very long time due to lots of combinations.
@@ -766,6 +796,7 @@
 add_RunCMake_test(install -DNO_NAMELINK=${NO_NAMELINK} -DCYGWIN=${CYGWIN} -DMSYS=${MSYS}
   -DCMAKE_SHARED_LIBRARY_RPATH_ORIGIN_TOKEN=${CMAKE_SHARED_LIBRARY_RPATH_ORIGIN_TOKEN}
   -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
+  -DCMAKE_SYSTEM_PROCESSOR=${CMAKE_SYSTEM_PROCESSOR}
   -DCMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG=${CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG}
   -DCMAKE_EXECUTABLE_FORMAT=${CMAKE_EXECUTABLE_FORMAT}
   -DCMake_TEST_ISPC=${CMake_TEST_ISPC}
@@ -779,6 +810,7 @@
 endif()
 add_RunCMake_test(file-GET_RUNTIME_DEPENDENCIES
   -DCMake_INSTALL_NAME_TOOL_BUG=${CMake_INSTALL_NAME_TOOL_BUG}
+  -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID}
   )
 
 add_RunCMake_test(CPackCommandLine)
diff --git a/Tests/RunCMake/CMakePresets/Comment-stderr.txt b/Tests/RunCMake/CMakePresets/Comment-stderr.txt
index b3b6b66..8619cf5 100644
--- a/Tests/RunCMake/CMakePresets/Comment-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/Comment-stderr.txt
@@ -1,7 +1,6 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/Comment: JSON parse error
-Errors:
-[^
+]*/Tests/RunCMake/CMakePresets/Comment:
+JSON Parse Error: [^
 ]*Comment\/CMakePresets.json:
 \* Line 1, Column 1
   Syntax error: value, object or array expected\.
diff --git a/Tests/RunCMake/CMakePresets/ConditionFuture-stderr.txt b/Tests/RunCMake/CMakePresets/ConditionFuture-stderr.txt
index ea5f47f..31ebbc1 100644
--- a/Tests/RunCMake/CMakePresets/ConditionFuture-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/ConditionFuture-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/ConditionFuture: File version must be 3 or higher for condition support$
+]*/Tests/RunCMake/CMakePresets/ConditionFuture:
+File version must be 3 or higher for condition support$
diff --git a/Tests/RunCMake/CMakePresets/CyclicInheritance0-stderr.txt b/Tests/RunCMake/CMakePresets/CyclicInheritance0-stderr.txt
index 895afcb..2d5b477 100644
--- a/Tests/RunCMake/CMakePresets/CyclicInheritance0-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/CyclicInheritance0-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/CyclicInheritance0: Cyclic preset inheritance$
+]*/Tests/RunCMake/CMakePresets/CyclicInheritance0:
+Cyclic preset inheritance for preset "CyclicInheritance0"$
diff --git a/Tests/RunCMake/CMakePresets/CyclicInheritance1-stderr.txt b/Tests/RunCMake/CMakePresets/CyclicInheritance1-stderr.txt
index 1e59e92..596fcf8 100644
--- a/Tests/RunCMake/CMakePresets/CyclicInheritance1-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/CyclicInheritance1-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/CyclicInheritance1: Cyclic preset inheritance$
+]*/Tests/RunCMake/CMakePresets/CyclicInheritance1:
+Cyclic preset inheritance for preset "CyclicInheritance0"$
diff --git a/Tests/RunCMake/CMakePresets/CyclicInheritance2-stderr.txt b/Tests/RunCMake/CMakePresets/CyclicInheritance2-stderr.txt
index 56e630b..a6b83c8 100644
--- a/Tests/RunCMake/CMakePresets/CyclicInheritance2-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/CyclicInheritance2-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/CyclicInheritance2: Cyclic preset inheritance$
+]*/Tests/RunCMake/CMakePresets/CyclicInheritance2:
+Cyclic preset inheritance for preset "CyclicInheritance0"$
diff --git a/Tests/RunCMake/CMakePresets/DuplicatePresets-stderr.txt b/Tests/RunCMake/CMakePresets/DuplicatePresets-stderr.txt
index c9361ae..ebb9a2d 100644
--- a/Tests/RunCMake/CMakePresets/DuplicatePresets-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/DuplicatePresets-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/DuplicatePresets: Duplicate presets$
+]*/Tests/RunCMake/CMakePresets/DuplicatePresets:
+Duplicate preset: "DuplicatePresets"$
diff --git a/Tests/RunCMake/CMakePresets/EmptyCacheKey-stderr.txt b/Tests/RunCMake/CMakePresets/EmptyCacheKey-stderr.txt
index 749d306..cb9e545 100644
--- a/Tests/RunCMake/CMakePresets/EmptyCacheKey-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/EmptyCacheKey-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/EmptyCacheKey: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/EmptyCacheKey:
+Invalid preset: "EmptyCacheKey"$
diff --git a/Tests/RunCMake/CMakePresets/EmptyEnv-stderr.txt b/Tests/RunCMake/CMakePresets/EmptyEnv-stderr.txt
index 723ac21..7774fee 100644
--- a/Tests/RunCMake/CMakePresets/EmptyEnv-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/EmptyEnv-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/EmptyEnv: Invalid macro expansion$
+]*/Tests/RunCMake/CMakePresets/EmptyEnv:
+Invalid macro expansion in "EmptyEnv"$
diff --git a/Tests/RunCMake/CMakePresets/EmptyEnvKey-stderr.txt b/Tests/RunCMake/CMakePresets/EmptyEnvKey-stderr.txt
index 365f537..bc0f866 100644
--- a/Tests/RunCMake/CMakePresets/EmptyEnvKey-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/EmptyEnvKey-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/EmptyEnvKey: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/EmptyEnvKey:
+Invalid preset: "EmptyEnvKey"$
diff --git a/Tests/RunCMake/CMakePresets/EmptyPenv-stderr.txt b/Tests/RunCMake/CMakePresets/EmptyPenv-stderr.txt
index 880cee6..b3bfe21 100644
--- a/Tests/RunCMake/CMakePresets/EmptyPenv-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/EmptyPenv-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/EmptyPenv: Invalid macro expansion$
+]*/Tests/RunCMake/CMakePresets/EmptyPenv:
+Invalid macro expansion in "EmptyPenv"$
diff --git a/Tests/RunCMake/CMakePresets/EmptyPenvInInclude-result.txt b/Tests/RunCMake/CMakePresets/EmptyPenvInInclude-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CMakePresets/EmptyPenvInInclude-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CMakePresets/EmptyPenvInInclude-stderr.txt b/Tests/RunCMake/CMakePresets/EmptyPenvInInclude-stderr.txt
new file mode 100644
index 0000000..e0f858a
--- /dev/null
+++ b/Tests/RunCMake/CMakePresets/EmptyPenvInInclude-stderr.txt
@@ -0,0 +1,5 @@
+^CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresets/EmptyPenvInInclude:
+Error: @3,15: Invalid "include" field
+  "include": \["\$penv\{\}"\],
+              \^$
diff --git a/Tests/RunCMake/CMakePresets/EmptyPenvInInclude.json.in b/Tests/RunCMake/CMakePresets/EmptyPenvInInclude.json.in
new file mode 100644
index 0000000..651b0de
--- /dev/null
+++ b/Tests/RunCMake/CMakePresets/EmptyPenvInInclude.json.in
@@ -0,0 +1,11 @@
+{
+  "version": 7,
+  "include": ["$penv{}"],
+  "configurePresets": [
+    {
+      "name": "EmptyPenvInInclude",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build"
+    }
+  ]
+}
diff --git a/Tests/RunCMake/CMakePresets/EmptyPresetName-stderr.txt b/Tests/RunCMake/CMakePresets/EmptyPresetName-stderr.txt
index 0d3c500..6f84690 100644
--- a/Tests/RunCMake/CMakePresets/EmptyPresetName-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/EmptyPresetName-stderr.txt
@@ -1,5 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/EmptyPresetName: Invalid preset
-Errors:
-[^
-]*/EmptyPresetName/CMakePresets.json$
+]*/Tests/RunCMake/CMakePresets/EmptyPresetName:
+Error: @5,15: Invalid Preset Name
+      "name": "",
+              \^$
diff --git a/Tests/RunCMake/CMakePresets/EnvCycle-stderr.txt b/Tests/RunCMake/CMakePresets/EnvCycle-stderr.txt
index 1d22b87..8e19c69 100644
--- a/Tests/RunCMake/CMakePresets/EnvCycle-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/EnvCycle-stderr.txt
@@ -1,2 +1,4 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/EnvCycle: Invalid macro expansion$
+]*/Tests/RunCMake/CMakePresets/EnvCycle:
+Invalid preset: "EnvCycle"
+Invalid macro expansion in "EnvCycle"$
diff --git a/Tests/RunCMake/CMakePresets/ErrorNoWarningDeprecated-stderr.txt b/Tests/RunCMake/CMakePresets/ErrorNoWarningDeprecated-stderr.txt
index 3221345..5c02d5b 100644
--- a/Tests/RunCMake/CMakePresets/ErrorNoWarningDeprecated-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/ErrorNoWarningDeprecated-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/ErrorNoWarningDeprecated: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/ErrorNoWarningDeprecated:
+Invalid preset: "ErrorNoWarningDeprecated"$
diff --git a/Tests/RunCMake/CMakePresets/ErrorNoWarningDev-stderr.txt b/Tests/RunCMake/CMakePresets/ErrorNoWarningDev-stderr.txt
index d2ddb90..4640b1e 100644
--- a/Tests/RunCMake/CMakePresets/ErrorNoWarningDev-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/ErrorNoWarningDev-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/ErrorNoWarningDev: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/ErrorNoWarningDev:
+Invalid preset: "ErrorNoWarningDev"$
diff --git a/Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt b/Tests/RunCMake/CMakePresets/ExplicitNoTrace.cmake
similarity index 100%
rename from Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt
rename to Tests/RunCMake/CMakePresets/ExplicitNoTrace.cmake
diff --git a/Tests/RunCMake/CMakePresets/ExtraPresetField-stderr.txt b/Tests/RunCMake/CMakePresets/ExtraPresetField-stderr.txt
index 559e3c2..2e94831 100644
--- a/Tests/RunCMake/CMakePresets/ExtraPresetField-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/ExtraPresetField-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/ExtraPresetField: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/ExtraPresetField:
+Error: @8,18: Invalid extra field "invalid" in Preset
+      "invalid": true
+                 \^$
diff --git a/Tests/RunCMake/CMakePresets/ExtraRootField-stderr.txt b/Tests/RunCMake/CMakePresets/ExtraRootField-stderr.txt
index bb281be..554cd4a 100644
--- a/Tests/RunCMake/CMakePresets/ExtraRootField-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/ExtraRootField-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/ExtraRootField: Invalid root object$
+]*/Tests/RunCMake/CMakePresets/ExtraRootField:
+Error: @10,14: Invalid extra field "invalid" in root object
+  "invalid": true
+             \^$
diff --git a/Tests/RunCMake/CMakePresets/ExtraVariableField-stderr.txt b/Tests/RunCMake/CMakePresets/ExtraVariableField-stderr.txt
index 9b346e7..5cb777c 100644
--- a/Tests/RunCMake/CMakePresets/ExtraVariableField-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/ExtraVariableField-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/ExtraVariableField: Invalid CMake variable definition$
+]*/Tests/RunCMake/CMakePresets/ExtraVariableField:
+Error: @11,22: Invalid extra field "invalid" in variable "EXTRA" for preset "ExtraVariableField"
+          "invalid": true
+                     \^$
diff --git a/Tests/RunCMake/CMakePresets/FileDirFuture-stderr.txt b/Tests/RunCMake/CMakePresets/FileDirFuture-stderr.txt
index ba85f0f..b49df79 100644
--- a/Tests/RunCMake/CMakePresets/FileDirFuture-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/FileDirFuture-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/FileDirFuture: Invalid macro expansion$
+]*/Tests/RunCMake/CMakePresets/FileDirFuture:
+Invalid macro expansion in "FileDirFuture"$
diff --git a/Tests/RunCMake/CMakePresets/FuturePresetInstallDirField-stderr.txt b/Tests/RunCMake/CMakePresets/FuturePresetInstallDirField-stderr.txt
index 36123bd..ca481c6 100644
--- a/Tests/RunCMake/CMakePresets/FuturePresetInstallDirField-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/FuturePresetInstallDirField-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/FuturePresetInstallDirField: File version must be 3 or higher for installDir preset support.$
+]*/Tests/RunCMake/CMakePresets/FuturePresetInstallDirField:
+File version must be 3 or higher for installDir preset support$
diff --git a/Tests/RunCMake/CMakePresets/FuturePresetToolchainField-stderr.txt b/Tests/RunCMake/CMakePresets/FuturePresetToolchainField-stderr.txt
index 9382423..7aedc6d 100644
--- a/Tests/RunCMake/CMakePresets/FuturePresetToolchainField-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/FuturePresetToolchainField-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/FuturePresetToolchainField: File version must be 3 or higher for toolchainFile preset support.$
+]*/Tests/RunCMake/CMakePresets/FuturePresetToolchainField:
+File version must be 3 or higher for toolchainFile preset support$
diff --git a/Tests/RunCMake/CMakePresets/HighVersion-stderr.txt b/Tests/RunCMake/CMakePresets/HighVersion-stderr.txt
index d8622f2..598478f 100644
--- a/Tests/RunCMake/CMakePresets/HighVersion-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/HighVersion-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/HighVersion: Unrecognized "version" field$
+]*/Tests/RunCMake/CMakePresets/HighVersion:
+Error: @2,14: Unrecognized "version" field
+  "version": 1000,
+             \^$
diff --git a/Tests/RunCMake/CMakePresets/HostSystemNameFuture-stderr.txt b/Tests/RunCMake/CMakePresets/HostSystemNameFuture-stderr.txt
index 7f4bb9a..a1dae4c 100644
--- a/Tests/RunCMake/CMakePresets/HostSystemNameFuture-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/HostSystemNameFuture-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/HostSystemNameFuture: Invalid macro expansion$
+]*/Tests/RunCMake/CMakePresets/HostSystemNameFuture:
+Invalid macro expansion in "HostSystemNameFuture"$
diff --git a/Tests/RunCMake/CMakePresets/IncludeCycle-stderr.txt b/Tests/RunCMake/CMakePresets/IncludeCycle-stderr.txt
index 3343204..fc6292d 100644
--- a/Tests/RunCMake/CMakePresets/IncludeCycle-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/IncludeCycle-stderr.txt
@@ -1,2 +1,4 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/IncludeCycle: Cyclic include among preset files$
+]*/Tests/RunCMake/CMakePresets/IncludeCycle:
+Cyclic include among preset files: [^
+]*/CMakeUserPresets.json$
diff --git a/Tests/RunCMake/CMakePresets/IncludeCycle3Files-stderr.txt b/Tests/RunCMake/CMakePresets/IncludeCycle3Files-stderr.txt
index 35aea4c..1bc402a 100644
--- a/Tests/RunCMake/CMakePresets/IncludeCycle3Files-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/IncludeCycle3Files-stderr.txt
@@ -1,2 +1,4 @@
-^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/IncludeCycle3Files: Cyclic include among preset files$
+CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresets/IncludeCycle3Files:
+Cyclic include among preset files: [^
+]*/CMakePresets.json
diff --git a/Tests/RunCMake/CMakePresets/IncludeExpansion-stdout.txt b/Tests/RunCMake/CMakePresets/IncludeExpansion-stdout.txt
new file mode 100644
index 0000000..d3f1afc
--- /dev/null
+++ b/Tests/RunCMake/CMakePresets/IncludeExpansion-stdout.txt
@@ -0,0 +1,5 @@
+^Not searching for unused variables given on the command line\.
+Available configure presets:
+
+  "Include"
+  "IncludeCommon"$
diff --git a/Tests/RunCMake/CMakePresets/IncludeExpansion.json.in b/Tests/RunCMake/CMakePresets/IncludeExpansion.json.in
new file mode 100644
index 0000000..b4f8292
--- /dev/null
+++ b/Tests/RunCMake/CMakePresets/IncludeExpansion.json.in
@@ -0,0 +1,10 @@
+{
+  "version": 7,
+  "include": ["$penv{TEST_ENV_INCLUDE_DIR}/IncludeCommon.json"],
+  "configurePresets": [
+    {
+      "name": "Include",
+      "inherits": ["IncludeCommon"]
+    }
+  ]
+}
diff --git a/Tests/RunCMake/CMakePresets/IncludeNotFound-stderr.txt b/Tests/RunCMake/CMakePresets/IncludeNotFound-stderr.txt
index 85a2d78..fba5b01 100644
--- a/Tests/RunCMake/CMakePresets/IncludeNotFound-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/IncludeNotFound-stderr.txt
@@ -1,5 +1,4 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/IncludeNotFound: File not found
-Errors:
-[^
-]*/IncludeNotFound/NotFound.json: Failed to read file$
+]*/Tests/RunCMake/CMakePresets/IncludeNotFound:
+File not found: [^
+]*/IncludeNotFound/NotFound.json$
diff --git a/Tests/RunCMake/CMakePresets/IncludeV3-stderr.txt b/Tests/RunCMake/CMakePresets/IncludeV3-stderr.txt
index 1869b6d..7e975a7 100644
--- a/Tests/RunCMake/CMakePresets/IncludeV3-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/IncludeV3-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/IncludeV3: File version must be 4 or higher for include support$
+]*/Tests/RunCMake/CMakePresets/IncludeV3:
+File version must be 4 or higher for include support$
diff --git a/Tests/RunCMake/CMakePresets/IncludeV4V3-stderr.txt b/Tests/RunCMake/CMakePresets/IncludeV4V3-stderr.txt
index 89e3e3d..f14c583 100644
--- a/Tests/RunCMake/CMakePresets/IncludeV4V3-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/IncludeV4V3-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/IncludeV4V3: File version must be 4 or higher for include support$
+]*/Tests/RunCMake/CMakePresets/IncludeV4V3:
+File version must be 4 or higher for include support$
diff --git a/Tests/RunCMake/CMakePresets/InvalidArchitectureStrategy-stderr.txt b/Tests/RunCMake/CMakePresets/InvalidArchitectureStrategy-stderr.txt
index 4a4d4ce..5df075d 100644
--- a/Tests/RunCMake/CMakePresets/InvalidArchitectureStrategy-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/InvalidArchitectureStrategy-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidArchitectureStrategy: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/InvalidArchitectureStrategy:
+Error: @9,21: Invalid preset
+        "strategy": {}
+                    \^$
diff --git a/Tests/RunCMake/CMakePresets/InvalidInheritance-stderr.txt b/Tests/RunCMake/CMakePresets/InvalidInheritance-stderr.txt
index 97f3876..216f308 100644
--- a/Tests/RunCMake/CMakePresets/InvalidInheritance-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/InvalidInheritance-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidInheritance: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/InvalidInheritance:
+Invalid preset: "InvalidInheritance"$
diff --git a/Tests/RunCMake/CMakePresets/InvalidPresetBinaryDir-stderr.txt b/Tests/RunCMake/CMakePresets/InvalidPresetBinaryDir-stderr.txt
index 2fe8c66..deb35b4 100644
--- a/Tests/RunCMake/CMakePresets/InvalidPresetBinaryDir-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/InvalidPresetBinaryDir-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidPresetBinaryDir: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/InvalidPresetBinaryDir:
+Error: @7,20: "binaryDir" expected a string
+      "binaryDir": \[\]
+                   \^$
diff --git a/Tests/RunCMake/CMakePresets/InvalidPresetGenerator-stderr.txt b/Tests/RunCMake/CMakePresets/InvalidPresetGenerator-stderr.txt
index 9572875..7f19412 100644
--- a/Tests/RunCMake/CMakePresets/InvalidPresetGenerator-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/InvalidPresetGenerator-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidPresetGenerator: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/InvalidPresetGenerator:
+Error: @6,20: "generator" expected a string
+      "generator": \[\],
+                   \^$
diff --git a/Tests/RunCMake/CMakePresets/InvalidPresetName-stderr.txt b/Tests/RunCMake/CMakePresets/InvalidPresetName-stderr.txt
index 8f6ff7c..4cceb73 100644
--- a/Tests/RunCMake/CMakePresets/InvalidPresetName-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/InvalidPresetName-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidPresetName: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/InvalidPresetName:
+Error: @5,15: Invalid Preset Name
+      "name": \[\],
+              \^$
diff --git a/Tests/RunCMake/CMakePresets/InvalidPresetVendor-stderr.txt b/Tests/RunCMake/CMakePresets/InvalidPresetVendor-stderr.txt
index 89a424a..f92c48e 100644
--- a/Tests/RunCMake/CMakePresets/InvalidPresetVendor-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/InvalidPresetVendor-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidPresetVendor: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/InvalidPresetVendor:
+Error: @8,17: Invalid preset
+      "vendor": true
+                \^$
diff --git a/Tests/RunCMake/CMakePresets/InvalidPresets-stderr.txt b/Tests/RunCMake/CMakePresets/InvalidPresets-stderr.txt
index 2b0f560..d7081af 100644
--- a/Tests/RunCMake/CMakePresets/InvalidPresets-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/InvalidPresets-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidPresets: Invalid "configurePresets" field$
+]*/Tests/RunCMake/CMakePresets/InvalidPresets:
+Error: @3,23: Invalid "configurePresets" field
+  "configurePresets": {}
+                      \^$
diff --git a/Tests/RunCMake/CMakePresets/InvalidRegex-stderr.txt b/Tests/RunCMake/CMakePresets/InvalidRegex-stderr.txt
index 5b500e4..86cd861 100644
--- a/Tests/RunCMake/CMakePresets/InvalidRegex-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/InvalidRegex-stderr.txt
@@ -1,2 +1,4 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidRegex: Invalid macro expansion$
+]*/Tests/RunCMake/CMakePresets/InvalidRegex:
+Invalid preset: "InvalidRegex"
+Invalid macro expansion in "InvalidRegex"$
diff --git a/Tests/RunCMake/CMakePresets/InvalidRoot-stderr.txt b/Tests/RunCMake/CMakePresets/InvalidRoot-stderr.txt
index e5c434d..5f2dcc1 100644
--- a/Tests/RunCMake/CMakePresets/InvalidRoot-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/InvalidRoot-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidRoot: Invalid root object$
+]*/Tests/RunCMake/CMakePresets/InvalidRoot:
+Error: \@1\,1\: Invalid root object
+\[\]
+\^$
diff --git a/Tests/RunCMake/CMakePresets/InvalidToolsetStrategy-stderr.txt b/Tests/RunCMake/CMakePresets/InvalidToolsetStrategy-stderr.txt
index fab3766..d0974ac 100644
--- a/Tests/RunCMake/CMakePresets/InvalidToolsetStrategy-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/InvalidToolsetStrategy-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidToolsetStrategy: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/InvalidToolsetStrategy:
+Error: @9,21: Invalid preset
+        "strategy": {}
+                    \^$
diff --git a/Tests/RunCMake/CMakePresets/InvalidVariableValue-stderr.txt b/Tests/RunCMake/CMakePresets/InvalidVariableValue-stderr.txt
index 0ab07c3..fdb7072 100644
--- a/Tests/RunCMake/CMakePresets/InvalidVariableValue-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/InvalidVariableValue-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidVariableValue: Invalid CMake variable definition$
+]*/Tests/RunCMake/CMakePresets/InvalidVariableValue:
+Error: @10,20: "value" expected a string
+          "value": \[\]
+                   \^$
diff --git a/Tests/RunCMake/CMakePresets/InvalidVariables-stderr.txt b/Tests/RunCMake/CMakePresets/InvalidVariables-stderr.txt
index 6d9102a..f329d97 100644
--- a/Tests/RunCMake/CMakePresets/InvalidVariables-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/InvalidVariables-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidVariables: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/InvalidVariables:
+Error: @8,25: Invalid preset
+      "cacheVariables": \[\]
+                        \^$
diff --git a/Tests/RunCMake/CMakePresets/InvalidVendor-stderr.txt b/Tests/RunCMake/CMakePresets/InvalidVendor-stderr.txt
index af923f0..fc6534e 100644
--- a/Tests/RunCMake/CMakePresets/InvalidVendor-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/InvalidVendor-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidVendor: Invalid root object$
+]*/Tests/RunCMake/CMakePresets/InvalidVendor:
+Error: @3,13: Invalid root object
+  "vendor": true,
+            \^$
diff --git a/Tests/RunCMake/CMakePresets/InvalidVersion-stderr.txt b/Tests/RunCMake/CMakePresets/InvalidVersion-stderr.txt
index 7e0fcfd..97fe9d6 100644
--- a/Tests/RunCMake/CMakePresets/InvalidVersion-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/InvalidVersion-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidVersion: Invalid "version" field$
+]*/Tests/RunCMake/CMakePresets/InvalidVersion:
+Error: @2,14: Invalid "version" field
+  "version": "1.0",
+             \^$
diff --git a/Tests/RunCMake/CMakePresets/JSONParseError-stderr.txt b/Tests/RunCMake/CMakePresets/JSONParseError-stderr.txt
index 89eff9f..92a1b2b 100644
--- a/Tests/RunCMake/CMakePresets/JSONParseError-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/JSONParseError-stderr.txt
@@ -1,9 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/JSONParseError: JSON parse error
-Errors:
-[^
-]*JSONParseError/CMakePresets.json:
-\* Line 1, Column 1
-  Syntax error: value, object or array expected\.
-\* Line 1, Column 1
-  A valid JSON document must be either an array or an object value\.$
+]*/Tests/RunCMake/CMakePresets/JSONParseError:
+A JSON document cannot be empty
diff --git a/Tests/RunCMake/CMakePresets/LowVersion-stderr.txt b/Tests/RunCMake/CMakePresets/LowVersion-stderr.txt
index 92b3723..e933100 100644
--- a/Tests/RunCMake/CMakePresets/LowVersion-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/LowVersion-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/LowVersion: Unrecognized "version" field$
+]*/Tests/RunCMake/CMakePresets/LowVersion:
+Error: @2,14: Unrecognized "version" field
+  "version": 0,
+             \^
diff --git a/Tests/RunCMake/CMakePresets/MinimumRequiredInvalid-stderr.txt b/Tests/RunCMake/CMakePresets/MinimumRequiredInvalid-stderr.txt
index 6548caf..30ab9ce 100644
--- a/Tests/RunCMake/CMakePresets/MinimumRequiredInvalid-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/MinimumRequiredInvalid-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/MinimumRequiredInvalid: Invalid "cmakeMinimumRequired" field$
+]*/Tests/RunCMake/CMakePresets/MinimumRequiredInvalid:
+Error: @3,27: Invalid "cmakeMinimumRequired"
+  "cmakeMinimumRequired": "3.18",
+                          \^$
diff --git a/Tests/RunCMake/CMakePresets/MinimumRequiredMajor-stderr.txt b/Tests/RunCMake/CMakePresets/MinimumRequiredMajor-stderr.txt
index 6036fe3..c9001ea 100644
--- a/Tests/RunCMake/CMakePresets/MinimumRequiredMajor-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/MinimumRequiredMajor-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/MinimumRequiredMajor: "cmakeMinimumRequired" version too new$
+]*/Tests/RunCMake/CMakePresets/MinimumRequiredMajor:
+Error: @4,14: "cmakeMinimumRequired" major version 1000 must be less than [0-9]*
+    "major": 1000
+             \^$
diff --git a/Tests/RunCMake/CMakePresets/MinimumRequiredMinor-stderr.txt b/Tests/RunCMake/CMakePresets/MinimumRequiredMinor-stderr.txt
index bdee4cd..3911c84 100644
--- a/Tests/RunCMake/CMakePresets/MinimumRequiredMinor-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/MinimumRequiredMinor-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/MinimumRequiredMinor: "cmakeMinimumRequired" version too new$
+]*/Tests/RunCMake/CMakePresets/MinimumRequiredMinor:
+Error: @5,14: "cmakeMinimumRequired" minor version 1000 must be less than [0-9]*
+    "minor": 1000
+             \^$
diff --git a/Tests/RunCMake/CMakePresets/MinimumRequiredPatch-stderr.txt b/Tests/RunCMake/CMakePresets/MinimumRequiredPatch-stderr.txt
index b5d3a39..e989fa8 100644
--- a/Tests/RunCMake/CMakePresets/MinimumRequiredPatch-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/MinimumRequiredPatch-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/MinimumRequiredPatch: "cmakeMinimumRequired" version too new$
+]*/Tests/RunCMake/CMakePresets/MinimumRequiredPatch:
+Error: @6,14: "cmakeMinimumRequired" patch version 50000000 must be less than [0-9]*
+    "patch": 50000000
+             \^$
diff --git a/Tests/RunCMake/CMakePresets/NoCMakePresets-stderr.txt b/Tests/RunCMake/CMakePresets/NoCMakePresets-stderr.txt
index c807ffc..afc5887 100644
--- a/Tests/RunCMake/CMakePresets/NoCMakePresets-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/NoCMakePresets-stderr.txt
@@ -1,2 +1,4 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/NoCMakePresets: File not found$
+]*/Tests/RunCMake/CMakePresets/NoCMakePresets:
+File not found: [^
+]*/CMakePresets.json$
diff --git a/Tests/RunCMake/CMakePresets/NoPresetBinaryDir-stderr.txt b/Tests/RunCMake/CMakePresets/NoPresetBinaryDir-stderr.txt
index b525fc3..bae9794 100644
--- a/Tests/RunCMake/CMakePresets/NoPresetBinaryDir-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/NoPresetBinaryDir-stderr.txt
@@ -1,2 +1,4 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/NoPresetBinaryDir: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/NoPresetBinaryDir:
+Preset "NoPresetBinaryDir" missing field "binaryDir"
+Invalid preset: "NoPresetBinaryDir"$
diff --git a/Tests/RunCMake/CMakePresets/NoPresetGenerator-stderr.txt b/Tests/RunCMake/CMakePresets/NoPresetGenerator-stderr.txt
index 6c0c9f7..c7e5b5e 100644
--- a/Tests/RunCMake/CMakePresets/NoPresetGenerator-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/NoPresetGenerator-stderr.txt
@@ -1,2 +1,4 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/NoPresetGenerator: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/NoPresetGenerator:
+Preset "NoPresetGenerator" missing field "generator"
+Invalid preset: "NoPresetGenerator"$
diff --git a/Tests/RunCMake/CMakePresets/NoPresetName-stderr.txt b/Tests/RunCMake/CMakePresets/NoPresetName-stderr.txt
index 0ee338a..9aff07f 100644
--- a/Tests/RunCMake/CMakePresets/NoPresetName-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/NoPresetName-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/NoPresetName: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/NoPresetName:
+Error: @4,5: Missing required field "name" in Preset
+    {
+    \^$
diff --git a/Tests/RunCMake/CMakePresets/NoSuchMacro-stderr.txt b/Tests/RunCMake/CMakePresets/NoSuchMacro-stderr.txt
index 7dafe62..8c7ca6e 100644
--- a/Tests/RunCMake/CMakePresets/NoSuchMacro-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/NoSuchMacro-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/NoSuchMacro: Invalid macro expansion$
+]*/Tests/RunCMake/CMakePresets/NoSuchMacro:
+Invalid macro expansion in "NoSuchMacro"$
diff --git a/Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt b/Tests/RunCMake/CMakePresets/NoTrace.cmake
similarity index 100%
copy from Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt
copy to Tests/RunCMake/CMakePresets/NoTrace.cmake
diff --git a/Tests/RunCMake/CMakePresets/NoVariableValue-stderr.txt b/Tests/RunCMake/CMakePresets/NoVariableValue-stderr.txt
index cdab32f..630c288 100644
--- a/Tests/RunCMake/CMakePresets/NoVariableValue-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/NoVariableValue-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/NoVariableValue: Invalid CMake variable definition$
+]*/Tests/RunCMake/CMakePresets/NoVariableValue:
+Error: @9,16: Missing required field "value" in variable "VAR" for preset "NoVariableValue"
+        "VAR": {}
+               \^$
diff --git a/Tests/RunCMake/CMakePresets/NoVersion-stderr.txt b/Tests/RunCMake/CMakePresets/NoVersion-stderr.txt
index d4f07e4..f7c95db 100644
--- a/Tests/RunCMake/CMakePresets/NoVersion-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/NoVersion-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/NoVersion: No "version" field$
+]*/Tests/RunCMake/CMakePresets/NoVersion:
+No "version" field$
diff --git a/Tests/RunCMake/CMakePresets/PathListSepFuture-stderr.txt b/Tests/RunCMake/CMakePresets/PathListSepFuture-stderr.txt
index b961aaf..7652ddc 100644
--- a/Tests/RunCMake/CMakePresets/PathListSepFuture-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/PathListSepFuture-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/PathListSepFuture: Invalid macro expansion$
+]*/Tests/RunCMake/CMakePresets/PathListSepFuture:
+Invalid macro expansion in "PathListSepFuture"$
diff --git a/Tests/RunCMake/CMakePresets/PresetNotObject-stderr.txt b/Tests/RunCMake/CMakePresets/PresetNotObject-stderr.txt
index 6604a14..62e9248 100644
--- a/Tests/RunCMake/CMakePresets/PresetNotObject-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/PresetNotObject-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/PresetNotObject: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/PresetNotObject:
+Error: @4,5: Invalid Preset
+    \[\]
+    \^$
diff --git a/Tests/RunCMake/CMakePresets/RunCMakeTest.cmake b/Tests/RunCMake/CMakePresets/RunCMakeTest.cmake
index efa838e..c4a8b3f 100644
--- a/Tests/RunCMake/CMakePresets/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMakePresets/RunCMakeTest.cmake
@@ -146,10 +146,12 @@
 run_cmake_presets(EnvCycle)
 run_cmake_presets(EmptyEnv)
 run_cmake_presets(EmptyPenv)
+run_cmake_presets(EmptyPenvInInclude)
 run_cmake_presets(InvalidRegex)
 set(CMakePresets_SCHEMA_EXPECTED_RESULT 1)
 run_cmake_presets(ConditionFuture)
 run_cmake_presets(SubConditionNull)
+run_cmake_presets(TraceNotSupported)
 
 # Test cmakeMinimumRequired field
 run_cmake_presets(MinimumRequiredInvalid)
@@ -326,6 +328,18 @@
 run_cmake_presets(NoDebug)
 run_cmake_presets(Debug)
 
+# Test trace
+set(CMakePresets_FILE "${RunCMake_SOURCE_DIR}/Trace.json.in")
+run_cmake_presets(NoTrace)
+run_cmake_presets(ExplicitNoTrace)
+run_cmake_presets(Trace)
+run_cmake_presets(TraceExpand)
+run_cmake_presets(TraceFormatJSON)
+run_cmake_presets(TraceFormatHuman)
+run_cmake_presets(TraceSource)
+run_cmake_presets(TraceRedirect)
+run_cmake_presets(TraceAll)
+
 # Test ${hostSystemName} macro
 set(CMakePresets_FILE "${RunCMake_SOURCE_DIR}/HostSystemName.json.in")
 run_cmake_presets(HostSystemName)
@@ -380,6 +394,12 @@
   "${RunCMake_SOURCE_DIR}/subdir/CMakePresets.json.in"
   )
 run_cmake_presets(Include --list-presets)
+set(CMakePresets_EXTRA_FILES
+  "${RunCMake_SOURCE_DIR}/IncludeCommon.json.in"
+  )
+set(ENV{TEST_ENV_INCLUDE_DIR} ${RunCMake_BINARY_DIR}/IncludeExpansion)
+run_cmake_presets(IncludeExpansion --list-presets)
+unset(ENV{TEST_ENV_INCLUDE_DIR})
 unset(CMakePresets_EXTRA_FILES)
 run_cmake_presets(IncludeNotFound)
 run_cmake_presets(IncludeCycle)
diff --git a/Tests/RunCMake/CMakePresets/SubConditionNull-stderr.txt b/Tests/RunCMake/CMakePresets/SubConditionNull-stderr.txt
index 42b74d6..520b473 100644
--- a/Tests/RunCMake/CMakePresets/SubConditionNull-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/SubConditionNull-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/SubConditionNull: Invalid preset condition$
+]*/Tests/RunCMake/CMakePresets/SubConditionNull:
+Invalid condition for preset "SubConditionNull"$
diff --git a/Tests/RunCMake/CMakePresets/Trace-stderr.txt b/Tests/RunCMake/CMakePresets/Trace-stderr.txt
new file mode 100644
index 0000000..5ed769c
--- /dev/null
+++ b/Tests/RunCMake/CMakePresets/Trace-stderr.txt
@@ -0,0 +1,4 @@
+^[^
+]*/Tests/RunCMake/CMakePresets/Trace/CMakeLists.txt\(1\):  cmake_minimum_required\(VERSION 3.18 \)
+[^
+]*/Tests/RunCMake/CMakePresets/Trace/CMakeLists.txt\(2\):  project\(\${RunCMake_TEST} NONE \)
diff --git a/Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt b/Tests/RunCMake/CMakePresets/Trace.cmake
similarity index 100%
copy from Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt
copy to Tests/RunCMake/CMakePresets/Trace.cmake
diff --git a/Tests/RunCMake/CMakePresets/Trace.json.in b/Tests/RunCMake/CMakePresets/Trace.json.in
new file mode 100644
index 0000000..f50a6f2
--- /dev/null
+++ b/Tests/RunCMake/CMakePresets/Trace.json.in
@@ -0,0 +1,69 @@
+{
+  "version": 7,
+  "configurePresets": [
+    {
+      "name": "NoTrace",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build"
+    },
+    {
+      "name": "ExplicitNoTrace",
+      "inherits": "NoTrace",
+      "trace": {
+        "mode": "off"
+      }
+    },
+    {
+      "name": "Trace",
+      "inherits": "NoTrace",
+      "trace": {
+        "mode": "on"
+      }
+    },
+    {
+      "name": "TraceExpand",
+      "inherits": "NoTrace",
+      "trace": {
+        "mode": "expand"
+      }
+    },
+    {
+      "name": "TraceFormatJSON",
+      "inherits": "NoTrace",
+      "trace": {
+        "format": "json-v1"
+      }
+    },
+    {
+      "name": "TraceFormatHuman",
+      "inherits": "NoTrace",
+      "trace": {
+        "format": "human"
+      }
+    },
+    {
+      "name": "TraceSource",
+      "inherits": "NoTrace",
+      "trace": {
+        "source": "TraceSourceFile.txt"
+      }
+    },
+    {
+      "name": "TraceRedirect",
+      "inherits": "NoTrace",
+      "trace": {
+        "redirect": "TraceRedirectFile.txt"
+      }
+    },
+    {
+      "name": "TraceAll",
+      "inherits": "NoTrace",
+      "trace": {
+        "mode": "expand",
+        "format": "json-v1",
+        "source": "TraceSourceFile.txt",
+        "redirect": "TraceRedirectFile.json"
+      }
+    }
+  ]
+}
diff --git a/Tests/RunCMake/CMakePresets/TraceAll.cmake b/Tests/RunCMake/CMakePresets/TraceAll.cmake
new file mode 100644
index 0000000..9896ec0
--- /dev/null
+++ b/Tests/RunCMake/CMakePresets/TraceAll.cmake
@@ -0,0 +1,4 @@
+include(${CMAKE_CURRENT_LIST_DIR}/TraceExpand.cmake)
+include(${CMAKE_CURRENT_LIST_DIR}/TraceFormatJSON.cmake)
+include(${CMAKE_CURRENT_LIST_DIR}/TraceSource.cmake)
+include(${CMAKE_CURRENT_LIST_DIR}/TraceRedirect.cmake)
diff --git a/Tests/RunCMake/CMakePresets/TraceExpand-stderr.txt b/Tests/RunCMake/CMakePresets/TraceExpand-stderr.txt
new file mode 100644
index 0000000..7ee4fea
--- /dev/null
+++ b/Tests/RunCMake/CMakePresets/TraceExpand-stderr.txt
@@ -0,0 +1,4 @@
+^[^
+]*/Tests/RunCMake/CMakePresets/TraceExpand/CMakeLists.txt\(1\):  cmake_minimum_required\(VERSION 3.18 \)
+[^
+]*/Tests/RunCMake/CMakePresets/TraceExpand/CMakeLists.txt\(2\):  project\(TraceExpand NONE \)
diff --git a/Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt b/Tests/RunCMake/CMakePresets/TraceExpand.cmake
similarity index 100%
copy from Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt
copy to Tests/RunCMake/CMakePresets/TraceExpand.cmake
diff --git a/Tests/RunCMake/CMakePresets/TraceFormatHuman-stderr.txt b/Tests/RunCMake/CMakePresets/TraceFormatHuman-stderr.txt
new file mode 100644
index 0000000..1d3450d
--- /dev/null
+++ b/Tests/RunCMake/CMakePresets/TraceFormatHuman-stderr.txt
@@ -0,0 +1,4 @@
+^[^
+]*/Tests/RunCMake/CMakePresets/TraceFormatHuman/CMakeLists.txt\(1\):  cmake_minimum_required\(VERSION 3.18 \)
+[^
+]*/Tests/RunCMake/CMakePresets/TraceFormatHuman/CMakeLists.txt\(2\):  project\(\${RunCMake_TEST} NONE \)
diff --git a/Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt b/Tests/RunCMake/CMakePresets/TraceFormatHuman.cmake
similarity index 100%
copy from Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt
copy to Tests/RunCMake/CMakePresets/TraceFormatHuman.cmake
diff --git a/Tests/RunCMake/CMakePresets/TraceFormatJSON-stderr.txt b/Tests/RunCMake/CMakePresets/TraceFormatJSON-stderr.txt
new file mode 100644
index 0000000..edf044c
--- /dev/null
+++ b/Tests/RunCMake/CMakePresets/TraceFormatJSON-stderr.txt
@@ -0,0 +1,3 @@
+^{"version":{"major":1,"minor":2}}
+{"args":\["VERSION","3\.18"\],"cmd":"cmake_minimum_required","file":"[^"]*/Tests/RunCMake/CMakePresets/TraceFormatJSON/CMakeLists\.txt","frame":1,"global_frame":1,"line":1,"time":[0-9.]+}
+{"args":\["\${RunCMake_TEST}","NONE"\],"cmd":"project","file":"[^"]*/Tests/RunCMake/CMakePresets/TraceFormatJSON/CMakeLists\.txt","frame":1,"global_frame":1,"line":2,"time":[0-9.]+}
diff --git a/Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt b/Tests/RunCMake/CMakePresets/TraceFormatJSON.cmake
similarity index 100%
copy from Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt
copy to Tests/RunCMake/CMakePresets/TraceFormatJSON.cmake
diff --git a/Tests/RunCMake/CMakePresets/TraceNotSupported-result.txt b/Tests/RunCMake/CMakePresets/TraceNotSupported-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CMakePresets/TraceNotSupported-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CMakePresets/TraceNotSupported-stderr.txt b/Tests/RunCMake/CMakePresets/TraceNotSupported-stderr.txt
new file mode 100644
index 0000000..de19a8c
--- /dev/null
+++ b/Tests/RunCMake/CMakePresets/TraceNotSupported-stderr.txt
@@ -0,0 +1,3 @@
+^CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresets/TraceNotSupported:
+File version must be 7 or higher for trace preset support$
diff --git a/Tests/RunCMake/CMakePresets/TraceNotSupported.json.in b/Tests/RunCMake/CMakePresets/TraceNotSupported.json.in
new file mode 100644
index 0000000..f3d3fbd
--- /dev/null
+++ b/Tests/RunCMake/CMakePresets/TraceNotSupported.json.in
@@ -0,0 +1,13 @@
+{
+  "version": 6,
+  "configurePresets": [
+    {
+      "name": "TraceNotSupported",
+      "generator": "@RunCMake_GENERATOR@",
+      "binaryDir": "${sourceDir}/build",
+      "trace": {
+        "mode": "expand"
+      }
+    }
+  ]
+}
diff --git a/Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt b/Tests/RunCMake/CMakePresets/TraceRedirect.cmake
similarity index 100%
copy from Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt
copy to Tests/RunCMake/CMakePresets/TraceRedirect.cmake
diff --git a/Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt b/Tests/RunCMake/CMakePresets/TraceSource.cmake
similarity index 100%
copy from Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt
copy to Tests/RunCMake/CMakePresets/TraceSource.cmake
diff --git a/Tests/RunCMake/CMakePresets/UnclosedMacro-stderr.txt b/Tests/RunCMake/CMakePresets/UnclosedMacro-stderr.txt
index f9481f0..abdbb81 100644
--- a/Tests/RunCMake/CMakePresets/UnclosedMacro-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/UnclosedMacro-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/UnclosedMacro: Invalid macro expansion$
+]*/Tests/RunCMake/CMakePresets/UnclosedMacro:
+Invalid macro expansion in "UnclosedMacro"$
diff --git a/Tests/RunCMake/CMakePresets/UnknownArchitectureStrategy-stderr.txt b/Tests/RunCMake/CMakePresets/UnknownArchitectureStrategy-stderr.txt
index cf17881..6063762 100644
--- a/Tests/RunCMake/CMakePresets/UnknownArchitectureStrategy-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/UnknownArchitectureStrategy-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/UnknownArchitectureStrategy: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/UnknownArchitectureStrategy:
+Error: @9,21: Invalid preset
+        "strategy": "unknown"
+                    \^$
diff --git a/Tests/RunCMake/CMakePresets/UnknownToolsetStrategy-stderr.txt b/Tests/RunCMake/CMakePresets/UnknownToolsetStrategy-stderr.txt
index 8f9be29..55f9c7a 100644
--- a/Tests/RunCMake/CMakePresets/UnknownToolsetStrategy-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/UnknownToolsetStrategy-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/UnknownToolsetStrategy: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/UnknownToolsetStrategy:
+Error: @9,21: Invalid preset
+        "strategy": "unknown"
+                    \^$
diff --git a/Tests/RunCMake/CMakePresets/UserDuplicateCross-stderr.txt b/Tests/RunCMake/CMakePresets/UserDuplicateCross-stderr.txt
index 125265f..8b4139f 100644
--- a/Tests/RunCMake/CMakePresets/UserDuplicateCross-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/UserDuplicateCross-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/UserDuplicateCross: Duplicate presets$
+]*/Tests/RunCMake/CMakePresets/UserDuplicateCross:
+Duplicate preset: "UserDuplicateCross"$
diff --git a/Tests/RunCMake/CMakePresets/UserDuplicateInUser-stderr.txt b/Tests/RunCMake/CMakePresets/UserDuplicateInUser-stderr.txt
index 1071b17..15c46f4 100644
--- a/Tests/RunCMake/CMakePresets/UserDuplicateInUser-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/UserDuplicateInUser-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/UserDuplicateInUser: Duplicate presets$
+]*/Tests/RunCMake/CMakePresets/UserDuplicateInUser:
+Duplicate preset: "UserDuplicateInUser"$
diff --git a/Tests/RunCMake/CMakePresets/UserInheritance-stderr.txt b/Tests/RunCMake/CMakePresets/UserInheritance-stderr.txt
index 5ad8b4b..2ce1316 100644
--- a/Tests/RunCMake/CMakePresets/UserInheritance-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/UserInheritance-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/UserInheritance: Inherited preset is unreachable from preset's file$
+]*/Tests/RunCMake/CMakePresets/UserInheritance:
+Inherited preset "UserInheritance" is unreachable from preset's file$
diff --git a/Tests/RunCMake/CMakePresets/VariableNotObject-stderr.txt b/Tests/RunCMake/CMakePresets/VariableNotObject-stderr.txt
index 8cacb0a..ce1aa20 100644
--- a/Tests/RunCMake/CMakePresets/VariableNotObject-stderr.txt
+++ b/Tests/RunCMake/CMakePresets/VariableNotObject-stderr.txt
@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/VariableNotObject: Invalid CMake variable definition$
+]*/Tests/RunCMake/CMakePresets/VariableNotObject:
+Error: @9,16: Invalid CMake variable "VAR" for preset "VariableNotObject"
+        "VAR": \[\]
+               \^$
diff --git a/Tests/RunCMake/CMakePresetsBuild/ConditionFuture-build-conditionFuture-stderr.txt b/Tests/RunCMake/CMakePresetsBuild/ConditionFuture-build-conditionFuture-stderr.txt
index f08f4c1..aea1dce 100644
--- a/Tests/RunCMake/CMakePresetsBuild/ConditionFuture-build-conditionFuture-stderr.txt
+++ b/Tests/RunCMake/CMakePresetsBuild/ConditionFuture-build-conditionFuture-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsBuild/ConditionFuture: File version must be 3 or higher for condition support$
+]*/Tests/RunCMake/CMakePresetsBuild/ConditionFuture:
+File version must be 3 or higher for condition support$
diff --git a/Tests/RunCMake/CMakePresetsBuild/ConfigurePresetUnreachable-build-x-stderr.txt b/Tests/RunCMake/CMakePresetsBuild/ConfigurePresetUnreachable-build-x-stderr.txt
index 05695d9..13b4158 100644
--- a/Tests/RunCMake/CMakePresetsBuild/ConfigurePresetUnreachable-build-x-stderr.txt
+++ b/Tests/RunCMake/CMakePresetsBuild/ConfigurePresetUnreachable-build-x-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsBuild/ConfigurePresetUnreachable: Configure preset is unreachable from preset's file$
+]*/Tests/RunCMake/CMakePresetsBuild/ConfigurePresetUnreachable:
+Configure preset "x" is unreachable from preset's file$
diff --git a/Tests/RunCMake/CMakePresetsBuild/InvalidConfigurePreset-build-badConfigurePreset-stderr.txt b/Tests/RunCMake/CMakePresetsBuild/InvalidConfigurePreset-build-badConfigurePreset-stderr.txt
index 303632e..b62fd16 100644
--- a/Tests/RunCMake/CMakePresetsBuild/InvalidConfigurePreset-build-badConfigurePreset-stderr.txt
+++ b/Tests/RunCMake/CMakePresetsBuild/InvalidConfigurePreset-build-badConfigurePreset-stderr.txt
@@ -1,2 +1,3 @@
 CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsBuild/InvalidConfigurePreset: Invalid "configurePreset" field
+]*/Tests/RunCMake/CMakePresetsBuild/InvalidConfigurePreset:
+Invalid "configurePreset": "badConfigurePreset"$
diff --git a/Tests/RunCMake/CMakePresetsBuild/InvalidConfigurePreset-configure-default-stderr.txt b/Tests/RunCMake/CMakePresetsBuild/InvalidConfigurePreset-configure-default-stderr.txt
index 303632e..b62fd16 100644
--- a/Tests/RunCMake/CMakePresetsBuild/InvalidConfigurePreset-configure-default-stderr.txt
+++ b/Tests/RunCMake/CMakePresetsBuild/InvalidConfigurePreset-configure-default-stderr.txt
@@ -1,2 +1,3 @@
 CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsBuild/InvalidConfigurePreset: Invalid "configurePreset" field
+]*/Tests/RunCMake/CMakePresetsBuild/InvalidConfigurePreset:
+Invalid "configurePreset": "badConfigurePreset"$
diff --git a/Tests/RunCMake/CMakePresetsBuild/NoConfigurePreset-build-noConfigurePreset-stderr.txt b/Tests/RunCMake/CMakePresetsBuild/NoConfigurePreset-build-noConfigurePreset-stderr.txt
index fcb37bc..9ce6ea5 100644
--- a/Tests/RunCMake/CMakePresetsBuild/NoConfigurePreset-build-noConfigurePreset-stderr.txt
+++ b/Tests/RunCMake/CMakePresetsBuild/NoConfigurePreset-build-noConfigurePreset-stderr.txt
@@ -1,2 +1,3 @@
 CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsBuild/NoConfigurePreset: Invalid preset
+]*/Tests/RunCMake/CMakePresetsBuild/NoConfigurePreset:
+Invalid preset: "noConfigurePreset"$
diff --git a/Tests/RunCMake/CMakePresetsBuild/PresetsUnsupported-build-x-stderr.txt b/Tests/RunCMake/CMakePresetsBuild/PresetsUnsupported-build-x-stderr.txt
index d6ae62d..48a5bd3 100644
--- a/Tests/RunCMake/CMakePresetsBuild/PresetsUnsupported-build-x-stderr.txt
+++ b/Tests/RunCMake/CMakePresetsBuild/PresetsUnsupported-build-x-stderr.txt
@@ -1,2 +1,3 @@
 CMake Error: Could not read presets from [^
-]*Tests/RunCMake/CMakePresetsBuild/PresetsUnsupported: File version must be 2 or higher for build and test preset support.
+]*Tests/RunCMake/CMakePresetsBuild/PresetsUnsupported:
+File version must be 2 or higher for build and test preset support$
diff --git a/Tests/RunCMake/CMakePresetsPackage/UnsupportedVersion-configure-x-stderr.txt b/Tests/RunCMake/CMakePresetsPackage/UnsupportedVersion-configure-x-stderr.txt
index 4c461e3..38ec6bf 100644
--- a/Tests/RunCMake/CMakePresetsPackage/UnsupportedVersion-configure-x-stderr.txt
+++ b/Tests/RunCMake/CMakePresetsPackage/UnsupportedVersion-configure-x-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsPackage/UnsupportedVersion: File version must be 6 or higher for package preset support$
+]*/Tests/RunCMake/CMakePresetsPackage/UnsupportedVersion:
+File version must be 6 or higher for package preset support$
diff --git a/Tests/RunCMake/CMakePresetsTest/ConditionFuture-test-x-stderr.txt b/Tests/RunCMake/CMakePresetsTest/ConditionFuture-test-x-stderr.txt
index b814bbb..3f1a506 100644
--- a/Tests/RunCMake/CMakePresetsTest/ConditionFuture-test-x-stderr.txt
+++ b/Tests/RunCMake/CMakePresetsTest/ConditionFuture-test-x-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsTest/ConditionFuture: File version must be 3 or higher for condition support$
+]*/Tests/RunCMake/CMakePresetsTest/ConditionFuture:
+File version must be 3 or higher for condition support$
diff --git a/Tests/RunCMake/CMakePresetsTest/ConfigurePresetUnreachable-test-x-stderr.txt b/Tests/RunCMake/CMakePresetsTest/ConfigurePresetUnreachable-test-x-stderr.txt
index d49148d..0be98ef 100644
--- a/Tests/RunCMake/CMakePresetsTest/ConfigurePresetUnreachable-test-x-stderr.txt
+++ b/Tests/RunCMake/CMakePresetsTest/ConfigurePresetUnreachable-test-x-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsTest/ConfigurePresetUnreachable: Configure preset is unreachable from preset's file$
+]*/Tests/RunCMake/CMakePresetsTest/ConfigurePresetUnreachable:
+Configure preset "x" is unreachable from preset's file
diff --git a/Tests/RunCMake/CMakePresetsTest/InvalidConfigurePreset-configure-default-stderr.txt b/Tests/RunCMake/CMakePresetsTest/InvalidConfigurePreset-configure-default-stderr.txt
index 3d7cdd0..c427e42 100644
--- a/Tests/RunCMake/CMakePresetsTest/InvalidConfigurePreset-configure-default-stderr.txt
+++ b/Tests/RunCMake/CMakePresetsTest/InvalidConfigurePreset-configure-default-stderr.txt
@@ -1,2 +1,3 @@
 CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsTest/InvalidConfigurePreset: Invalid "configurePreset" field
+]*/Tests/RunCMake/CMakePresetsTest/InvalidConfigurePreset:
+Invalid "configurePreset": "badConfigurePreset"$
diff --git a/Tests/RunCMake/CMakePresetsTest/InvalidConfigurePreset-test-badConfigurePreset-stderr.txt b/Tests/RunCMake/CMakePresetsTest/InvalidConfigurePreset-test-badConfigurePreset-stderr.txt
index 3d7cdd0..c427e42 100644
--- a/Tests/RunCMake/CMakePresetsTest/InvalidConfigurePreset-test-badConfigurePreset-stderr.txt
+++ b/Tests/RunCMake/CMakePresetsTest/InvalidConfigurePreset-test-badConfigurePreset-stderr.txt
@@ -1,2 +1,3 @@
 CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsTest/InvalidConfigurePreset: Invalid "configurePreset" field
+]*/Tests/RunCMake/CMakePresetsTest/InvalidConfigurePreset:
+Invalid "configurePreset": "badConfigurePreset"$
diff --git a/Tests/RunCMake/CMakePresetsTest/NoConfigurePreset-test-noConfigurePreset-stderr.txt b/Tests/RunCMake/CMakePresetsTest/NoConfigurePreset-test-noConfigurePreset-stderr.txt
index b167f68..a70497f 100644
--- a/Tests/RunCMake/CMakePresetsTest/NoConfigurePreset-test-noConfigurePreset-stderr.txt
+++ b/Tests/RunCMake/CMakePresetsTest/NoConfigurePreset-test-noConfigurePreset-stderr.txt
@@ -1,2 +1,3 @@
 CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsTest/NoConfigurePreset: Invalid preset
+]*/Tests/RunCMake/CMakePresetsTest/NoConfigurePreset:
+Invalid preset: "noConfigurePreset"$
diff --git a/Tests/RunCMake/CMakePresetsTest/OutputJUnitUnsupported-test-x-stderr.txt b/Tests/RunCMake/CMakePresetsTest/OutputJUnitUnsupported-test-x-stderr.txt
index acd5785..da94c3a 100644
--- a/Tests/RunCMake/CMakePresetsTest/OutputJUnitUnsupported-test-x-stderr.txt
+++ b/Tests/RunCMake/CMakePresetsTest/OutputJUnitUnsupported-test-x-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsTest/OutputJUnitUnsupported: File version must be 6 or higher for CTest JUnit output support$
+]*/Tests/RunCMake/CMakePresetsTest/OutputJUnitUnsupported:
+File version must be 6 or higher for CTest JUnit output support$
diff --git a/Tests/RunCMake/CMakePresetsTest/PresetsUnsupported-test-x-stderr.txt b/Tests/RunCMake/CMakePresetsTest/PresetsUnsupported-test-x-stderr.txt
index eb0ec1a..74c0740 100644
--- a/Tests/RunCMake/CMakePresetsTest/PresetsUnsupported-test-x-stderr.txt
+++ b/Tests/RunCMake/CMakePresetsTest/PresetsUnsupported-test-x-stderr.txt
@@ -1,2 +1,3 @@
 CMake Error: Could not read presets from [^
-]*Tests/RunCMake/CMakePresetsTest/PresetsUnsupported: File version must be 2 or higher for build and test preset support.
+]*Tests/RunCMake/CMakePresetsTest/PresetsUnsupported:
+File version must be 2 or higher for build and test preset support
diff --git a/Tests/RunCMake/CMakePresetsTest/TestOutputTruncationUnsupported-test-x-stderr.txt b/Tests/RunCMake/CMakePresetsTest/TestOutputTruncationUnsupported-test-x-stderr.txt
index 90ea7c3..4deb755 100644
--- a/Tests/RunCMake/CMakePresetsTest/TestOutputTruncationUnsupported-test-x-stderr.txt
+++ b/Tests/RunCMake/CMakePresetsTest/TestOutputTruncationUnsupported-test-x-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsTest/TestOutputTruncationUnsupported: File version must be 5 or higher for testOutputTruncation preset support\.$
+]*/Tests/RunCMake/CMakePresetsTest/TestOutputTruncationUnsupported:
+File version must be 5 or higher for testOutputTruncation preset support$
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch-stderr.txt b/Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch-stderr.txt
index 22ca94d..33fe914 100644
--- a/Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch-stderr.txt
+++ b/Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch: Invalid workflow steps$
+]*/Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch:
+Invalid workflow step "default"$
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure-stderr.txt b/Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure-stderr.txt
index cbfee5a..776257b 100644
--- a/Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure-stderr.txt
+++ b/Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure: Invalid workflow steps$
+]*/Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure:
+First workflow step "default" must be a configure step$
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps-stderr.txt b/Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps-stderr.txt
index 049ed6b..f34a605 100644
--- a/Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps-stderr.txt
+++ b/Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps: Invalid workflow steps$
+]*/Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps:
+No workflow steps specified for "default"$
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep-stderr.txt b/Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep-stderr.txt
index c522b84..a9029e1 100644
--- a/Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep-stderr.txt
+++ b/Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep: Invalid workflow steps$
+]*/Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep:
+Invalid workflow step "default"$
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure-stderr.txt b/Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure-stderr.txt
index b0ad7d5..35eac16 100644
--- a/Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure-stderr.txt
+++ b/Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure: Invalid workflow steps$
+]*/Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure:
+Configure workflow step "default" must be the first step
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/UnreachableStep-stderr.txt b/Tests/RunCMake/CMakePresetsWorkflow/UnreachableStep-stderr.txt
index 425e719..f0a36f8 100644
--- a/Tests/RunCMake/CMakePresetsWorkflow/UnreachableStep-stderr.txt
+++ b/Tests/RunCMake/CMakePresetsWorkflow/UnreachableStep-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsWorkflow/UnreachableStep: Workflow step is unreachable from preset's file$
+]*/Tests/RunCMake/CMakePresetsWorkflow/UnreachableStep:
+Workflow step "default" is unreachable from preset's file$
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/UnsupportedVersion-stderr.txt b/Tests/RunCMake/CMakePresetsWorkflow/UnsupportedVersion-stderr.txt
index 5cf01aa..93e31eb 100644
--- a/Tests/RunCMake/CMakePresetsWorkflow/UnsupportedVersion-stderr.txt
+++ b/Tests/RunCMake/CMakePresetsWorkflow/UnsupportedVersion-stderr.txt
@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsWorkflow/UnsupportedVersion: File version must be 6 or higher for workflow preset support$
+]*/Tests/RunCMake/CMakePresetsWorkflow/UnsupportedVersion:
+File version must be 6 or higher for workflow preset support$
diff --git a/Tests/RunCMake/CPack/CMakeLists.txt b/Tests/RunCMake/CPack/CMakeLists.txt
index c81b34e..18a673e 100644
--- a/Tests/RunCMake/CPack/CMakeLists.txt
+++ b/Tests/RunCMake/CPack/CMakeLists.txt
@@ -1,8 +1,4 @@
-cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
-
-if(POLICY CMP0129)
-  cmake_policy(SET CMP0129 NEW)
-endif()
+cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
 
 set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "")
 
diff --git a/Tests/RunCMake/CPack/RunCMakeTest.cmake b/Tests/RunCMake/CPack/RunCMakeTest.cmake
index ef4cf5e..ca02b76 100644
--- a/Tests/RunCMake/CPack/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CPack/RunCMakeTest.cmake
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
+cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
 
 include(RunCMake)
 include("${RunCMake_SOURCE_DIR}/CPackTestHelpers.cmake")
diff --git a/Tests/RunCMake/CPackConfig/CMakeLists.txt b/Tests/RunCMake/CPackConfig/CMakeLists.txt
index 1e071ec..2b3e1f9 100644
--- a/Tests/RunCMake/CPackConfig/CMakeLists.txt
+++ b/Tests/RunCMake/CPackConfig/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 
 project(${RunCMake_TEST})
 include(${RunCMake_TEST}.cmake NO_POLICY_SCOPE)
diff --git a/Tests/RunCMake/CPackInstallProperties/CMakeLists.txt b/Tests/RunCMake/CPackInstallProperties/CMakeLists.txt
index 89ff7c4..404e162 100644
--- a/Tests/RunCMake/CPackInstallProperties/CMakeLists.txt
+++ b/Tests/RunCMake/CPackInstallProperties/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.5)
 
 project(${RunCMake_TEST} CXX)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/CPackSymlinks/testcpacksym.tar b/Tests/RunCMake/CPackSymlinks/testcpacksym.tar
index c24af48..7cfcb36 100644
--- a/Tests/RunCMake/CPackSymlinks/testcpacksym.tar
+++ b/Tests/RunCMake/CPackSymlinks/testcpacksym.tar
Binary files differ
diff --git a/Tests/RunCMake/CSharpCustomCommand/CMakeLists.txt b/Tests/RunCMake/CSharpCustomCommand/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/CSharpCustomCommand/CMakeLists.txt
+++ b/Tests/RunCMake/CSharpCustomCommand/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/CSharpReferenceImport/CMakeLists.txt b/Tests/RunCMake/CSharpReferenceImport/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/CSharpReferenceImport/CMakeLists.txt
+++ b/Tests/RunCMake/CSharpReferenceImport/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/CTest/CMP0145-Dart-NEW-result.txt b/Tests/RunCMake/CTest/CMP0145-Dart-NEW-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CTest/CMP0145-Dart-NEW-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CTest/CMP0145-Dart-NEW-stderr.txt b/Tests/RunCMake/CTest/CMP0145-Dart-NEW-stderr.txt
new file mode 100644
index 0000000..06fce77
--- /dev/null
+++ b/Tests/RunCMake/CTest/CMP0145-Dart-NEW-stderr.txt
@@ -0,0 +1,6 @@
+^CMake Error at CMP0145-Dart-NEW\.cmake:[0-9]+ \(include\):
+  include could not find requested file:
+
+    Dart
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/CTest/CMP0145-Dart-NEW.cmake b/Tests/RunCMake/CTest/CMP0145-Dart-NEW.cmake
new file mode 100644
index 0000000..5b14ecc
--- /dev/null
+++ b/Tests/RunCMake/CTest/CMP0145-Dart-NEW.cmake
@@ -0,0 +1,2 @@
+cmake_policy(SET CMP0145 NEW)
+include(Dart)
diff --git a/Tests/RunCMake/CTest/CMP0145-Dart-OLD.cmake b/Tests/RunCMake/CTest/CMP0145-Dart-OLD.cmake
new file mode 100644
index 0000000..2f66c3f
--- /dev/null
+++ b/Tests/RunCMake/CTest/CMP0145-Dart-OLD.cmake
@@ -0,0 +1,7 @@
+cmake_policy(SET CMP0145 OLD)
+set(_FindDart_testing 1)
+include(Dart)
+
+if(NOT _FindDart_included)
+  message(FATAL_ERROR "FindDart.cmake not included")
+endif()
diff --git a/Tests/RunCMake/CTest/CMP0145-Dart-WARN-stderr.txt b/Tests/RunCMake/CTest/CMP0145-Dart-WARN-stderr.txt
new file mode 100644
index 0000000..5a751fc
--- /dev/null
+++ b/Tests/RunCMake/CTest/CMP0145-Dart-WARN-stderr.txt
@@ -0,0 +1,18 @@
+^CMake Warning \(dev\) at CMP0145-Dart-WARN\.cmake:[0-9]+ \(include\):
+  Policy CMP0145 is not set: The Dart and FindDart modules are removed\.  Run
+  "cmake --help-policy CMP0145" for policy details\.  Use the cmake_policy
+  command to set the policy and suppress this warning\.
+
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.
++
+CMake Warning \(dev\) at [^
+]*/Modules/Dart\.cmake:[0-9]+ \(message\):
+  Policy CMP0145 is not set: The Dart and FindDart modules are removed\.  Run
+  "cmake --help-policy CMP0145" for policy details\.  Use the cmake_policy
+  command to set the policy and suppress this warning\.
+Call Stack \(most recent call first\):
+  CMP0145-Dart-WARN\.cmake:[0-9]+ \(include\)
+  CMakeLists\.txt:[0-9]+ \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.$
diff --git a/Tests/RunCMake/CTest/CMP0145-Dart-WARN.cmake b/Tests/RunCMake/CTest/CMP0145-Dart-WARN.cmake
new file mode 100644
index 0000000..1398dbe
--- /dev/null
+++ b/Tests/RunCMake/CTest/CMP0145-Dart-WARN.cmake
@@ -0,0 +1,7 @@
+# Do not set CMP0145.
+set(_FindDart_testing 1)
+include(Dart)
+
+if(NOT _FindDart_included)
+  message(FATAL_ERROR "FindDart.cmake not included")
+endif()
diff --git a/Tests/RunCMake/CTest/CMP0145-FindDart-NEW-result.txt b/Tests/RunCMake/CTest/CMP0145-FindDart-NEW-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CTest/CMP0145-FindDart-NEW-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CTest/CMP0145-FindDart-NEW-stderr.txt b/Tests/RunCMake/CTest/CMP0145-FindDart-NEW-stderr.txt
new file mode 100644
index 0000000..b045636
--- /dev/null
+++ b/Tests/RunCMake/CTest/CMP0145-FindDart-NEW-stderr.txt
@@ -0,0 +1,6 @@
+^CMake Error at CMP0145-FindDart-NEW\.cmake:[0-9]+ \(include\):
+  include could not find requested file:
+
+    FindDart
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/CTest/CMP0145-FindDart-NEW.cmake b/Tests/RunCMake/CTest/CMP0145-FindDart-NEW.cmake
new file mode 100644
index 0000000..c1227d6
--- /dev/null
+++ b/Tests/RunCMake/CTest/CMP0145-FindDart-NEW.cmake
@@ -0,0 +1,2 @@
+cmake_policy(SET CMP0145 NEW)
+include(FindDart)
diff --git a/Tests/RunCMake/CTest/CMP0145-FindDart-OLD.cmake b/Tests/RunCMake/CTest/CMP0145-FindDart-OLD.cmake
new file mode 100644
index 0000000..b9f3c76
--- /dev/null
+++ b/Tests/RunCMake/CTest/CMP0145-FindDart-OLD.cmake
@@ -0,0 +1,7 @@
+cmake_policy(SET CMP0145 OLD)
+set(_FindDart_testing 1)
+include(FindDart)
+
+if(NOT _FindDart_included)
+  message(FATAL_ERROR "FindDart.cmake not included")
+endif()
diff --git a/Tests/RunCMake/CTest/CMP0145-FindDart-WARN-stderr.txt b/Tests/RunCMake/CTest/CMP0145-FindDart-WARN-stderr.txt
new file mode 100644
index 0000000..d076235
--- /dev/null
+++ b/Tests/RunCMake/CTest/CMP0145-FindDart-WARN-stderr.txt
@@ -0,0 +1,8 @@
+^CMake Warning \(dev\) at CMP0145-FindDart-WARN\.cmake:[0-9]+ \(include\):
+  Policy CMP0145 is not set: The Dart and FindDart modules are removed\.  Run
+  "cmake --help-policy CMP0145" for policy details\.  Use the cmake_policy
+  command to set the policy and suppress this warning\.
+
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.$
diff --git a/Tests/RunCMake/CTest/CMP0145-FindDart-WARN.cmake b/Tests/RunCMake/CTest/CMP0145-FindDart-WARN.cmake
new file mode 100644
index 0000000..59febdf
--- /dev/null
+++ b/Tests/RunCMake/CTest/CMP0145-FindDart-WARN.cmake
@@ -0,0 +1,7 @@
+# Do not set CMP0145.
+set(_FindDart_testing 1)
+include(FindDart)
+
+if(NOT _FindDart_included)
+  message(FATAL_ERROR "FindDart.cmake not included")
+endif()
diff --git a/Tests/RunCMake/CTest/CMakeLists.txt b/Tests/RunCMake/CTest/CMakeLists.txt
index f1a83e8..1319aec 100644
--- a/Tests/RunCMake/CTest/CMakeLists.txt
+++ b/Tests/RunCMake/CTest/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 if(NOT NoProject)
   project(${RunCMake_TEST} NONE)
 endif()
diff --git a/Tests/RunCMake/CTest/RunCMakeTest.cmake b/Tests/RunCMake/CTest/RunCMakeTest.cmake
index b81f319..4c2c107 100644
--- a/Tests/RunCMake/CTest/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CTest/RunCMakeTest.cmake
@@ -39,3 +39,10 @@
 if(NOT RunCMake_GENERATOR_IS_MULTI_CONFIG)
   run_SingleConfig()
 endif()
+
+run_cmake(CMP0145-Dart-OLD)
+run_cmake(CMP0145-Dart-WARN)
+run_cmake(CMP0145-Dart-NEW)
+run_cmake(CMP0145-FindDart-OLD)
+run_cmake(CMP0145-FindDart-WARN)
+run_cmake(CMP0145-FindDart-NEW)
diff --git a/Tests/RunCMake/CTestCommandLine/CMakeLists.txt b/Tests/RunCMake/CTestCommandLine/CMakeLists.txt
index 2897109..93ee9df 100644
--- a/Tests/RunCMake/CTestCommandLine/CMakeLists.txt
+++ b/Tests/RunCMake/CTestCommandLine/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/CTestCommandLine/test.cmake.in b/Tests/RunCMake/CTestCommandLine/test.cmake.in
index b82968a..11bede7 100644
--- a/Tests/RunCMake/CTestCommandLine/test.cmake.in
+++ b/Tests/RunCMake/CTestCommandLine/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 set(CTEST_SITE                          "test-site")
 set(CTEST_BUILD_NAME                    "test-build-name")
diff --git a/Tests/RunCMake/CTestResourceAllocation/ctresalloc.cxx b/Tests/RunCMake/CTestResourceAllocation/ctresalloc.cxx
index 5c6c8d8..2824fbf 100644
--- a/Tests/RunCMake/CTestResourceAllocation/ctresalloc.cxx
+++ b/Tests/RunCMake/CTestResourceAllocation/ctresalloc.cxx
@@ -19,6 +19,7 @@
 #include "cmCTestTestHandler.h"
 #include "cmFileLock.h"
 #include "cmFileLockResult.h"
+#include "cmList.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 
@@ -282,12 +283,11 @@
   if (argc == 5) {
     testNames = argv[4];
   }
-  auto testNameList = cmExpandedList(testNames, false);
+  cmList testNameList{ testNames };
   std::set<std::string> testNameSet(testNameList.begin(), testNameList.end());
 
   cmCTestResourceSpec spec;
-  if (spec.ReadFromJSONFile(resFile) !=
-      cmCTestResourceSpec::ReadFileResult::READ_OK) {
+  if (spec.ReadFromJSONFile(resFile) != true) {
     std::cout << "Could not read resource spec " << resFile << std::endl;
     return 1;
   }
diff --git a/Tests/RunCMake/CTestTimeout/CMakeLists.txt.in b/Tests/RunCMake/CTestTimeout/CMakeLists.txt.in
index 20faa94..ee3323c 100644
--- a/Tests/RunCMake/CTestTimeout/CMakeLists.txt.in
+++ b/Tests/RunCMake/CTestTimeout/CMakeLists.txt.in
@@ -4,7 +4,7 @@
 
 add_executable(TestTimeout TestTimeout.c)
 
-if(NOT TIMEOUT)
+if(NOT DEFINED TIMEOUT)
   set(TIMEOUT 4)
 endif()
 target_compile_definitions(TestTimeout PRIVATE TIMEOUT=${TIMEOUT})
diff --git a/Tests/RunCMake/CTestTimeout/RunCMakeTest.cmake b/Tests/RunCMake/CTestTimeout/RunCMakeTest.cmake
index 7e96b6d..a4080e3 100644
--- a/Tests/RunCMake/CTestTimeout/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CTestTimeout/RunCMakeTest.cmake
@@ -1,6 +1,6 @@
 include(RunCTest)
 
-if(NOT TIMEOUT)
+if(NOT DEFINED TIMEOUT)
   # Give the process time to load and start running.
   set(TIMEOUT 4)
 endif()
@@ -8,7 +8,7 @@
 function(run_ctest_timeout CASE_NAME)
   configure_file(${RunCMake_SOURCE_DIR}/TestTimeout.c
                  ${RunCMake_BINARY_DIR}/${CASE_NAME}/TestTimeout.c COPYONLY)
-  run_ctest(${CASE_NAME})
+  run_ctest(${CASE_NAME} ${ARGN})
 endfunction()
 
 run_ctest_timeout(Basic)
@@ -20,3 +20,14 @@
   run_ctest_timeout(Fork)
   unset(CASE_CMAKELISTS_SUFFIX_CODE)
 endif()
+
+block()
+  # An explicit zero TIMEOUT test property means "no timeout".
+  set(TIMEOUT 0)
+  # The test sleeps for 4 seconds longer than the TIMEOUT value.
+  # Set a default timeout to less than that so that the test will
+  # timeout if the zero TIMEOUT does not suppress it.
+  run_ctest_timeout(ZeroOverridesFlag --timeout 2)
+  set(CASE_TEST_PREFIX_CODE "set(CTEST_TEST_TIMEOUT 2)")
+  run_ctest_timeout(ZeroOverridesVar)
+endblock()
diff --git a/Tests/RunCMake/CTestTimeout/ZeroOverridesFlag-stdout.txt b/Tests/RunCMake/CTestTimeout/ZeroOverridesFlag-stdout.txt
new file mode 100644
index 0000000..746bc21
--- /dev/null
+++ b/Tests/RunCMake/CTestTimeout/ZeroOverridesFlag-stdout.txt
@@ -0,0 +1,6 @@
+Test project [^
+]*/Tests/RunCMake/CTestTimeout/ZeroOverridesFlag-build
+    Start 1: TestTimeout
+1/1 Test #1: TestTimeout ......................   Passed +[1-9][0-9.]* sec
++
+100% tests passed, 0 tests failed out of 1
diff --git a/Tests/RunCMake/CTestTimeout/ZeroOverridesVar-stdout.txt b/Tests/RunCMake/CTestTimeout/ZeroOverridesVar-stdout.txt
new file mode 100644
index 0000000..7192055
--- /dev/null
+++ b/Tests/RunCMake/CTestTimeout/ZeroOverridesVar-stdout.txt
@@ -0,0 +1,6 @@
+Test project [^
+]*/Tests/RunCMake/CTestTimeout/ZeroOverridesVar-build
+    Start 1: TestTimeout
+1/1 Test #1: TestTimeout ......................   Passed +[1-9][0-9.]* sec
++
+100% tests passed, 0 tests failed out of 1
diff --git a/Tests/RunCMake/CTestTimeoutAfterMatch/CMakeLists.txt.in b/Tests/RunCMake/CTestTimeoutAfterMatch/CMakeLists.txt.in
index e9592f6..cfcf56d 100644
--- a/Tests/RunCMake/CTestTimeoutAfterMatch/CMakeLists.txt.in
+++ b/Tests/RunCMake/CTestTimeoutAfterMatch/CMakeLists.txt.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.4)
+cmake_minimum_required(VERSION 3.5)
 project(TimeoutAfterMatch NONE)
 include(CTest)
 add_test(NAME SleepFor1Second COMMAND "${CMAKE_COMMAND}" -P ${CMAKE_SOURCE_DIR}/SleepFor1Second.cmake)
diff --git a/Tests/RunCMake/CTestTimeoutAfterMatch/test.cmake.in b/Tests/RunCMake/CTestTimeoutAfterMatch/test.cmake.in
index d049c9f..172d2c6 100644
--- a/Tests/RunCMake/CTestTimeoutAfterMatch/test.cmake.in
+++ b/Tests/RunCMake/CTestTimeoutAfterMatch/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.4)
+cmake_minimum_required(VERSION 3.5)
 
 set(CTEST_SITE                          "test-site")
 set(CTEST_BUILD_NAME                    "test-build-name")
diff --git a/Tests/RunCMake/CXXModules/RunCMakeTest.cmake b/Tests/RunCMake/CXXModules/RunCMakeTest.cmake
index c1129ca..b088724 100644
--- a/Tests/RunCMake/CXXModules/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CXXModules/RunCMakeTest.cmake
@@ -155,6 +155,9 @@
 # Tests which require collation work.
 if ("collation" IN_LIST CMake_TEST_MODULE_COMPILATION)
   run_cxx_module_test(public-req-private)
+  set(RunCMake_CXXModules_NO_TEST 1)
+  run_cxx_module_test(req-private-other-target)
+  unset(RunCMake_CXXModules_NO_TEST)
 endif ()
 
 # Tests which use named modules in shared libraries.
diff --git a/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-build-stderr.txt b/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-build-stderr.txt
index 5e4392a..c8dbdcf 100644
--- a/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-build-stderr.txt
+++ b/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-build-stderr.txt
@@ -1,4 +1,4 @@
-CMake Warning \(dev\) at CMakeLists.txt:7 \(target_sources\):
+CMake Warning \(dev\) at CMakeLists.txt:[0-9]+ \(target_sources\):
   CMake's C\+\+ module support is experimental.  It is meant only for
   experimentation and feedback to CMake developers.
 This warning is for project developers.  Use -Wno-dev to suppress it.
diff --git a/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-build/CMakeLists.txt b/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-build/CMakeLists.txt
index a450b7e..71e7b62 100644
--- a/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-build/CMakeLists.txt
+++ b/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-build/CMakeLists.txt
@@ -18,10 +18,14 @@
       BASE_DIRS
         "${CMAKE_CURRENT_SOURCE_DIR}"
       FILES
-        importable.cxx)
+        importable.cxx
+        subdir/importable.cxx
+  )
 target_compile_features(export_bmi_and_interfaces PUBLIC cxx_std_20)
 
-install(TARGETS export_bmi_and_interfaces
+add_library(no_modules STATIC no_modules.cxx)
+
+install(TARGETS export_bmi_and_interfaces no_modules
   EXPORT CXXModules
   FILE_SET modules DESTINATION "lib/cxx/miu"
   CXX_MODULES_BMI DESTINATION "lib/cxx/bmi")
diff --git a/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-build/no_modules.cxx b/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-build/no_modules.cxx
new file mode 100644
index 0000000..eea854f
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-build/no_modules.cxx
@@ -0,0 +1,3 @@
+void no_modules()
+{
+}
diff --git a/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-build/subdir/importable.cxx b/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-build/subdir/importable.cxx
new file mode 100644
index 0000000..07d6af6
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-build/subdir/importable.cxx
@@ -0,0 +1,6 @@
+export module subdir_importable;
+
+export int from_subdir()
+{
+  return 0;
+}
diff --git a/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-build/test/CMakeLists.txt b/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-build/test/CMakeLists.txt
index 2e37da2..3cb185c 100644
--- a/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-build/test/CMakeLists.txt
+++ b/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-build/test/CMakeLists.txt
@@ -14,19 +14,31 @@
   PROPERTY INTERFACE_CXX_MODULE_SETS)
 if (NOT file_sets STREQUAL "modules")
   message(FATAL_ERROR
-    "Incorrect exported file sets in `CXXModules::export_bmi_and_interfaces`: `${file_sets}`")
+    "Incorrect exported file sets in CXXModules::export_bmi_and_interfaces:\n  ${file_sets}")
 endif ()
 
 get_property(file_set_files TARGET CXXModules::export_bmi_and_interfaces
   PROPERTY CXX_MODULE_SET_modules)
-if (NOT file_set_files STREQUAL "${expected_source_dir}/importable.cxx")
+set(expected_file_set_files
+  "${expected_source_dir}/importable.cxx"
+  "${expected_source_dir}/subdir/importable.cxx"
+  )
+if (NOT file_set_files STREQUAL "${expected_file_set_files}")
   message(FATAL_ERROR
-    "Incorrect exported file set paths in CXXModules::export_bmi_and_interfaces`: `${file_set_files}`")
+    "Incorrect exported file set paths in CXXModules::export_bmi_and_interfaces:\n  ${file_set_files}")
 endif ()
 
 get_property(imported_modules TARGET CXXModules::export_bmi_and_interfaces
   PROPERTY IMPORTED_CXX_MODULES_DEBUG)
-if (NOT imported_modules MATCHES "importable=${expected_source_dir}/importable.cxx,${expected_binary_dir}/CMakeFiles/export_bmi_and_interfaces.dir(/Debug)?/importable.(gcm|pcm|ifc)")
+set(expected_imported_modules
+  "importable=${expected_source_dir}/importable.cxx,${expected_binary_dir}/CMakeFiles/export_bmi_and_interfaces.dir(/Debug)?/importable.(gcm|pcm|ifc)"
+  "subdir_importable=${expected_source_dir}/subdir/importable.cxx,${expected_binary_dir}/CMakeFiles/export_bmi_and_interfaces.dir(/Debug)?/subdir_importable.(gcm|pcm|ifc)"
+  )
+if (NOT imported_modules MATCHES "^${expected_imported_modules}$")
   message(FATAL_ERROR
-    "Incorrect exported modules in CXXModules::export_bmi_and_interfaces`: `${imported_modules}`")
+    "Incorrect exported modules in CXXModules::export_bmi_and_interfaces:\n"
+    "  ${imported_modules}\n"
+    "does not match:\n"
+    "  ${expected_imported_modules}"
+  )
 endif ()
diff --git a/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-install-stderr.txt b/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-install-stderr.txt
index 5e4392a..c8dbdcf 100644
--- a/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-install-stderr.txt
+++ b/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-install-stderr.txt
@@ -1,4 +1,4 @@
-CMake Warning \(dev\) at CMakeLists.txt:7 \(target_sources\):
+CMake Warning \(dev\) at CMakeLists.txt:[0-9]+ \(target_sources\):
   CMake's C\+\+ module support is experimental.  It is meant only for
   experimentation and feedback to CMake developers.
 This warning is for project developers.  Use -Wno-dev to suppress it.
diff --git a/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-install/CMakeLists.txt b/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-install/CMakeLists.txt
index a5574fe..e675507 100644
--- a/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-install/CMakeLists.txt
+++ b/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-install/CMakeLists.txt
@@ -18,10 +18,14 @@
       BASE_DIRS
         "${CMAKE_CURRENT_SOURCE_DIR}"
       FILES
-        importable.cxx)
+        importable.cxx
+        subdir/importable.cxx
+  )
 target_compile_features(export_bmi_and_interfaces PUBLIC cxx_std_20)
 
-install(TARGETS export_bmi_and_interfaces
+add_library(no_modules STATIC no_modules.cxx)
+
+install(TARGETS export_bmi_and_interfaces no_modules
   EXPORT CXXModules
   FILE_SET modules DESTINATION "lib/cxx/miu"
   CXX_MODULES_BMI DESTINATION "lib/cxx/bmi")
diff --git a/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-install/no_modules.cxx b/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-install/no_modules.cxx
new file mode 100644
index 0000000..eea854f
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-install/no_modules.cxx
@@ -0,0 +1,3 @@
+void no_modules()
+{
+}
diff --git a/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-install/subdir/importable.cxx b/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-install/subdir/importable.cxx
new file mode 100644
index 0000000..07d6af6
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-install/subdir/importable.cxx
@@ -0,0 +1,6 @@
+export module subdir_importable;
+
+export int from_subdir()
+{
+  return 0;
+}
diff --git a/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-install/test/CMakeLists.txt b/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-install/test/CMakeLists.txt
index 1adccb3..7b36f8c 100644
--- a/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-install/test/CMakeLists.txt
+++ b/Tests/RunCMake/CXXModules/examples/export-bmi-and-interface-install/test/CMakeLists.txt
@@ -14,19 +14,31 @@
   PROPERTY INTERFACE_CXX_MODULE_SETS)
 if (NOT file_sets STREQUAL "modules")
   message(FATAL_ERROR
-    "Incorrect exported file sets in `CXXModules::export_bmi_and_interfaces`: `${file_sets}`")
+    "Incorrect exported file sets in CXXModules::export_bmi_and_interfaces:\n  ${file_sets}")
 endif ()
 
 get_property(file_set_files TARGET CXXModules::export_bmi_and_interfaces
   PROPERTY CXX_MODULE_SET_modules)
-if (NOT file_set_files STREQUAL "${expected_source_dir}/importable.cxx")
+set(expected_file_set_files
+  "${expected_source_dir}/importable.cxx"
+  "${expected_source_dir}/subdir/importable.cxx"
+  )
+if (NOT file_set_files STREQUAL "${expected_file_set_files}")
   message(FATAL_ERROR
-    "Incorrect exported file set paths in CXXModules::export_bmi_and_interfaces`: `${file_set_files}`")
+    "Incorrect exported file set paths in CXXModules::export_bmi_and_interfaces:\n  ${file_set_files}")
 endif ()
 
 get_property(imported_modules TARGET CXXModules::export_bmi_and_interfaces
   PROPERTY IMPORTED_CXX_MODULES_DEBUG)
-if (NOT imported_modules MATCHES "importable=${expected_source_dir}/importable.cxx,${expected_binary_dir}/importable.(gcm|pcm|ifc)")
+set(expected_imported_modules
+  "importable=${expected_source_dir}/importable.cxx,${expected_binary_dir}/importable.(gcm|pcm|ifc)"
+  "subdir_importable=${expected_source_dir}/subdir/importable.cxx,${expected_binary_dir}/subdir_importable.(gcm|pcm|ifc)"
+  )
+if (NOT imported_modules MATCHES "^${expected_imported_modules}$")
   message(FATAL_ERROR
-    "Incorrect exported modules in CXXModules::export_bmi_and_interfaces`: `${imported_modules}`")
+    "Incorrect exported modules in CXXModules::export_bmi_and_interfaces:\n"
+    "  ${imported_modules}\n"
+    "does not match:\n"
+    "  ${expected_imported_modules}"
+  )
 endif ()
diff --git a/Tests/RunCMake/CXXModules/examples/export-interface-build-stderr.txt b/Tests/RunCMake/CXXModules/examples/export-interface-build-stderr.txt
index 5e4392a..e318a34 100644
--- a/Tests/RunCMake/CXXModules/examples/export-interface-build-stderr.txt
+++ b/Tests/RunCMake/CXXModules/examples/export-interface-build-stderr.txt
@@ -1,4 +1,4 @@
-CMake Warning \(dev\) at CMakeLists.txt:7 \(target_sources\):
+CMake Warning \(dev\) at CMakeLists.txt:[0-9] \(target_sources\):
   CMake's C\+\+ module support is experimental.  It is meant only for
   experimentation and feedback to CMake developers.
 This warning is for project developers.  Use -Wno-dev to suppress it.
diff --git a/Tests/RunCMake/CXXModules/examples/export-interface-build/CMakeLists.txt b/Tests/RunCMake/CXXModules/examples/export-interface-build/CMakeLists.txt
index 8584dce..136e885 100644
--- a/Tests/RunCMake/CXXModules/examples/export-interface-build/CMakeLists.txt
+++ b/Tests/RunCMake/CXXModules/examples/export-interface-build/CMakeLists.txt
@@ -18,10 +18,14 @@
       BASE_DIRS
         "${CMAKE_CURRENT_SOURCE_DIR}"
       FILES
-        importable.cxx)
+        importable.cxx
+        subdir/importable.cxx
+  )
 target_compile_features(export_interfaces PUBLIC cxx_std_20)
 
-install(TARGETS export_interfaces
+add_library(no_modules STATIC no_modules.cxx)
+
+install(TARGETS export_interfaces no_modules
   EXPORT CXXModules
   FILE_SET modules DESTINATION "lib/cxx/miu")
 export(EXPORT CXXModules
diff --git a/Tests/RunCMake/CXXModules/examples/export-interface-build/no_modules.cxx b/Tests/RunCMake/CXXModules/examples/export-interface-build/no_modules.cxx
new file mode 100644
index 0000000..eea854f
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/export-interface-build/no_modules.cxx
@@ -0,0 +1,3 @@
+void no_modules()
+{
+}
diff --git a/Tests/RunCMake/CXXModules/examples/export-interface-build/subdir/importable.cxx b/Tests/RunCMake/CXXModules/examples/export-interface-build/subdir/importable.cxx
new file mode 100644
index 0000000..07d6af6
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/export-interface-build/subdir/importable.cxx
@@ -0,0 +1,6 @@
+export module subdir_importable;
+
+export int from_subdir()
+{
+  return 0;
+}
diff --git a/Tests/RunCMake/CXXModules/examples/export-interface-build/test/CMakeLists.txt b/Tests/RunCMake/CXXModules/examples/export-interface-build/test/CMakeLists.txt
index 9949969..1874c97 100644
--- a/Tests/RunCMake/CXXModules/examples/export-interface-build/test/CMakeLists.txt
+++ b/Tests/RunCMake/CXXModules/examples/export-interface-build/test/CMakeLists.txt
@@ -14,19 +14,31 @@
   PROPERTY INTERFACE_CXX_MODULE_SETS)
 if (NOT file_sets STREQUAL "modules")
   message(FATAL_ERROR
-    "Incorrect exported file sets in `CXXModules::export_interfaces`: `${file_sets}`")
+    "Incorrect exported file sets in CXXModules::export_interfaces:\n  ${file_sets}")
 endif ()
 
 get_property(file_set_files TARGET CXXModules::export_interfaces
   PROPERTY CXX_MODULE_SET_modules)
-if (NOT file_set_files STREQUAL "${expected_source_dir}/importable.cxx")
+set(expected_file_set_files
+  "${expected_source_dir}/importable.cxx"
+  "${expected_source_dir}/subdir/importable.cxx"
+  )
+if (NOT file_set_files STREQUAL "${expected_file_set_files}")
   message(FATAL_ERROR
-    "Incorrect exported file set paths in CXXModules::export_interfaces`: `${file_set_files}`")
+    "Incorrect exported file set paths in CXXModules::export_interfaces:\n  ${file_set_files}")
 endif ()
 
 get_property(imported_modules TARGET CXXModules::export_interfaces
   PROPERTY IMPORTED_CXX_MODULES_DEBUG)
-if (NOT imported_modules MATCHES "importable=${expected_source_dir}/importable.cxx,${expected_binary_dir}/CMakeFiles/export_interfaces.dir(/Debug)?/importable.(gcm|pcm|ifc)")
+set(expected_imported_modules
+  "importable=${expected_source_dir}/importable.cxx,${expected_binary_dir}/CMakeFiles/export_interfaces.dir(/Debug)?/importable.(gcm|pcm|ifc)"
+  "subdir_importable=${expected_source_dir}/subdir/importable.cxx,${expected_binary_dir}/CMakeFiles/export_interfaces.dir(/Debug)?/subdir_importable.(gcm|pcm|ifc)"
+  )
+if (NOT imported_modules MATCHES "^${expected_imported_modules}$")
   message(FATAL_ERROR
-    "Incorrect exported modules in CXXModules::export_interfaces`: `${imported_modules}`\n`importable=${expected_source_dir}/importable.cxx,${expected_binary_dir}/CMakeFiles/export_interfaces.dir(/Debug)?/importable.(gcm|pcm|ifc)`")
+    "Incorrect exported modules in CXXModules::export_interfaces:\n"
+    "  ${imported_modules}\n"
+    "does not match:\n"
+    "  ${expected_imported_modules}"
+  )
 endif ()
diff --git a/Tests/RunCMake/CXXModules/examples/export-interface-install-stderr.txt b/Tests/RunCMake/CXXModules/examples/export-interface-install-stderr.txt
index 5e4392a..c8dbdcf 100644
--- a/Tests/RunCMake/CXXModules/examples/export-interface-install-stderr.txt
+++ b/Tests/RunCMake/CXXModules/examples/export-interface-install-stderr.txt
@@ -1,4 +1,4 @@
-CMake Warning \(dev\) at CMakeLists.txt:7 \(target_sources\):
+CMake Warning \(dev\) at CMakeLists.txt:[0-9]+ \(target_sources\):
   CMake's C\+\+ module support is experimental.  It is meant only for
   experimentation and feedback to CMake developers.
 This warning is for project developers.  Use -Wno-dev to suppress it.
diff --git a/Tests/RunCMake/CXXModules/examples/export-interface-install/CMakeLists.txt b/Tests/RunCMake/CXXModules/examples/export-interface-install/CMakeLists.txt
index b5c6224..df87980 100644
--- a/Tests/RunCMake/CXXModules/examples/export-interface-install/CMakeLists.txt
+++ b/Tests/RunCMake/CXXModules/examples/export-interface-install/CMakeLists.txt
@@ -18,10 +18,14 @@
       BASE_DIRS
         "${CMAKE_CURRENT_SOURCE_DIR}"
       FILES
-        importable.cxx)
+        importable.cxx
+        subdir/importable.cxx
+  )
 target_compile_features(export_interfaces PUBLIC cxx_std_20)
 
-install(TARGETS export_interfaces
+add_library(no_modules STATIC no_modules.cxx)
+
+install(TARGETS export_interfaces no_modules
   EXPORT CXXModules
   FILE_SET modules DESTINATION "lib/cxx/miu")
 install(EXPORT CXXModules
diff --git a/Tests/RunCMake/CXXModules/examples/export-interface-install/no_modules.cxx b/Tests/RunCMake/CXXModules/examples/export-interface-install/no_modules.cxx
new file mode 100644
index 0000000..eea854f
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/export-interface-install/no_modules.cxx
@@ -0,0 +1,3 @@
+void no_modules()
+{
+}
diff --git a/Tests/RunCMake/CXXModules/examples/export-interface-install/subdir/importable.cxx b/Tests/RunCMake/CXXModules/examples/export-interface-install/subdir/importable.cxx
new file mode 100644
index 0000000..07d6af6
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/export-interface-install/subdir/importable.cxx
@@ -0,0 +1,6 @@
+export module subdir_importable;
+
+export int from_subdir()
+{
+  return 0;
+}
diff --git a/Tests/RunCMake/CXXModules/examples/export-interface-install/test/CMakeLists.txt b/Tests/RunCMake/CXXModules/examples/export-interface-install/test/CMakeLists.txt
index 7079256..78177ce 100644
--- a/Tests/RunCMake/CXXModules/examples/export-interface-install/test/CMakeLists.txt
+++ b/Tests/RunCMake/CXXModules/examples/export-interface-install/test/CMakeLists.txt
@@ -14,19 +14,31 @@
   PROPERTY INTERFACE_CXX_MODULE_SETS)
 if (NOT file_sets STREQUAL "modules")
   message(FATAL_ERROR
-    "Incorrect exported file sets in `CXXModules::export_interfaces`: `${file_sets}`")
+    "Incorrect exported file sets in CXXModules::export_interfaces:\n  ${file_sets}")
 endif ()
 
 get_property(file_set_files TARGET CXXModules::export_interfaces
   PROPERTY CXX_MODULE_SET_modules)
-if (NOT file_set_files STREQUAL "${expected_source_dir}/importable.cxx")
+set(expected_file_set_files
+  "${expected_source_dir}/importable.cxx"
+  "${expected_source_dir}/subdir/importable.cxx"
+  )
+if (NOT file_set_files STREQUAL "${expected_file_set_files}")
   message(FATAL_ERROR
-    "Incorrect exported file set paths in CXXModules::export_interfaces`: `${file_set_files}`")
+    "Incorrect exported file set paths in CXXModules::export_interfaces:\n  ${file_set_files}")
 endif ()
 
 get_property(imported_modules TARGET CXXModules::export_interfaces
   PROPERTY IMPORTED_CXX_MODULES_DEBUG)
-if (NOT imported_modules STREQUAL "importable=${expected_source_dir}/importable.cxx")
+set(expected_imported_modules
+  "importable=${expected_source_dir}/importable.cxx"
+  "subdir_importable=${expected_source_dir}/subdir/importable.cxx"
+  )
+if (NOT imported_modules STREQUAL "${expected_imported_modules}")
   message(FATAL_ERROR
-    "Incorrect exported modules in CXXModules::export_interfaces`: `${imported_modules}`")
+    "Incorrect exported modules in CXXModules::export_interfaces:\n"
+    "  ${imported_modules}\n"
+    "does not match:\n"
+    "  ${expected_imported_modules}"
+  )
 endif ()
diff --git a/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-build-stderr.txt b/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-build-stderr.txt
index 5e4392a..c8dbdcf 100644
--- a/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-build-stderr.txt
+++ b/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-build-stderr.txt
@@ -1,4 +1,4 @@
-CMake Warning \(dev\) at CMakeLists.txt:7 \(target_sources\):
+CMake Warning \(dev\) at CMakeLists.txt:[0-9]+ \(target_sources\):
   CMake's C\+\+ module support is experimental.  It is meant only for
   experimentation and feedback to CMake developers.
 This warning is for project developers.  Use -Wno-dev to suppress it.
diff --git a/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-build/CMakeLists.txt b/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-build/CMakeLists.txt
index 7633bec..a93e3a4 100644
--- a/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-build/CMakeLists.txt
+++ b/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-build/CMakeLists.txt
@@ -18,10 +18,14 @@
       BASE_DIRS
         "${CMAKE_CURRENT_SOURCE_DIR}"
       FILES
-        importable.cxx)
+        importable.cxx
+        subdir/importable.cxx
+  )
 target_compile_features(export_interfaces_no_properties PUBLIC cxx_std_20)
 
-install(TARGETS export_interfaces_no_properties
+add_library(no_modules STATIC no_modules.cxx)
+
+install(TARGETS export_interfaces_no_properties no_modules
   EXPORT CXXModules
   FILE_SET modules DESTINATION "lib/cxx/miu")
 export(EXPORT CXXModules
diff --git a/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-build/no_modules.cxx b/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-build/no_modules.cxx
new file mode 100644
index 0000000..eea854f
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-build/no_modules.cxx
@@ -0,0 +1,3 @@
+void no_modules()
+{
+}
diff --git a/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-build/subdir/importable.cxx b/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-build/subdir/importable.cxx
new file mode 100644
index 0000000..07d6af6
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-build/subdir/importable.cxx
@@ -0,0 +1,6 @@
+export module subdir_importable;
+
+export int from_subdir()
+{
+  return 0;
+}
diff --git a/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-build/test/CMakeLists.txt b/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-build/test/CMakeLists.txt
index 9cdc80a..18e933c 100644
--- a/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-build/test/CMakeLists.txt
+++ b/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-build/test/CMakeLists.txt
@@ -14,14 +14,18 @@
   PROPERTY INTERFACE_CXX_MODULE_SETS)
 if (NOT file_sets STREQUAL "modules")
   message(FATAL_ERROR
-    "Incorrect exported file sets in `CXXModules::export_interfaces_no_properties`: `${file_sets}`")
+    "Incorrect exported file sets in CXXModules::export_interfaces_no_properties:\n  ${file_sets}")
 endif ()
 
 get_property(file_set_files TARGET CXXModules::export_interfaces_no_properties
   PROPERTY CXX_MODULE_SET_modules)
-if (NOT file_set_files STREQUAL "${expected_dir}/importable.cxx")
+set(expected_file_set_files
+  "${expected_dir}/importable.cxx"
+  "${expected_dir}/subdir/importable.cxx"
+  )
+if (NOT file_set_files STREQUAL "${expected_file_set_files}")
   message(FATAL_ERROR
-    "Incorrect exported file set paths in CXXModules::export_interfaces_no_properties`: `${file_set_files}`")
+    "Incorrect exported file set paths in CXXModules::export_interfaces_no_properties:\n  ${file_set_files}")
 endif ()
 
 get_property(imported_modules_set TARGET CXXModules::export_interfaces_no_properties
diff --git a/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-install-stderr.txt b/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-install-stderr.txt
index 5e4392a..c8dbdcf 100644
--- a/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-install-stderr.txt
+++ b/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-install-stderr.txt
@@ -1,4 +1,4 @@
-CMake Warning \(dev\) at CMakeLists.txt:7 \(target_sources\):
+CMake Warning \(dev\) at CMakeLists.txt:[0-9]+ \(target_sources\):
   CMake's C\+\+ module support is experimental.  It is meant only for
   experimentation and feedback to CMake developers.
 This warning is for project developers.  Use -Wno-dev to suppress it.
diff --git a/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-install/CMakeLists.txt b/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-install/CMakeLists.txt
index 75f2440..99e67e7 100644
--- a/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-install/CMakeLists.txt
+++ b/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-install/CMakeLists.txt
@@ -18,10 +18,14 @@
       BASE_DIRS
         "${CMAKE_CURRENT_SOURCE_DIR}"
       FILES
-        importable.cxx)
+        importable.cxx
+        subdir/importable.cxx
+  )
 target_compile_features(export_interfaces_no_properties PUBLIC cxx_std_20)
 
-install(TARGETS export_interfaces_no_properties
+add_library(no_modules STATIC no_modules.cxx)
+
+install(TARGETS export_interfaces_no_properties no_modules
   EXPORT CXXModules
   FILE_SET modules DESTINATION "lib/cxx/miu")
 install(EXPORT CXXModules
diff --git a/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-install/no_modules.cxx b/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-install/no_modules.cxx
new file mode 100644
index 0000000..eea854f
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-install/no_modules.cxx
@@ -0,0 +1,3 @@
+void no_modules()
+{
+}
diff --git a/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-install/subdir/importable.cxx b/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-install/subdir/importable.cxx
new file mode 100644
index 0000000..07d6af6
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-install/subdir/importable.cxx
@@ -0,0 +1,6 @@
+export module subdir_importable;
+
+export int from_subdir()
+{
+  return 0;
+}
diff --git a/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-install/test/CMakeLists.txt b/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-install/test/CMakeLists.txt
index 9cdc80a..18e933c 100644
--- a/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-install/test/CMakeLists.txt
+++ b/Tests/RunCMake/CXXModules/examples/export-interface-no-properties-install/test/CMakeLists.txt
@@ -14,14 +14,18 @@
   PROPERTY INTERFACE_CXX_MODULE_SETS)
 if (NOT file_sets STREQUAL "modules")
   message(FATAL_ERROR
-    "Incorrect exported file sets in `CXXModules::export_interfaces_no_properties`: `${file_sets}`")
+    "Incorrect exported file sets in CXXModules::export_interfaces_no_properties:\n  ${file_sets}")
 endif ()
 
 get_property(file_set_files TARGET CXXModules::export_interfaces_no_properties
   PROPERTY CXX_MODULE_SET_modules)
-if (NOT file_set_files STREQUAL "${expected_dir}/importable.cxx")
+set(expected_file_set_files
+  "${expected_dir}/importable.cxx"
+  "${expected_dir}/subdir/importable.cxx"
+  )
+if (NOT file_set_files STREQUAL "${expected_file_set_files}")
   message(FATAL_ERROR
-    "Incorrect exported file set paths in CXXModules::export_interfaces_no_properties`: `${file_set_files}`")
+    "Incorrect exported file set paths in CXXModules::export_interfaces_no_properties:\n  ${file_set_files}")
 endif ()
 
 get_property(imported_modules_set TARGET CXXModules::export_interfaces_no_properties
diff --git a/Tests/RunCMake/CXXModules/examples/req-private-other-target-build-result.txt b/Tests/RunCMake/CXXModules/examples/req-private-other-target-build-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/req-private-other-target-build-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CXXModules/examples/req-private-other-target-build-stdout.txt b/Tests/RunCMake/CXXModules/examples/req-private-other-target-build-stdout.txt
new file mode 100644
index 0000000..912c2e9
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/req-private-other-target-build-stdout.txt
@@ -0,0 +1 @@
+((Ninja generators)?(Unable to use module 'lib.priv' as it is 'PRIVATE' and therefore not accessible outside of its owning target.))
diff --git a/Tests/RunCMake/CXXModules/examples/req-private-other-target-stderr.txt b/Tests/RunCMake/CXXModules/examples/req-private-other-target-stderr.txt
new file mode 100644
index 0000000..5e4392a
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/req-private-other-target-stderr.txt
@@ -0,0 +1,9 @@
+CMake Warning \(dev\) at CMakeLists.txt:7 \(target_sources\):
+  CMake's C\+\+ module support is experimental.  It is meant only for
+  experimentation and feedback to CMake developers.
+This warning is for project developers.  Use -Wno-dev to suppress it.
+
+CMake Warning \(dev\):
+  C\+\+20 modules support via CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP is
+  experimental.  It is meant only for compiler developers to try.
+This warning is for project developers.  Use -Wno-dev to suppress it.
diff --git a/Tests/RunCMake/CXXModules/examples/req-private-other-target/CMakeLists.txt b/Tests/RunCMake/CXXModules/examples/req-private-other-target/CMakeLists.txt
new file mode 100644
index 0000000..910c515
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/req-private-other-target/CMakeLists.txt
@@ -0,0 +1,16 @@
+cmake_minimum_required(VERSION 3.26)
+project(req_private_other_target CXX)
+
+include("${CMAKE_SOURCE_DIR}/../cxx-modules-rules.cmake")
+
+add_library(lib)
+target_sources(lib
+  PUBLIC FILE_SET pub TYPE CXX_MODULES FILES lib.cxx
+  PRIVATE FILE_SET priv TYPE CXX_MODULES FILES priv.cxx
+)
+target_compile_features(lib PUBLIC cxx_std_20)
+
+add_executable(exe main.cxx)
+target_link_libraries(exe PRIVATE lib)
+
+add_test(NAME exe COMMAND exe)
diff --git a/Tests/RunCMake/CXXModules/examples/req-private-other-target/lib.cxx b/Tests/RunCMake/CXXModules/examples/req-private-other-target/lib.cxx
new file mode 100644
index 0000000..066c2e1
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/req-private-other-target/lib.cxx
@@ -0,0 +1 @@
+export module lib;
diff --git a/Tests/RunCMake/CXXModules/examples/req-private-other-target/main.cxx b/Tests/RunCMake/CXXModules/examples/req-private-other-target/main.cxx
new file mode 100644
index 0000000..08b08ff
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/req-private-other-target/main.cxx
@@ -0,0 +1,7 @@
+import lib;
+import lib.priv;
+
+int main(int argc, char const* argv[])
+{
+  return 0;
+}
diff --git a/Tests/RunCMake/CXXModules/examples/req-private-other-target/priv.cxx b/Tests/RunCMake/CXXModules/examples/req-private-other-target/priv.cxx
new file mode 100644
index 0000000..a7cad3b
--- /dev/null
+++ b/Tests/RunCMake/CXXModules/examples/req-private-other-target/priv.cxx
@@ -0,0 +1 @@
+export module lib.priv;
diff --git a/Tests/RunCMake/CacheNewline/CacheNewline.cmake b/Tests/RunCMake/CacheNewline/CacheNewline.cmake
index 81851db..418a847 100644
--- a/Tests/RunCMake/CacheNewline/CacheNewline.cmake
+++ b/Tests/RunCMake/CacheNewline/CacheNewline.cmake
@@ -1,5 +1 @@
-cmake_minimum_required(VERSION 3.5)
-
-project(CacheNewlineTest NONE)
-
 set(NEWLINE_VARIABLE "a\nb" CACHE STRING "Offending entry")
diff --git a/Tests/RunCMake/CheckCompilerFlag/CMakeLists.txt b/Tests/RunCMake/CheckCompilerFlag/CMakeLists.txt
index 30cb9ae..67eae65 100644
--- a/Tests/RunCMake/CheckCompilerFlag/CMakeLists.txt
+++ b/Tests/RunCMake/CheckCompilerFlag/CMakeLists.txt
@@ -1,9 +1,3 @@
 cmake_minimum_required(VERSION 3.13)
-
-if(POLICY CMP0129)
-  cmake_policy(SET CMP0129 NEW)
-endif()
-
 project(${RunCMake_TEST} LANGUAGES NONE)
-
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/CheckIPOSupported/CMakeLists.txt b/Tests/RunCMake/CheckIPOSupported/CMakeLists.txt
index 4a13d29..9f18d8d 100644
--- a/Tests/RunCMake/CheckIPOSupported/CMakeLists.txt
+++ b/Tests/RunCMake/CheckIPOSupported/CMakeLists.txt
@@ -1,7 +1,5 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.9)
 project(${RunCMake_TEST} NONE)
 
-cmake_policy(SET CMP0069 NEW)
-
 include(CheckIPOSupported)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/CheckModules/CMakeLists.txt b/Tests/RunCMake/CheckModules/CMakeLists.txt
index 842c5cf..93ee9df 100644
--- a/Tests/RunCMake/CheckModules/CMakeLists.txt
+++ b/Tests/RunCMake/CheckModules/CMakeLists.txt
@@ -1,4 +1,3 @@
-cmake_minimum_required(VERSION 2.8.12)
-cmake_policy(SET CMP0054 NEW)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/CheckModules/CheckStructHasMemberWrongKey-stderr.txt b/Tests/RunCMake/CheckModules/CheckStructHasMemberWrongKey-stderr.txt
index b9fbd38..4a3f601 100644
--- a/Tests/RunCMake/CheckModules/CheckStructHasMemberWrongKey-stderr.txt
+++ b/Tests/RunCMake/CheckModules/CheckStructHasMemberWrongKey-stderr.txt
@@ -1,7 +1,7 @@
 CMake Error at .*/Modules/CheckStructHasMember.cmake:[0-9]+. \(message\):
   Unknown arguments:
 
-    LANGUAG;C
+    LANGUAG_;C
 
 Call Stack \(most recent call first\):
   CheckStructHasMemberWrongKey.cmake:[0-9]+ \(check_struct_has_member\)
diff --git a/Tests/RunCMake/CheckModules/CheckStructHasMemberWrongKey.cmake b/Tests/RunCMake/CheckModules/CheckStructHasMemberWrongKey.cmake
index 900eb0a..fea0eb0 100644
--- a/Tests/RunCMake/CheckModules/CheckStructHasMemberWrongKey.cmake
+++ b/Tests/RunCMake/CheckModules/CheckStructHasMemberWrongKey.cmake
@@ -1,2 +1,2 @@
 include(CheckStructHasMember)
-check_struct_has_member("struct timeval" tv_sec sys/select.h HAVE_TIMEVAL_TV_SEC_K LANGUAG C)
+check_struct_has_member("struct timeval" tv_sec sys/select.h HAVE_TIMEVAL_TV_SEC_K LANGUAG_ C)
diff --git a/Tests/RunCMake/CheckModules/CheckTypeSizeUnknownArgument-stderr.txt b/Tests/RunCMake/CheckModules/CheckTypeSizeUnknownArgument-stderr.txt
index 085488e..9227cc3 100644
--- a/Tests/RunCMake/CheckModules/CheckTypeSizeUnknownArgument-stderr.txt
+++ b/Tests/RunCMake/CheckModules/CheckTypeSizeUnknownArgument-stderr.txt
@@ -1,7 +1,7 @@
 CMake Error at .*/Modules/CheckTypeSize.cmake:[0-9]+. \(message\):
   Unknown argument:
 
-    LANGUAG
+    LANGUAG_
 
 Call Stack \(most recent call first\):
   CheckTypeSizeUnknownArgument.cmake:[0-9]+ \(check_type_size\)
diff --git a/Tests/RunCMake/CheckModules/CheckTypeSizeUnknownArgument.cmake b/Tests/RunCMake/CheckModules/CheckTypeSizeUnknownArgument.cmake
index 6f24ee1..cf6f0fc 100644
--- a/Tests/RunCMake/CheckModules/CheckTypeSizeUnknownArgument.cmake
+++ b/Tests/RunCMake/CheckModules/CheckTypeSizeUnknownArgument.cmake
@@ -1,2 +1,2 @@
 include(CheckTypeSize)
-check_type_size(int SIZEOF_INT BUILTIN_TYPES_ONLY LANGUAG CXX)
+check_type_size(int SIZEOF_INT BUILTIN_TYPES_ONLY LANGUAG_ CXX)
diff --git a/Tests/RunCMake/ClangTidy/CXX.cmake b/Tests/RunCMake/ClangTidy/CXX.cmake
index 2d22325..3214122 100644
--- a/Tests/RunCMake/ClangTidy/CXX.cmake
+++ b/Tests/RunCMake/ClangTidy/CXX.cmake
@@ -1,3 +1,3 @@
 enable_language(CXX)
-set(CMAKE_CXX_CLANG_TIDY "${PSEUDO_TIDY}" -some -args)
+set(CMAKE_CXX_CLANG_TIDY "$<1:${PSEUDO_TIDY}>" -some -args)
 add_executable(main main.cxx)
diff --git a/Tests/RunCMake/CommandLine/CMakeLists.txt b/Tests/RunCMake/CommandLine/CMakeLists.txt
index 2897109..93ee9df 100644
--- a/Tests/RunCMake/CommandLine/CMakeLists.txt
+++ b/Tests/RunCMake/CommandLine/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog-result.txt b/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog-stderr.txt b/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog-stderr.txt
new file mode 100644
index 0000000..6269c19
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog-stderr.txt
@@ -0,0 +1,2 @@
+^CMake Error: No file specified for --debugger-dap-log
+CMake Error: Run 'cmake --help' for all supported options\.$
diff --git a/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog.cmake b/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog.cmake
new file mode 100644
index 0000000..6ddce8b
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached.")
diff --git a/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe-result.txt b/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe-stderr.txt b/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe-stderr.txt
new file mode 100644
index 0000000..947cb00
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe-stderr.txt
@@ -0,0 +1,2 @@
+^CMake Error: No path specified for --debugger-pipe
+CMake Error: Run 'cmake --help' for all supported options\.$
diff --git a/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe.cmake b/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe.cmake
new file mode 100644
index 0000000..6ddce8b
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached.")
diff --git a/Tests/RunCMake/CommandLine/DebuggerCapabilityInspect-check.cmake b/Tests/RunCMake/CommandLine/DebuggerCapabilityInspect-check.cmake
new file mode 100644
index 0000000..75769f2
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerCapabilityInspect-check.cmake
@@ -0,0 +1,5 @@
+if(actual_stdout MATCHES [["debugger" *: *true]])
+  set_property(DIRECTORY PROPERTY CMake_ENABLE_DEBUGGER 1)
+else()
+  set_property(DIRECTORY PROPERTY CMake_ENABLE_DEBUGGER 0)
+endif()
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupported-result.txt b/Tests/RunCMake/CommandLine/DebuggerNotSupported-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupported-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupported-stderr.txt b/Tests/RunCMake/CommandLine/DebuggerNotSupported-stderr.txt
new file mode 100644
index 0000000..5845bb3
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupported-stderr.txt
@@ -0,0 +1,2 @@
+^CMake Error: CMake was not built with support for --debugger
+CMake Error: Run 'cmake --help' for all supported options\.$
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupported.cmake b/Tests/RunCMake/CommandLine/DebuggerNotSupported.cmake
new file mode 100644
index 0000000..6ddce8b
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupported.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached.")
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog-result.txt b/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog-stderr.txt b/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog-stderr.txt
new file mode 100644
index 0000000..84c2200
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog-stderr.txt
@@ -0,0 +1,2 @@
+^CMake Error: CMake was not built with support for --debugger-dap-log
+CMake Error: Run 'cmake --help' for all supported options\.$
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog.cmake b/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog.cmake
new file mode 100644
index 0000000..6ddce8b
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached.")
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe-result.txt b/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe-stderr.txt b/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe-stderr.txt
new file mode 100644
index 0000000..5684f4c
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe-stderr.txt
@@ -0,0 +1,2 @@
+^CMake Error: CMake was not built with support for --debugger-pipe
+CMake Error: Run 'cmake --help' for all supported options\.$
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe.cmake b/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe.cmake
new file mode 100644
index 0000000..6ddce8b
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached.")
diff --git a/Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt b/Tests/RunCMake/CommandLine/DeprecateVS9-WARN-OFF.cmake
similarity index 100%
copy from Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt
copy to Tests/RunCMake/CommandLine/DeprecateVS9-WARN-OFF.cmake
diff --git a/Tests/RunCMake/CommandLine/DeprecateVS9-WARN-ON-stderr.txt b/Tests/RunCMake/CommandLine/DeprecateVS9-WARN-ON-stderr.txt
new file mode 100644
index 0000000..c3329a0
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DeprecateVS9-WARN-ON-stderr.txt
@@ -0,0 +1,5 @@
+^CMake Warning:
+  The "Visual Studio 9 2008" generator is deprecated and will be removed in a
+  future version of CMake.
+
+  Add CMAKE_WARN_VS9=OFF to the cache to disable this warning.$
diff --git a/Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt b/Tests/RunCMake/CommandLine/DeprecateVS9-WARN-ON.cmake
similarity index 100%
copy from Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt
copy to Tests/RunCMake/CommandLine/DeprecateVS9-WARN-ON.cmake
diff --git a/Tests/RunCMake/CommandLine/E_capabilities-stdout.txt b/Tests/RunCMake/CommandLine/E_capabilities-stdout.txt
index 597dbd4..c01f414 100644
--- a/Tests/RunCMake/CommandLine/E_capabilities-stdout.txt
+++ b/Tests/RunCMake/CommandLine/E_capabilities-stdout.txt
@@ -1 +1 @@
-^{"fileApi":{"requests":\[{"kind":"codemodel","version":\[{"major":2,"minor":5}]},{"kind":"configureLog","version":\[{"major":1,"minor":0}]},{"kind":"cache","version":\[{"major":2,"minor":0}]},{"kind":"cmakeFiles","version":\[{"major":1,"minor":0}]},{"kind":"toolchains","version":\[{"major":1,"minor":0}]}]},"generators":\[.*\],"serverMode":false,"tls":(true|false),"version":{.*}}$
+^{"debugger":(true|false),"fileApi":{"requests":\[{"kind":"codemodel","version":\[{"major":2,"minor":6}]},{"kind":"configureLog","version":\[{"major":1,"minor":0}]},{"kind":"cache","version":\[{"major":2,"minor":0}]},{"kind":"cmakeFiles","version":\[{"major":1,"minor":0}]},{"kind":"toolchains","version":\[{"major":1,"minor":0}]}]},"generators":\[.*\],"serverMode":false,"tls":(true|false),"version":{.*}}$
diff --git a/Tests/RunCMake/CommandLine/E_time-stdout.txt b/Tests/RunCMake/CommandLine/E_time-stdout.txt
index a51446a..1a5e134 100644
--- a/Tests/RunCMake/CommandLine/E_time-stdout.txt
+++ b/Tests/RunCMake/CommandLine/E_time-stdout.txt
@@ -1,3 +1,3 @@
 ^hello  world
-Elapsed time: [^
+Elapsed time \(seconds\): [^
 ]*$
diff --git a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
index 943be24..45b4c0e 100644
--- a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 include(RunCMake)
 
@@ -125,6 +125,17 @@
 run_cmake_command(cache-empty-entry
   ${CMAKE_COMMAND} --build ${RunCMake_SOURCE_DIR}/cache-empty-entry/)
 
+run_cmake_command(DebuggerCapabilityInspect ${CMAKE_COMMAND} -E capabilities)
+get_property(CMake_ENABLE_DEBUGGER DIRECTORY PROPERTY CMake_ENABLE_DEBUGGER)
+if(CMake_ENABLE_DEBUGGER)
+  run_cmake_with_options(DebuggerArgMissingPipe --debugger-pipe)
+  run_cmake_with_options(DebuggerArgMissingDapLog --debugger-dap-log)
+else()
+  run_cmake_with_options(DebuggerNotSupported --debugger)
+  run_cmake_with_options(DebuggerNotSupportedPipe --debugger-pipe pipe)
+  run_cmake_with_options(DebuggerNotSupportedDapLog --debugger-dap-log dap-log)
+endif()
+
 function(run_ExplicitDirs)
   set(RunCMake_TEST_NO_CLEAN 1)
   set(RunCMake_TEST_NO_SOURCE_DIR 1)
@@ -1101,6 +1112,13 @@
 run_cmake(ProfilingTest)
 unset(RunCMake_TEST_OPTIONS)
 
+if(RunCMake_GENERATOR MATCHES "^Visual Studio 9 2008")
+  run_cmake_with_options(DeprecateVS9-WARN-ON -DCMAKE_WARN_VS9=ON)
+  unset(ENV{CMAKE_WARN_VS9})
+  run_cmake(DeprecateVS9-WARN-ON)
+  run_cmake_with_options(DeprecateVS9-WARN-OFF -DCMAKE_WARN_VS9=OFF)
+endif()
+
 if(RunCMake_GENERATOR MATCHES "^Visual Studio 11 2012")
   run_cmake_with_options(DeprecateVS11-WARN-ON -DCMAKE_WARN_VS11=ON)
   unset(ENV{CMAKE_WARN_VS11})
diff --git a/Tests/RunCMake/CommandLine/trace-expand-stderr.txt b/Tests/RunCMake/CommandLine/trace-expand-stderr.txt
index 4fee9bc..b900686 100644
--- a/Tests/RunCMake/CommandLine/trace-expand-stderr.txt
+++ b/Tests/RunCMake/CommandLine/trace-expand-stderr.txt
@@ -1,2 +1,2 @@
-^.*/Tests/RunCMake/CommandLine/CMakeLists.txt\(1\):  cmake_minimum_required\(VERSION 3.0 \)
+^.*/Tests/RunCMake/CommandLine/CMakeLists.txt\(1\):  cmake_minimum_required\(VERSION 3.5 \)
 .*/Tests/RunCMake/CommandLine/CMakeLists.txt\(2\):  project\(trace-expand NONE \)
diff --git a/Tests/RunCMake/CommandLine/trace-expand-warn-uninitialized-stderr.txt b/Tests/RunCMake/CommandLine/trace-expand-warn-uninitialized-stderr.txt
index 74429b6..88aad00 100644
--- a/Tests/RunCMake/CommandLine/trace-expand-warn-uninitialized-stderr.txt
+++ b/Tests/RunCMake/CommandLine/trace-expand-warn-uninitialized-stderr.txt
@@ -1,2 +1,2 @@
-^.*/Tests/RunCMake/CommandLine/CMakeLists.txt\(1\):  cmake_minimum_required\(VERSION 3.0 \)
+^.*/Tests/RunCMake/CommandLine/CMakeLists.txt\(1\):  cmake_minimum_required\(VERSION 3.5 \)
 .*/Tests/RunCMake/CommandLine/CMakeLists.txt\(2\):  project\(trace-expand-warn-uninitialized NONE \)
diff --git a/Tests/RunCMake/CommandLine/trace-stderr.txt b/Tests/RunCMake/CommandLine/trace-stderr.txt
index 8e8ddfa..4bf3cff 100644
--- a/Tests/RunCMake/CommandLine/trace-stderr.txt
+++ b/Tests/RunCMake/CommandLine/trace-stderr.txt
@@ -1,2 +1,2 @@
-^.*/Tests/RunCMake/CommandLine/CMakeLists.txt\(1\):  cmake_minimum_required\(VERSION 3.0 \)
+^.*/Tests/RunCMake/CommandLine/CMakeLists.txt\(1\):  cmake_minimum_required\(VERSION 3.5 \)
 .*/Tests/RunCMake/CommandLine/CMakeLists.txt\(2\):  project\(\${RunCMake_TEST} NONE \)
diff --git a/Tests/RunCMake/CommandLine/trace-try_compile-redirect.cmake b/Tests/RunCMake/CommandLine/trace-try_compile-redirect.cmake
index 982cb89..4107aa4 100644
--- a/Tests/RunCMake/CommandLine/trace-try_compile-redirect.cmake
+++ b/Tests/RunCMake/CommandLine/trace-try_compile-redirect.cmake
@@ -1,2 +1,2 @@
-cmake_minimum_required(VERSION 3.24)
-project(test C)
+cmake_policy(VERSION 3.24)
+enable_language(C)
diff --git a/Tests/RunCMake/CommandLine/trace-try_compile.cmake b/Tests/RunCMake/CommandLine/trace-try_compile.cmake
index 982cb89..4107aa4 100644
--- a/Tests/RunCMake/CommandLine/trace-try_compile.cmake
+++ b/Tests/RunCMake/CommandLine/trace-try_compile.cmake
@@ -1,2 +1,2 @@
-cmake_minimum_required(VERSION 3.24)
-project(test C)
+cmake_policy(VERSION 3.24)
+enable_language(C)
diff --git a/Tests/RunCMake/CommandLineTar/CMakeLists.txt b/Tests/RunCMake/CommandLineTar/CMakeLists.txt
index 2897109..93ee9df 100644
--- a/Tests/RunCMake/CommandLineTar/CMakeLists.txt
+++ b/Tests/RunCMake/CommandLineTar/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/CompatibleInterface/CMakeLists.txt b/Tests/RunCMake/CompatibleInterface/CMakeLists.txt
index ebab7a3..12a7fd4 100644
--- a/Tests/RunCMake/CompatibleInterface/CMakeLists.txt
+++ b/Tests/RunCMake/CompatibleInterface/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} CXX)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/CompatibleInterface/DebugProperties.cmake b/Tests/RunCMake/CompatibleInterface/DebugProperties.cmake
index 64b52d9..60a4246 100644
--- a/Tests/RunCMake/CompatibleInterface/DebugProperties.cmake
+++ b/Tests/RunCMake/CompatibleInterface/DebugProperties.cmake
@@ -1,7 +1,4 @@
-
-cmake_minimum_required(VERSION 3.3)
-
-project(CompatibleInterface)
+enable_language(CXX)
 
 include(GenerateExportHeader)
 set(CMAKE_INCLUDE_CURRENT_DIR ON)
diff --git a/Tests/RunCMake/CompileDefinitions/CMakeLists.txt b/Tests/RunCMake/CompileDefinitions/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/CompileDefinitions/CMakeLists.txt
+++ b/Tests/RunCMake/CompileDefinitions/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/CompileFeatures/CMakeLists.txt b/Tests/RunCMake/CompileFeatures/CMakeLists.txt
index 3482e6b..12a7fd4 100644
--- a/Tests/RunCMake/CompileFeatures/CMakeLists.txt
+++ b/Tests/RunCMake/CompileFeatures/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} CXX)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/CompileFeatures/RunCMakeTest.cmake b/Tests/RunCMake/CompileFeatures/RunCMakeTest.cmake
index a001c5d..7b72ffe 100644
--- a/Tests/RunCMake/CompileFeatures/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CompileFeatures/RunCMakeTest.cmake
@@ -130,7 +130,7 @@
 
 function(test_cmp0128_warn_unset)
   # For compilers that had CMAKE_<LANG>_EXTENSION_COMPILE_OPTION (only IAR)
-  # there is no behavioural change and thus no warning.
+  # there is no behavioral change and thus no warning.
   if(NOT "${${lang}_EXT_FLAG}" STREQUAL "")
     return()
   endif()
diff --git a/Tests/RunCMake/CompilerArgs/CMakeLists.txt b/Tests/RunCMake/CompilerArgs/CMakeLists.txt
index 18dfd26..93ee9df 100644
--- a/Tests/RunCMake/CompilerArgs/CMakeLists.txt
+++ b/Tests/RunCMake/CompilerArgs/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.2)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/CompilerChange/CMakeLists.txt b/Tests/RunCMake/CompilerChange/CMakeLists.txt
index 14c47ad..b41f3f3 100644
--- a/Tests/RunCMake/CompilerChange/CMakeLists.txt
+++ b/Tests/RunCMake/CompilerChange/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 if(NOT RunCMake_TEST)
   set(RunCMake_TEST "$ENV{RunCMake_TEST}") # needed when cache is deleted
 endif()
diff --git a/Tests/RunCMake/CompilerLauncher/C-env-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/C-env-Build-stdout.txt
index 3313e31..544b65f 100644
--- a/Tests/RunCMake/CompilerLauncher/C-env-Build-stdout.txt
+++ b/Tests/RunCMake/CompilerLauncher/C-env-Build-stdout.txt
@@ -1 +1 @@
-.*-E env USED_LAUNCHER=1.*
+.*-E env USED_LAUNCHER=1 TARGET_NAME=main LANGUAGE=C.*
diff --git a/Tests/RunCMake/CompilerLauncher/C-launch-env-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/C-launch-env-Build-stdout.txt
index 3313e31..544b65f 100644
--- a/Tests/RunCMake/CompilerLauncher/C-launch-env-Build-stdout.txt
+++ b/Tests/RunCMake/CompilerLauncher/C-launch-env-Build-stdout.txt
@@ -1 +1 @@
-.*-E env USED_LAUNCHER=1.*
+.*-E env USED_LAUNCHER=1 TARGET_NAME=main LANGUAGE=C.*
diff --git a/Tests/RunCMake/CompilerLauncher/CMakeLists.txt b/Tests/RunCMake/CompilerLauncher/CMakeLists.txt
index 18dfd26..93ee9df 100644
--- a/Tests/RunCMake/CompilerLauncher/CMakeLists.txt
+++ b/Tests/RunCMake/CompilerLauncher/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.2)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/CompilerLauncher/CUDA-env-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/CUDA-env-Build-stdout.txt
index 3313e31..a6e8b0a 100644
--- a/Tests/RunCMake/CompilerLauncher/CUDA-env-Build-stdout.txt
+++ b/Tests/RunCMake/CompilerLauncher/CUDA-env-Build-stdout.txt
@@ -1 +1 @@
-.*-E env USED_LAUNCHER=1.*
+.*-E env USED_LAUNCHER=1 TARGET_NAME=main LANGUAGE=CUDA.*
diff --git a/Tests/RunCMake/CompilerLauncher/CUDA-launch-env-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/CUDA-launch-env-Build-stdout.txt
index 3313e31..a6e8b0a 100644
--- a/Tests/RunCMake/CompilerLauncher/CUDA-launch-env-Build-stdout.txt
+++ b/Tests/RunCMake/CompilerLauncher/CUDA-launch-env-Build-stdout.txt
@@ -1 +1 @@
-.*-E env USED_LAUNCHER=1.*
+.*-E env USED_LAUNCHER=1 TARGET_NAME=main LANGUAGE=CUDA.*
diff --git a/Tests/RunCMake/CompilerLauncher/CXX-env-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/CXX-env-Build-stdout.txt
index 3313e31..082c7b5 100644
--- a/Tests/RunCMake/CompilerLauncher/CXX-env-Build-stdout.txt
+++ b/Tests/RunCMake/CompilerLauncher/CXX-env-Build-stdout.txt
@@ -1 +1 @@
-.*-E env USED_LAUNCHER=1.*
+.*-E env USED_LAUNCHER=1 TARGET_NAME=main LANGUAGE=CXX.*
diff --git a/Tests/RunCMake/CompilerLauncher/CXX-launch-env-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/CXX-launch-env-Build-stdout.txt
index 3313e31..082c7b5 100644
--- a/Tests/RunCMake/CompilerLauncher/CXX-launch-env-Build-stdout.txt
+++ b/Tests/RunCMake/CompilerLauncher/CXX-launch-env-Build-stdout.txt
@@ -1 +1 @@
-.*-E env USED_LAUNCHER=1.*
+.*-E env USED_LAUNCHER=1 TARGET_NAME=main LANGUAGE=CXX.*
diff --git a/Tests/RunCMake/CompilerLauncher/Fortran-env-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/Fortran-env-Build-stdout.txt
index 3313e31..9f8c754 100644
--- a/Tests/RunCMake/CompilerLauncher/Fortran-env-Build-stdout.txt
+++ b/Tests/RunCMake/CompilerLauncher/Fortran-env-Build-stdout.txt
@@ -1 +1 @@
-.*-E env USED_LAUNCHER=1.*
+.*-E env USED_LAUNCHER=1 TARGET_NAME=main LANGUAGE=Fortran.*
diff --git a/Tests/RunCMake/CompilerLauncher/Fortran-launch-env-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/Fortran-launch-env-Build-stdout.txt
index 3313e31..9f8c754 100644
--- a/Tests/RunCMake/CompilerLauncher/Fortran-launch-env-Build-stdout.txt
+++ b/Tests/RunCMake/CompilerLauncher/Fortran-launch-env-Build-stdout.txt
@@ -1 +1 @@
-.*-E env USED_LAUNCHER=1.*
+.*-E env USED_LAUNCHER=1 TARGET_NAME=main LANGUAGE=Fortran.*
diff --git a/Tests/RunCMake/CompilerLauncher/HIP-env-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/HIP-env-Build-stdout.txt
index 3313e31..354e317 100644
--- a/Tests/RunCMake/CompilerLauncher/HIP-env-Build-stdout.txt
+++ b/Tests/RunCMake/CompilerLauncher/HIP-env-Build-stdout.txt
@@ -1 +1 @@
-.*-E env USED_LAUNCHER=1.*
+.*-E env USED_LAUNCHER=1 TARGET_NAME=main LANGUAGE=HIP.*
diff --git a/Tests/RunCMake/CompilerLauncher/HIP-env-launch-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/HIP-env-launch-Build-stdout.txt
deleted file mode 100644
index 3313e31..0000000
--- a/Tests/RunCMake/CompilerLauncher/HIP-env-launch-Build-stdout.txt
+++ /dev/null
@@ -1 +0,0 @@
-.*-E env USED_LAUNCHER=1.*
diff --git a/Tests/RunCMake/CompilerLauncher/HIP-launch-env-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/HIP-launch-env-Build-stdout.txt
new file mode 100644
index 0000000..354e317
--- /dev/null
+++ b/Tests/RunCMake/CompilerLauncher/HIP-launch-env-Build-stdout.txt
@@ -0,0 +1 @@
+.*-E env USED_LAUNCHER=1 TARGET_NAME=main LANGUAGE=HIP.*
diff --git a/Tests/RunCMake/CompilerLauncher/ISPC-env-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/ISPC-env-Build-stdout.txt
index 3313e31..6b71839 100644
--- a/Tests/RunCMake/CompilerLauncher/ISPC-env-Build-stdout.txt
+++ b/Tests/RunCMake/CompilerLauncher/ISPC-env-Build-stdout.txt
@@ -1 +1 @@
-.*-E env USED_LAUNCHER=1.*
+.*-E env USED_LAUNCHER=1 TARGET_NAME=main LANGUAGE=ISPC.*
diff --git a/Tests/RunCMake/CompilerLauncher/ISPC-launch-env-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/ISPC-launch-env-Build-stdout.txt
index 3313e31..6b71839 100644
--- a/Tests/RunCMake/CompilerLauncher/ISPC-launch-env-Build-stdout.txt
+++ b/Tests/RunCMake/CompilerLauncher/ISPC-launch-env-Build-stdout.txt
@@ -1 +1 @@
-.*-E env USED_LAUNCHER=1.*
+.*-E env USED_LAUNCHER=1 TARGET_NAME=main LANGUAGE=ISPC.*
diff --git a/Tests/RunCMake/CompilerLauncher/OBJC-env-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/OBJC-env-Build-stdout.txt
index 3313e31..d2efd3d 100644
--- a/Tests/RunCMake/CompilerLauncher/OBJC-env-Build-stdout.txt
+++ b/Tests/RunCMake/CompilerLauncher/OBJC-env-Build-stdout.txt
@@ -1 +1 @@
-.*-E env USED_LAUNCHER=1.*
+.*-E env USED_LAUNCHER=1 TARGET_NAME=main LANGUAGE=OBJC.*
diff --git a/Tests/RunCMake/CompilerLauncher/OBJC-env-launch-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/OBJC-env-launch-Build-stdout.txt
deleted file mode 100644
index 3313e31..0000000
--- a/Tests/RunCMake/CompilerLauncher/OBJC-env-launch-Build-stdout.txt
+++ /dev/null
@@ -1 +0,0 @@
-.*-E env USED_LAUNCHER=1.*
diff --git a/Tests/RunCMake/CompilerLauncher/OBJC-launch-env-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/OBJC-launch-env-Build-stdout.txt
new file mode 100644
index 0000000..d2efd3d
--- /dev/null
+++ b/Tests/RunCMake/CompilerLauncher/OBJC-launch-env-Build-stdout.txt
@@ -0,0 +1 @@
+.*-E env USED_LAUNCHER=1 TARGET_NAME=main LANGUAGE=OBJC.*
diff --git a/Tests/RunCMake/CompilerLauncher/OBJCXX-env-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/OBJCXX-env-Build-stdout.txt
index 3313e31..0082ab2 100644
--- a/Tests/RunCMake/CompilerLauncher/OBJCXX-env-Build-stdout.txt
+++ b/Tests/RunCMake/CompilerLauncher/OBJCXX-env-Build-stdout.txt
@@ -1 +1 @@
-.*-E env USED_LAUNCHER=1.*
+.*-E env USED_LAUNCHER=1 TARGET_NAME=main LANGUAGE=OBJCXX.*
diff --git a/Tests/RunCMake/CompilerLauncher/OBJCXX-env-launch-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/OBJCXX-env-launch-Build-stdout.txt
deleted file mode 100644
index 3313e31..0000000
--- a/Tests/RunCMake/CompilerLauncher/OBJCXX-env-launch-Build-stdout.txt
+++ /dev/null
@@ -1 +0,0 @@
-.*-E env USED_LAUNCHER=1.*
diff --git a/Tests/RunCMake/CompilerLauncher/OBJCXX-launch-env-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/OBJCXX-launch-env-Build-stdout.txt
new file mode 100644
index 0000000..0082ab2
--- /dev/null
+++ b/Tests/RunCMake/CompilerLauncher/OBJCXX-launch-env-Build-stdout.txt
@@ -0,0 +1 @@
+.*-E env USED_LAUNCHER=1 TARGET_NAME=main LANGUAGE=OBJCXX.*
diff --git a/Tests/RunCMake/CompilerLauncher/RunCMakeTest.cmake b/Tests/RunCMake/CompilerLauncher/RunCMakeTest.cmake
index e6a2605..b051a19 100644
--- a/Tests/RunCMake/CompilerLauncher/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CompilerLauncher/RunCMakeTest.cmake
@@ -18,7 +18,7 @@
 function(run_compiler_launcher_env lang)
   string(REGEX REPLACE "-.*" "" core_lang "${lang}")
   # Use the noop genexp $<PATH:...> genexp to validate genexp support.
-  set(ENV{CMAKE_${core_lang}_COMPILER_LAUNCHER} "$<PATH:CMAKE_PATH,${CMAKE_COMMAND}>;-E;env;USED_LAUNCHER=1")
+  set(ENV{CMAKE_${core_lang}_COMPILER_LAUNCHER} "$<PATH:CMAKE_PATH,${CMAKE_COMMAND}>;-E;env;USED_LAUNCHER=1;TARGET_NAME=$<TARGET_PROPERTY:NAME>;LANGUAGE=$<COMPILE_LANGUAGE>")
   run_compiler_launcher(${lang})
   unset(ENV{CMAKE_${core_lang}_COMPILER_LAUNCHER})
 endfunction()
diff --git a/Tests/RunCMake/Configure/CMakeLists.txt b/Tests/RunCMake/Configure/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/Configure/CMakeLists.txt
+++ b/Tests/RunCMake/Configure/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/Cppcheck/CXX.cmake b/Tests/RunCMake/Cppcheck/CXX.cmake
index 3b79471..7030c61 100644
--- a/Tests/RunCMake/Cppcheck/CXX.cmake
+++ b/Tests/RunCMake/Cppcheck/CXX.cmake
@@ -1,3 +1,3 @@
 enable_language(CXX)
-set(CMAKE_CXX_CPPCHECK "${PSEUDO_CPPCHECK}")
+set(CMAKE_CXX_CPPCHECK "$<1:${PSEUDO_CPPCHECK}>")
 add_executable(main main.cxx)
diff --git a/Tests/RunCMake/Cpplint/CXX.cmake b/Tests/RunCMake/Cpplint/CXX.cmake
index 35f05ee..b58609c 100644
--- a/Tests/RunCMake/Cpplint/CXX.cmake
+++ b/Tests/RunCMake/Cpplint/CXX.cmake
@@ -1,3 +1,3 @@
 enable_language(CXX)
-set(CMAKE_CXX_CPPLINT "${PSEUDO_CPPLINT}" --verbose=0 --linelength=80)
+set(CMAKE_CXX_CPPLINT "$<1:${PSEUDO_CPPLINT}>" --verbose=0 --linelength=80)
 add_executable(main main.cxx)
diff --git a/Tests/RunCMake/CrosscompilingEmulator/CMakeLists.txt b/Tests/RunCMake/CrosscompilingEmulator/CMakeLists.txt
index 2d75985..12a7fd4 100644
--- a/Tests/RunCMake/CrosscompilingEmulator/CMakeLists.txt
+++ b/Tests/RunCMake/CrosscompilingEmulator/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} CXX)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/DisallowedCommands/RunCMakeTest.cmake b/Tests/RunCMake/DisallowedCommands/RunCMakeTest.cmake
index 208ea20..f3974ea 100644
--- a/Tests/RunCMake/DisallowedCommands/RunCMakeTest.cmake
+++ b/Tests/RunCMake/DisallowedCommands/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 foreach(p
     CMP0029
diff --git a/Tests/RunCMake/ExcludeFromAll/CMakeLists.txt b/Tests/RunCMake/ExcludeFromAll/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/ExcludeFromAll/CMakeLists.txt
+++ b/Tests/RunCMake/ExcludeFromAll/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/ExternalData/CMakeLists.txt b/Tests/RunCMake/ExternalData/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/ExternalData/CMakeLists.txt
+++ b/Tests/RunCMake/ExternalData/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/ExternalProject/Add_StepDependencies-stderr.txt b/Tests/RunCMake/ExternalProject/Add_StepDependencies-stderr.txt
new file mode 100644
index 0000000..650be64
--- /dev/null
+++ b/Tests/RunCMake/ExternalProject/Add_StepDependencies-stderr.txt
@@ -0,0 +1,10 @@
+^(CMake Deprecation Warning at Add_StepDependencies.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0114 will be removed from a future version
+  of CMake.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\))?$
diff --git a/Tests/RunCMake/ExternalProject/Add_StepDependencies.cmake b/Tests/RunCMake/ExternalProject/Add_StepDependencies.cmake
index 364bf9e..02c7c8e 100644
--- a/Tests/RunCMake/ExternalProject/Add_StepDependencies.cmake
+++ b/Tests/RunCMake/ExternalProject/Add_StepDependencies.cmake
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION ${CMAKE_VERSION})
+cmake_policy(VERSION ${CMAKE_VERSION})
 if(CMAKE_XCODE_BUILD_SYSTEM VERSION_GREATER_EQUAL 12)
   cmake_policy(SET CMP0114 NEW)
 else()
diff --git a/Tests/RunCMake/ExternalProject/Add_StepDependencies_no_target-stderr.txt b/Tests/RunCMake/ExternalProject/Add_StepDependencies_no_target-stderr.txt
new file mode 100644
index 0000000..c142541
--- /dev/null
+++ b/Tests/RunCMake/ExternalProject/Add_StepDependencies_no_target-stderr.txt
@@ -0,0 +1,10 @@
+^(CMake Deprecation Warning at Add_StepDependencies_no_target.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0114 will be removed from a future version
+  of CMake.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\))?$
diff --git a/Tests/RunCMake/ExternalProject/Add_StepDependencies_no_target.cmake b/Tests/RunCMake/ExternalProject/Add_StepDependencies_no_target.cmake
index da823cd..31b7baf 100644
--- a/Tests/RunCMake/ExternalProject/Add_StepDependencies_no_target.cmake
+++ b/Tests/RunCMake/ExternalProject/Add_StepDependencies_no_target.cmake
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION ${CMAKE_VERSION})
+cmake_policy(VERSION ${CMAKE_VERSION})
 if(CMAKE_XCODE_BUILD_SYSTEM VERSION_GREATER_EQUAL 12)
   cmake_policy(SET CMP0114 NEW)
 else()
diff --git a/Tests/RunCMake/ExternalProject/NO_DEPENDS-CMP0114-OLD-stderr.txt b/Tests/RunCMake/ExternalProject/NO_DEPENDS-CMP0114-OLD-stderr.txt
index 2b0feb6..2428b8c 100644
--- a/Tests/RunCMake/ExternalProject/NO_DEPENDS-CMP0114-OLD-stderr.txt
+++ b/Tests/RunCMake/ExternalProject/NO_DEPENDS-CMP0114-OLD-stderr.txt
@@ -1,4 +1,15 @@
-^CMake Warning \(dev\) at [^
+^CMake Deprecation Warning at NO_DEPENDS-CMP0114-OLD.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0114 will be removed from a future version
+  of CMake.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
++
+CMake Warning \(dev\) at [^
 ]*/Modules/ExternalProject.cmake:[0-9]+ \(message\):
   Using NO_DEPENDS for "configure" step might break parallel builds
 Call Stack \(most recent call first\):
diff --git a/Tests/RunCMake/ExternalProject/Steps-CMP0114-OLD-stderr.txt b/Tests/RunCMake/ExternalProject/Steps-CMP0114-OLD-stderr.txt
new file mode 100644
index 0000000..6dd7cb0
--- /dev/null
+++ b/Tests/RunCMake/ExternalProject/Steps-CMP0114-OLD-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at Steps-CMP0114-OLD.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0114 will be removed from a future version
+  of CMake.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/ExternalProject/UsesTerminal-check.cmake b/Tests/RunCMake/ExternalProject/UsesTerminal-check.cmake
index 2946c0b..351d70f 100644
--- a/Tests/RunCMake/ExternalProject/UsesTerminal-check.cmake
+++ b/Tests/RunCMake/ExternalProject/UsesTerminal-check.cmake
@@ -1,5 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
-
 # If we are using the Ninja generator, we can check and verify that the
 # USES_TERMINAL option actually works by examining the Ninja build file.
 # This is the only way, since CMake doesn't offer a way to examine the
diff --git a/Tests/RunCMake/ExtraGenerators/CMakeLists.txt b/Tests/RunCMake/ExtraGenerators/CMakeLists.txt
new file mode 100644
index 0000000..93ee9df
--- /dev/null
+++ b/Tests/RunCMake/ExtraGenerators/CMakeLists.txt
@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.5)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/ExtraGenerators/RunCMakeTest.cmake b/Tests/RunCMake/ExtraGenerators/RunCMakeTest.cmake
new file mode 100644
index 0000000..fbef79c
--- /dev/null
+++ b/Tests/RunCMake/ExtraGenerators/RunCMakeTest.cmake
@@ -0,0 +1,19 @@
+include(RunCMake)
+
+foreach(
+  extraGenerator
+  IN ITEMS
+    "CodeBlocks"
+    "CodeLite"
+    "Eclipse CDT4"
+    "Kate"
+    "Sublime Text 2"
+  )
+  block()
+    set(RunCMake_GENERATOR "${extraGenerator} - ${RunCMake_GENERATOR}")
+    set(RunCMake_TEST_VARIANT_DESCRIPTION ": ${RunCMake_GENERATOR}")
+    string(REPLACE " " "" extraGeneratorNoSpaces "${extraGenerator}")
+    set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/Simple-${extraGeneratorNoSpaces})
+    run_cmake(Simple)
+  endblock()
+endforeach()
diff --git a/Tests/RunCMake/ExtraGenerators/Simple-stderr.txt b/Tests/RunCMake/ExtraGenerators/Simple-stderr.txt
new file mode 100644
index 0000000..e327a9f
--- /dev/null
+++ b/Tests/RunCMake/ExtraGenerators/Simple-stderr.txt
@@ -0,0 +1,8 @@
+^CMake Deprecation Warning:
+  Support for "Extra Generators" like
+
+    [^
+]+
+
+  is deprecated and will be removed from a future version of CMake\.  IDEs may
+  use the cmake-file-api\(7\) to view CMake-generated project build trees\.
diff --git a/Tests/RunCMake/ExtraGenerators/Simple.cmake b/Tests/RunCMake/ExtraGenerators/Simple.cmake
new file mode 100644
index 0000000..d077046
--- /dev/null
+++ b/Tests/RunCMake/ExtraGenerators/Simple.cmake
@@ -0,0 +1,4 @@
+enable_language(C)
+enable_language(CXX)
+
+add_subdirectory(../../Simple Simple)
diff --git a/Tests/RunCMake/FPHSA/CMakeLists.txt b/Tests/RunCMake/FPHSA/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/FPHSA/CMakeLists.txt
+++ b/Tests/RunCMake/FPHSA/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/FeatureSummary/CMakeLists.txt b/Tests/RunCMake/FeatureSummary/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/FeatureSummary/CMakeLists.txt
+++ b/Tests/RunCMake/FeatureSummary/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/FetchContent/IgnoreToolchainFile.cmake b/Tests/RunCMake/FetchContent/IgnoreToolchainFile.cmake
new file mode 100644
index 0000000..f8ee749
--- /dev/null
+++ b/Tests/RunCMake/FetchContent/IgnoreToolchainFile.cmake
@@ -0,0 +1,2 @@
+set(ENV{CMAKE_TOOLCHAIN_FILE} path/to/somewhere/iDoNotExist.cmake)
+include(DownloadFile.cmake)
diff --git a/Tests/RunCMake/FetchContent/RunCMakeTest.cmake b/Tests/RunCMake/FetchContent/RunCMakeTest.cmake
index bb27491..3781089 100644
--- a/Tests/RunCMake/FetchContent/RunCMakeTest.cmake
+++ b/Tests/RunCMake/FetchContent/RunCMakeTest.cmake
@@ -7,6 +7,7 @@
 run_cmake(FirstDetailsWin)
 run_cmake(DownloadTwice)
 run_cmake(DownloadFile)
+run_cmake(IgnoreToolchainFile)
 run_cmake(SameGenerator)
 run_cmake(System)
 run_cmake(VarDefinitions)
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-check.py b/Tests/RunCMake/FileAPI/codemodel-v2-check.py
index fda18b5..b669543 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-check.py
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-check.py
@@ -12,7 +12,7 @@
 def check_objects(o, g):
     assert is_list(o)
     assert len(o) == 1
-    check_index_object(o[0], "codemodel", 2, 5, check_object_codemodel(g))
+    check_index_object(o[0], "codemodel", 2, 6, check_object_codemodel(g))
 
 def check_backtrace(t, b, backtrace):
     btg = t["backtraceGraph"]
@@ -578,6 +578,30 @@
                                      missing_exception=lambda e: "Include path: %s" % e["path"],
                                      extra_exception=lambda a: "Include path: %s" % a["path"])
 
+                if expected["frameworks"] is not None:
+                    expected_keys.append("frameworks")
+
+                    def check_include(actual, expected):
+                        assert is_dict(actual)
+                        expected_keys = ["path"]
+
+                        if expected["isSystem"] is not None:
+                            expected_keys.append("isSystem")
+                            assert is_bool(actual["isSystem"], expected["isSystem"])
+
+                        if expected["backtrace"] is not None:
+                            expected_keys.append("backtrace")
+                            check_backtrace(obj, actual["backtrace"], expected["backtrace"])
+
+                        assert sorted(actual.keys()) == sorted(expected_keys)
+
+                    check_list_match(lambda a, e: matches(a["path"], e["path"]),
+                                     actual["frameworks"], expected["frameworks"],
+                                     check=check_include,
+                                     check_exception=lambda a, e: "Framework path: %s" % a["path"],
+                                     missing_exception=lambda e: "Framework path: %s" % e["path"],
+                                     extra_exception=lambda a: "Framework path: %s" % a["path"])
+
                 if "precompileHeaders" in expected:
                     expected_keys.append("precompileHeaders")
 
@@ -693,6 +717,7 @@
         read_codemodel_json_data("directories/external.json"),
         read_codemodel_json_data("directories/fileset.json"),
         read_codemodel_json_data("directories/subdir.json"),
+        read_codemodel_json_data("directories/framework.json"),
     ]
 
     if matches(g["name"], "^Visual Studio "):
@@ -776,6 +801,12 @@
         read_codemodel_json_data("targets/cxx_object_lib.json"),
         read_codemodel_json_data("targets/cxx_object_exe.json"),
 
+        read_codemodel_json_data("targets/all_build_framework.json"),
+        read_codemodel_json_data("targets/zero_check_framework.json"),
+        read_codemodel_json_data("targets/static_framework.json"),
+        read_codemodel_json_data("targets/shared_framework.json"),
+        read_codemodel_json_data("targets/exe_framework.json"),
+
         read_codemodel_json_data("targets/all_build_imported.json"),
         read_codemodel_json_data("targets/zero_check_imported.json"),
         read_codemodel_json_data("targets/link_imported_exe.json"),
@@ -800,6 +831,21 @@
         read_codemodel_json_data("targets/c_headers_2.json"),
     ]
 
+    if sys.platform == "darwin":
+        for e in expected:
+            if e["name"] == "static_framework":
+                apple_static_framework = read_codemodel_json_data("targets/apple_static_framework.json")
+                e["artifacts"] = apple_static_framework["artifacts"]
+                e["nameOnDisk"] = apple_static_framework["nameOnDisk"]
+            elif e["name"] == "shared_framework":
+                apple_shared_framework = read_codemodel_json_data("targets/apple_shared_framework.json")
+                e["artifacts"] = apple_shared_framework["artifacts"]
+                e["nameOnDisk"] = apple_shared_framework["nameOnDisk"]
+            elif e["name"] == "exe_framework":
+                apple_exe_framework = read_codemodel_json_data("targets/apple_exe_framework.json")
+                e["compileGroups"] = apple_exe_framework["compileGroups"]
+                e["link"] = apple_exe_framework["link"]
+
     if cxx_compiler_id in ['Clang', 'AppleClang', 'LCC', 'GNU', 'Intel', 'IntelLLVM', 'MSVC', 'Embarcadero', 'IBMClang'] and g["name"] != "Xcode":
         for e in expected:
             if e["name"] == "cxx_exe":
@@ -848,8 +894,219 @@
         for e in expected:
             if e["type"] == "UTILITY":
                 if e["id"] == "^ZERO_CHECK::@6890427a1f51a3e7e1df$":
+                    # The json files have data for Xcode.  Substitute data for VS.
                     e["sources"] = [
                         {
+                            "path": "^CMakeLists\\.txt$",
+                            "isGenerated": None,
+                            "fileSetName": None,
+                            "sourceGroupName": "",
+                            "compileGroupLanguage": None,
+                            "backtrace": [
+                                {
+                                    "file": "^CMakeLists\\.txt$",
+                                    "line": None,
+                                    "command": None,
+                                    "hasParent": False,
+                                },
+                            ],
+                        },
+                        {
+                            "path": "^alias/CMakeLists\\.txt$",
+                            "isGenerated": None,
+                            "fileSetName": None,
+                            "sourceGroupName": "",
+                            "compileGroupLanguage": None,
+                            "backtrace": [
+                                {
+                                    "file": "^CMakeLists\\.txt$",
+                                    "line": None,
+                                    "command": None,
+                                    "hasParent": False,
+                                },
+                            ],
+                        },
+                        {
+                            "path": "^codemodel-v2\\.cmake$",
+                            "isGenerated": None,
+                            "fileSetName": None,
+                            "sourceGroupName": "",
+                            "compileGroupLanguage": None,
+                            "backtrace": [
+                                {
+                                    "file": "^CMakeLists\\.txt$",
+                                    "line": None,
+                                    "command": None,
+                                    "hasParent": False,
+                                },
+                            ],
+                        },
+                        {
+                            "path": "^custom/CMakeLists\\.txt$",
+                            "isGenerated": None,
+                            "fileSetName": None,
+                            "sourceGroupName": "",
+                            "compileGroupLanguage": None,
+                            "backtrace": [
+                                {
+                                    "file": "^CMakeLists\\.txt$",
+                                    "line": None,
+                                    "command": None,
+                                    "hasParent": False,
+                                },
+                            ],
+                        },
+                        {
+                            "path": "^cxx/CMakeLists\\.txt$",
+                            "isGenerated": None,
+                            "fileSetName": None,
+                            "sourceGroupName": "",
+                            "compileGroupLanguage": None,
+                            "backtrace": [
+                                {
+                                    "file": "^CMakeLists\\.txt$",
+                                    "line": None,
+                                    "command": None,
+                                    "hasParent": False,
+                                },
+                            ],
+                        },
+                        {
+                            "path": "^framework/CMakeLists\\.txt$",
+                            "isGenerated": None,
+                            "fileSetName": None,
+                            "sourceGroupName": "",
+                            "compileGroupLanguage": None,
+                            "backtrace": [
+                                {
+                                    "file": "^CMakeLists\\.txt$",
+                                    "line": None,
+                                    "command": None,
+                                    "hasParent": False,
+                                },
+                            ],
+                        },
+                        {
+                            "path": "^dir/CMakeLists\\.txt$",
+                            "isGenerated": None,
+                            "fileSetName": None,
+                            "sourceGroupName": "",
+                            "compileGroupLanguage": None,
+                            "backtrace": [
+                                {
+                                    "file": "^CMakeLists\\.txt$",
+                                    "line": None,
+                                    "command": None,
+                                    "hasParent": False,
+                                },
+                            ],
+                        },
+                        {
+                            "path": "^dir/dir/CMakeLists\\.txt$",
+                            "isGenerated": None,
+                            "fileSetName": None,
+                            "sourceGroupName": "",
+                            "compileGroupLanguage": None,
+                            "backtrace": [
+                                {
+                                    "file": "^CMakeLists\\.txt$",
+                                    "line": None,
+                                    "command": None,
+                                    "hasParent": False,
+                                },
+                            ],
+                        },
+                        {
+                            "path": "^fileset/CMakeLists\\.txt$",
+                            "isGenerated": None,
+                            "fileSetName": None,
+                            "sourceGroupName": "",
+                            "compileGroupLanguage": None,
+                            "backtrace": [
+                                {
+                                    "file": "^CMakeLists\\.txt$",
+                                    "line": None,
+                                    "command": None,
+                                    "hasParent": False,
+                                },
+                            ],
+                        },
+                        {
+                            "path": "^imported/CMakeLists\\.txt$",
+                            "isGenerated": None,
+                            "fileSetName": None,
+                            "sourceGroupName": "",
+                            "compileGroupLanguage": None,
+                            "backtrace": [
+                                {
+                                    "file": "^CMakeLists\\.txt$",
+                                    "line": None,
+                                    "command": None,
+                                    "hasParent": False,
+                                },
+                            ],
+                        },
+                        {
+                            "path": "^include_test\\.cmake$",
+                            "isGenerated": None,
+                            "fileSetName": None,
+                            "sourceGroupName": "",
+                            "compileGroupLanguage": None,
+                            "backtrace": [
+                                {
+                                    "file": "^CMakeLists\\.txt$",
+                                    "line": None,
+                                    "command": None,
+                                    "hasParent": False,
+                                },
+                            ],
+                        },
+                        {
+                            "path": "^interface/CMakeLists\\.txt$",
+                            "isGenerated": None,
+                            "fileSetName": None,
+                            "sourceGroupName": "",
+                            "compileGroupLanguage": None,
+                            "backtrace": [
+                                {
+                                    "file": "^CMakeLists\\.txt$",
+                                    "line": None,
+                                    "command": None,
+                                    "hasParent": False,
+                                },
+                            ],
+                        },
+                        {
+                            "path": "^object/CMakeLists\\.txt$",
+                            "isGenerated": None,
+                            "fileSetName": None,
+                            "sourceGroupName": "",
+                            "compileGroupLanguage": None,
+                            "backtrace": [
+                                {
+                                    "file": "^CMakeLists\\.txt$",
+                                    "line": None,
+                                    "command": None,
+                                    "hasParent": False,
+                                },
+                            ],
+                        },
+                        {
+                            "path": "^subdir/CMakeLists\\.txt$",
+                            "isGenerated": None,
+                            "fileSetName": None,
+                            "sourceGroupName": "",
+                            "compileGroupLanguage": None,
+                            "backtrace": [
+                                {
+                                    "file": "^CMakeLists\\.txt$",
+                                    "line": None,
+                                    "command": None,
+                                    "hasParent": False,
+                                },
+                            ],
+                        },
+                        {
                             "path": "^.*/Tests/RunCMake/FileAPI/codemodel-v2-build/CMakeFiles/([0-9a-f]+/)?generate\\.stamp\\.rule$",
                             "isGenerated": True,
                             "fileSetName": None,
@@ -867,6 +1124,25 @@
                     ]
                     e["sourceGroups"] = [
                         {
+                            "name": "",
+                            "sourcePaths": [
+                                "^CMakeLists\\.txt$",
+                                "^alias/CMakeLists\\.txt$",
+                                "^codemodel-v2\\.cmake$",
+                                "^custom/CMakeLists\\.txt$",
+                                "^cxx/CMakeLists\\.txt$",
+                                "^framework/CMakeLists\\.txt$",
+                                "^dir/CMakeLists\\.txt$",
+                                "^dir/dir/CMakeLists\\.txt$",
+                                "^fileset/CMakeLists\\.txt$",
+                                "^imported/CMakeLists\\.txt$",
+                                "^include_test\\.cmake$",
+                                "^interface/CMakeLists\\.txt$",
+                                "^object/CMakeLists\\.txt$",
+                                "^subdir/CMakeLists\\.txt$",
+                            ],
+                        },
+                        {
                             "name": "CMake Rules",
                             "sourcePaths": [
                                 "^.*/Tests/RunCMake/FileAPI/codemodel-v2-build/CMakeFiles/([0-9a-f]+/)?generate\\.stamp\\.rule$",
@@ -930,6 +1206,7 @@
         read_codemodel_json_data("projects/interface.json"),
         read_codemodel_json_data("projects/custom.json"),
         read_codemodel_json_data("projects/external.json"),
+        read_codemodel_json_data("projects/framework.json"),
     ]
 
     if matches(g["name"], "^Visual Studio "):
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/directories/framework.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/directories/framework.json
new file mode 100644
index 0000000..3affc02
--- /dev/null
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/directories/framework.json
@@ -0,0 +1,17 @@
+{
+    "source": "^framework$",
+    "build": "^framework$",
+    "parentSource": "^\\.$",
+    "childSources": null,
+    "targetIds": [
+        "^ALL_BUILD::@217a96c3a62328a73ef4$",
+        "^ZERO_CHECK::@217a96c3a62328a73ef4$",
+        "^shared_framework::@217a96c3a62328a73ef4$",
+        "^static_framework::@217a96c3a62328a73ef4$",
+        "^exe_framework::@217a96c3a62328a73ef4$"
+    ],
+    "projectName": "Framework",
+    "minimumCMakeVersion": "3.13",
+    "hasInstallRule": null,
+    "installers": []
+}
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/directories/top.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/directories/top.json
index aed07e2..a35d5e2 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/directories/top.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/directories/top.json
@@ -12,7 +12,8 @@
         "^.*/Tests/RunCMake/FileAPIExternalSource$",
         "^dir$",
         "^fileset$",
-        "^subdir$"
+        "^subdir$",
+        "^framework$"
     ],
     "targetIds": [
         "^ALL_BUILD::@6890427a1f51a3e7e1df$",
@@ -50,7 +51,7 @@
             "backtrace": [
                 {
                     "file": "^codemodel-v2\\.cmake$",
-                    "line": 42,
+                    "line": 43,
                     "command": "install",
                     "hasParent": true
                 },
@@ -95,7 +96,7 @@
             "backtrace": [
                 {
                     "file": "^codemodel-v2\\.cmake$",
-                    "line": 45,
+                    "line": 46,
                     "command": "install",
                     "hasParent": true
                 },
@@ -143,7 +144,7 @@
             "backtrace": [
                 {
                     "file": "^codemodel-v2\\.cmake$",
-                    "line": 45,
+                    "line": 46,
                     "command": "install",
                     "hasParent": true
                 },
@@ -188,7 +189,7 @@
             "backtrace": [
                 {
                     "file": "^codemodel-v2\\.cmake$",
-                    "line": 45,
+                    "line": 46,
                     "command": "install",
                     "hasParent": true
                 },
@@ -232,7 +233,7 @@
             "backtrace": [
                 {
                     "file": "^codemodel-v2\\.cmake$",
-                    "line": 45,
+                    "line": 46,
                     "command": "install",
                     "hasParent": true
                 },
@@ -276,7 +277,7 @@
             "backtrace": [
                 {
                     "file": "^codemodel-v2\\.cmake$",
-                    "line": 50,
+                    "line": 51,
                     "command": "install",
                     "hasParent": true
                 },
@@ -323,7 +324,7 @@
             "backtrace": [
                 {
                     "file": "^codemodel-v2\\.cmake$",
-                    "line": 52,
+                    "line": 53,
                     "command": "install",
                     "hasParent": true
                 },
@@ -368,7 +369,7 @@
             "backtrace": [
                 {
                     "file": "^codemodel-v2\\.cmake$",
-                    "line": 53,
+                    "line": 54,
                     "command": "install",
                     "hasParent": true
                 },
@@ -417,7 +418,7 @@
             "backtrace": [
                 {
                     "file": "^codemodel-v2\\.cmake$",
-                    "line": 54,
+                    "line": 55,
                     "command": "install",
                     "hasParent": true
                 },
@@ -469,7 +470,7 @@
             "backtrace": [
                 {
                     "file": "^codemodel-v2\\.cmake$",
-                    "line": 55,
+                    "line": 56,
                     "command": "install",
                     "hasParent": true
                 },
@@ -518,7 +519,7 @@
             "backtrace": [
                 {
                     "file": "^codemodel-v2\\.cmake$",
-                    "line": 56,
+                    "line": 57,
                     "command": "install",
                     "hasParent": true
                 },
@@ -560,7 +561,7 @@
             "backtrace": [
                 {
                     "file": "^codemodel-v2\\.cmake$",
-                    "line": 57,
+                    "line": 58,
                     "command": "install",
                     "hasParent": true
                 },
@@ -602,7 +603,7 @@
             "backtrace": [
                 {
                     "file": "^codemodel-v2\\.cmake$",
-                    "line": 58,
+                    "line": 59,
                     "command": "install",
                     "hasParent": true
                 },
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/projects/codemodel-v2.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/projects/codemodel-v2.json
index 151c0a8..8d2712d 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/projects/codemodel-v2.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/projects/codemodel-v2.json
@@ -8,7 +8,8 @@
         "Imported",
         "Interface",
         "Object",
-        "External"
+        "External",
+        "Framework"
     ],
     "directorySources": [
         "^\\.$",
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/projects/framework.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/projects/framework.json
new file mode 100644
index 0000000..259ead1
--- /dev/null
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/projects/framework.json
@@ -0,0 +1,15 @@
+{
+    "name": "Framework",
+    "parentName": "codemodel-v2",
+    "childNames": null,
+    "directorySources": [
+        "^framework$"
+    ],
+    "targetIds": [
+        "^ALL_BUILD::@217a96c3a62328a73ef4$",
+        "^ZERO_CHECK::@217a96c3a62328a73ef4$",
+        "^shared_framework::@217a96c3a62328a73ef4$",
+        "^static_framework::@217a96c3a62328a73ef4$",
+        "^exe_framework::@217a96c3a62328a73ef4$"
+    ]
+}
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/all_build_framework.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/all_build_framework.json
new file mode 100644
index 0000000..a4d806a
--- /dev/null
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/all_build_framework.json
@@ -0,0 +1,90 @@
+{
+    "name": "ALL_BUILD",
+    "id": "^ALL_BUILD::@217a96c3a62328a73ef4$",
+    "directorySource": "^framework$",
+    "projectName": "Framework",
+    "type": "UTILITY",
+    "isGeneratorProvided": true,
+    "fileSets": null,
+    "sources": [
+        {
+            "path": "^.*/Tests/RunCMake/FileAPI/codemodel-v2-build/framework/CMakeFiles/ALL_BUILD$",
+            "isGenerated": true,
+            "fileSetName": null,
+            "sourceGroupName": "",
+            "compileGroupLanguage": null,
+            "backtrace": [
+                {
+                    "file": "^framework/CMakeLists\\.txt$",
+                    "line": null,
+                    "command": null,
+                    "hasParent": false
+                }
+            ]
+        },
+        {
+            "path": "^.*/Tests/RunCMake/FileAPI/codemodel-v2-build/framework/CMakeFiles/ALL_BUILD\\.rule$",
+            "isGenerated": true,
+            "fileSetName": null,
+            "sourceGroupName": "CMake Rules",
+            "compileGroupLanguage": null,
+            "backtrace": [
+                {
+                    "file": "^framework/CMakeLists\\.txt$",
+                    "line": null,
+                    "command": null,
+                    "hasParent": false
+                }
+            ]
+        }
+    ],
+    "sourceGroups": [
+        {
+            "name": "",
+            "sourcePaths": [
+                "^.*/Tests/RunCMake/FileAPI/codemodel-v2-build/framework/CMakeFiles/ALL_BUILD$"
+            ]
+        },
+        {
+            "name": "CMake Rules",
+            "sourcePaths": [
+                "^.*/Tests/RunCMake/FileAPI/codemodel-v2-build/framework/CMakeFiles/ALL_BUILD\\.rule$"
+            ]
+        }
+    ],
+    "compileGroups": null,
+    "backtrace": [
+        {
+            "file": "^framework/CMakeLists\\.txt$",
+            "line": null,
+            "command": null,
+            "hasParent": false
+        }
+    ],
+    "folder": null,
+    "nameOnDisk": null,
+    "artifacts": null,
+    "build": "^framework$",
+    "source": "^framework$",
+    "install": null,
+    "link": null,
+    "archive": null,
+    "dependencies": [
+        {
+            "id": "^ZERO_CHECK::@217a96c3a62328a73ef4$",
+            "backtrace": null
+        },
+        {
+            "id": "^shared_framework::@217a96c3a62328a73ef4$",
+            "backtrace": null
+        },
+        {
+            "id": "^static_framework::@217a96c3a62328a73ef4$",
+            "backtrace": null
+        },
+        {
+            "id": "^exe_framework::@217a96c3a62328a73ef4$",
+            "backtrace": null
+        }
+    ]
+}
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/all_build_top.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/all_build_top.json
index 46495ac..9d0007f 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/all_build_top.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/all_build_top.json
@@ -201,6 +201,18 @@
         {
             "id": "^c_headers_2::@6b8db101d64c125f29fe$",
             "backtrace": null
+        },
+        {
+            "id": "^static_framework::@217a96c3a62328a73ef4$",
+            "backtrace": null
+        },
+        {
+            "id": "^shared_framework::@217a96c3a62328a73ef4$",
+            "backtrace": null
+        },
+        {
+            "id": "^exe_framework::@217a96c3a62328a73ef4$",
+            "backtrace": null
         }
     ]
 }
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/apple_exe_framework.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/apple_exe_framework.json
new file mode 100644
index 0000000..6d320f4
--- /dev/null
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/apple_exe_framework.json
@@ -0,0 +1,79 @@
+{
+    "compileGroups":
+    [
+        {
+            "language": "CXX",
+            "sourcePaths": [
+                "^empty\\.cxx$"
+            ],
+            "includes": null,
+            "defines": null,
+            "frameworks":
+            [
+                {
+                    "isSystem": null,
+                    "path": "^.*/framework/((Debug|Release|RelWithDebInfo|MinSizeRel)/)?static_framework.framework",
+                    "backtrace": null
+                },
+                {
+                    "isSystem": true,
+                    "path": "^.+/framework/((Debug|Release|RelWithDebInfo|MinSizeRel)/)?shared_framework.framework",
+                    "backtrace": null
+                },
+                {
+                    "isSystem": true,
+                    "path": "/usr/Frameworks/Foo.framework",
+                    "backtrace": null
+                }
+            ],
+            "compileCommandFragments": []
+        }
+    ],
+    "link": {
+        "language": "CXX",
+        "lto": null,
+        "commandFragments": [
+            {
+                "fragment": "-iframework .+/framework(/(Debug|Release|RelWithDebInfo|MinSizeRel))?\"? -iframework /usr/Frameworks$",
+                "role": "frameworkPath",
+                "backtrace": null
+            },
+            {
+                "fragment": ".*static_framework\\.framework/.+/static_framework",
+                "role": "libraries",
+                "backtrace": [
+                    {
+                        "file": "^framework/CMakeLists\\.txt$",
+                        "line": 17,
+                        "command": "target_link_libraries",
+                        "hasParent": true
+                    },
+                    {
+                        "file": "^framework/CMakeLists\\.txt$",
+                        "line": null,
+                        "command": null,
+                        "hasParent": false
+                    }
+                ]
+            },
+            {
+                "fragment": ".*shared_framework\\.framework/.+/shared_framework",
+                "role": "libraries",
+                "backtrace": [
+                    {
+                        "file": "^framework/CMakeLists\\.txt$",
+                        "line": 17,
+                        "command": "target_link_libraries",
+                        "hasParent": true
+                    },
+                    {
+                        "file": "^framework/CMakeLists\\.txt$",
+                        "line": null,
+                        "command": null,
+                        "hasParent": false
+                    }
+                ]
+            }
+        ]
+    }
+}
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/apple_shared_framework.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/apple_shared_framework.json
new file mode 100644
index 0000000..31104cf
--- /dev/null
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/apple_shared_framework.json
@@ -0,0 +1,9 @@
+{
+    "nameOnDisk": "^shared_framework\\.framework/shared_framework$",
+    "artifacts": [
+        {
+            "path": "^framework/((Debug|Release|RelWithDebInfo|MinSizeRel)/)?shared_framework\\.framework/shared_framework$",
+            "_dllExtra": false
+        }
+    ]
+}
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/apple_static_framework.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/apple_static_framework.json
new file mode 100644
index 0000000..25ffd1a
--- /dev/null
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/apple_static_framework.json
@@ -0,0 +1,9 @@
+{
+    "nameOnDisk": "^static_framework\\.framework/static_framework$",
+    "artifacts": [
+        {
+            "path": "^framework/((Debug|Release|RelWithDebInfo|MinSizeRel)/)?static_framework\\.framework/static_framework$",
+            "_dllExtra": false
+        }
+    ]
+}
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_alias_exe.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_alias_exe.json
index a27d328..74b3287 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_alias_exe.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_alias_exe.json
@@ -45,6 +45,7 @@
             ],
             "includes": null,
             "defines": null,
+            "frameworks": null,
             "compileCommandFragments": null
         }
     ],
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_exe.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_exe.json
index 7cfc0f2..c6ff37a 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_exe.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_exe.json
@@ -57,6 +57,7 @@
             ],
             "includes": null,
             "defines": null,
+            "frameworks": null,
             "compileCommandFragments": null
         }
     ],
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_headers_1.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_headers_1.json
index 715514d..f6cfa9c 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_headers_1.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_headers_1.json
@@ -199,6 +199,7 @@
           ]
         }
       ],
+      "frameworks": null,
       "defines": null,
       "compileCommandFragments": null
     }
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_headers_2.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_headers_2.json
index 4757a9c..591ba4f 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_headers_2.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_headers_2.json
@@ -51,6 +51,7 @@
         "^fileset/empty\\.c$"
       ],
       "includes": null,
+      "frameworks": null,
       "defines": null,
       "compileCommandFragments": null
     }
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_lib.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_lib.json
index 2bfc63f..dc74fdf 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_lib.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_lib.json
@@ -56,6 +56,7 @@
                 "^empty\\.c$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": null,
             "compileCommandFragments": null
         }
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_object_exe.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_object_exe.json
index 6342191..3034e98 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_object_exe.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_object_exe.json
@@ -71,6 +71,7 @@
                 "^empty\\.c$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": null,
             "compileCommandFragments": null
         }
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_object_lib.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_object_lib.json
index 3e1b03b..ec0fdc6 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_object_lib.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_object_lib.json
@@ -44,6 +44,7 @@
                 "^empty\\.c$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": null,
             "compileCommandFragments": null
         }
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_shared_exe.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_shared_exe.json
index f7a8db4..5e92840 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_shared_exe.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_shared_exe.json
@@ -56,6 +56,7 @@
                 "^empty\\.c$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": null,
   "compileCommandFragments": null
         }
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_shared_lib.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_shared_lib.json
index 9066053..85b5108 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_shared_lib.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_shared_lib.json
@@ -56,6 +56,7 @@
                 "^empty\\.c$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": [
                 {
                     "define": "c_shared_lib_EXPORTS",
@@ -117,7 +118,7 @@
                 "backtrace": [
                     {
                         "file": "^codemodel-v2\\.cmake$",
-                        "line": 45,
+                        "line": 46,
                         "command": "install",
                         "hasParent": true
                     },
@@ -147,7 +148,7 @@
                 "backtrace": [
                     {
                         "file": "^codemodel-v2\\.cmake$",
-                        "line": 45,
+                        "line": 46,
                         "command": "install",
                         "hasParent": true
                     },
@@ -177,7 +178,7 @@
                 "backtrace": [
                     {
                         "file": "^codemodel-v2\\.cmake$",
-                        "line": 50,
+                        "line": 51,
                         "command": "install",
                         "hasParent": true
                     },
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_static_exe.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_static_exe.json
index 46c5bfe..df43319 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_static_exe.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_static_exe.json
@@ -56,6 +56,7 @@
                 "^empty\\.c$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": null,
             "compileCommandFragments": null
         }
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_static_lib.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_static_lib.json
index df28479..6a51295 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_static_lib.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_static_lib.json
@@ -56,6 +56,7 @@
                 "^empty\\.c$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": null,
             "compileCommandFragments": null
         }
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_subdir.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_subdir.json
index 4fa62e3..362caf9 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_subdir.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/c_subdir.json
@@ -63,6 +63,7 @@
                 ]
               }
             ],
+            "frameworks": null,
             "defines": [
                 {
                     "define": "SUBDIR",
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/custom_exe.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/custom_exe.json
index 8d52ab8..449c261 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/custom_exe.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/custom_exe.json
@@ -44,6 +44,7 @@
                 "^empty\\.c$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": null,
             "compileCommandFragments": null
         }
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_alias_exe.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_alias_exe.json
index b27fc5b..a2d3ca4 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_alias_exe.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_alias_exe.json
@@ -44,6 +44,7 @@
                 "^empty\\.cxx$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": null,
             "compileCommandFragments": null
         }
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe.json
index 12b2551..73f9346 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe.json
@@ -44,6 +44,7 @@
                 "^empty\\.cxx$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": null,
             "precompileHeaders": [
               {
@@ -138,7 +139,7 @@
                 "backtrace": [
                     {
                         "file": "^codemodel-v2\\.cmake$",
-                        "line": 42,
+                        "line": 43,
                         "command": "install",
                         "hasParent": true
                     },
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_precompileheader.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_precompileheader.json
index 3251777..ac6a1c0 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_precompileheader.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_precompileheader.json
@@ -6,6 +6,7 @@
         ".*cmake_pch(_[^.]+)?(\\.hxx)?\\.cxx$"
       ],
       "includes": null,
+      "frameworks": null,
       "defines": null,
       "precompileHeaders": [
         {
@@ -52,6 +53,7 @@
         "^empty\\.cxx$"
       ],
       "includes": null,
+      "frameworks": null,
       "defines": null,
       "precompileHeaders": [
         {
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_precompileheader_2arch.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_precompileheader_2arch.json
index 0ac40c2..311fe7a 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_precompileheader_2arch.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_precompileheader_2arch.json
@@ -6,6 +6,7 @@
         ".*cmake_pch(_[^.]+)?(\\.hxx)?\\.cxx$"
       ],
       "includes": null,
+      "frameworks": null,
       "defines": null,
       "precompileHeaders": [
         {
@@ -52,6 +53,7 @@
         ".*cmake_pch(_[^.]+)?(\\.hxx)?\\.cxx$"
       ],
       "includes": null,
+      "frameworks": null,
       "defines": null,
       "precompileHeaders": [
         {
@@ -98,6 +100,7 @@
         "^empty\\.cxx$"
       ],
       "includes": null,
+      "frameworks": null,
       "defines": null,
       "precompileHeaders": [
         {
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_precompileheader_multigen.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_precompileheader_multigen.json
index 86168f1..adf979e 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_precompileheader_multigen.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_precompileheader_multigen.json
@@ -6,6 +6,7 @@
         ".*cmake_pch(_[^.]+)?(\\.hxx)?\\.cxx$"
       ],
       "includes": null,
+      "frameworks": null,
       "defines": null,
       "precompileHeaders": [
         {
@@ -52,6 +53,7 @@
         "^empty\\.cxx$"
       ],
       "includes": null,
+      "frameworks": null,
       "defines": null,
       "precompileHeaders": [
         {
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_lib.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_lib.json
index f665004..725cad9 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_lib.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_lib.json
@@ -44,6 +44,7 @@
                 "^empty\\.cxx$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": null,
             "compileCommandFragments": null
         }
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_object_exe.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_object_exe.json
index 68c5dcc..6655215 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_object_exe.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_object_exe.json
@@ -71,6 +71,7 @@
                 "^empty\\.cxx$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": null,
             "compileCommandFragments": null
         }
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_object_lib.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_object_lib.json
index 0438a49..cc2deeb 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_object_lib.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_object_lib.json
@@ -44,6 +44,7 @@
                 "^empty\\.cxx$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": null,
             "compileCommandFragments": null
         }
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_shared_exe.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_shared_exe.json
index bb9989e..1858df7 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_shared_exe.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_shared_exe.json
@@ -44,6 +44,7 @@
                 "^empty\\.cxx$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": null,
             "compileCommandFragments": null
         }
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_shared_lib.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_shared_lib.json
index d6d59a4..c92e573 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_shared_lib.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_shared_lib.json
@@ -44,6 +44,7 @@
                 "^empty\\.cxx$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": [
                 {
                     "define": "cxx_shared_lib_EXPORTS",
@@ -93,7 +94,7 @@
                 "backtrace": [
                     {
                         "file": "^codemodel-v2\\.cmake$",
-                        "line": 45,
+                        "line": 46,
                         "command": "install",
                         "hasParent": true
                     },
@@ -123,7 +124,7 @@
                 "backtrace": [
                     {
                         "file": "^codemodel-v2\\.cmake$",
-                        "line": 45,
+                        "line": 46,
                         "command": "install",
                         "hasParent": true
                     },
@@ -153,7 +154,7 @@
                 "backtrace": [
                     {
                         "file": "^codemodel-v2\\.cmake$",
-                        "line": 50,
+                        "line": 51,
                         "command": "install",
                         "hasParent": true
                     },
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_standard_compile_feature_exe.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_standard_compile_feature_exe.json
index a6bacf7..5b07d5a 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_standard_compile_feature_exe.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_standard_compile_feature_exe.json
@@ -64,6 +64,7 @@
                 "^empty\\.cxx$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": null,
             "compileCommandFragments": null
         }
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_standard_exe.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_standard_exe.json
index fe884e0..d9554f1 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_standard_exe.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_standard_exe.json
@@ -64,6 +64,7 @@
                 "^empty\\.cxx$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": null,
             "compileCommandFragments": null
         }
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_static_exe.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_static_exe.json
index d904bd9..001eb8d 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_static_exe.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_static_exe.json
@@ -44,6 +44,7 @@
                 "^empty\\.cxx$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": null,
             "compileCommandFragments": null
         }
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_static_lib.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_static_lib.json
index bced68a..38790dd 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_static_lib.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_static_lib.json
@@ -44,6 +44,7 @@
                 "^empty\\.cxx$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": null,
             "compileCommandFragments": null
         }
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/exe_framework.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/exe_framework.json
new file mode 100644
index 0000000..d5d6522
--- /dev/null
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/exe_framework.json
@@ -0,0 +1,164 @@
+{
+    "name": "exe_framework",
+    "id": "^exe_framework::@217a96c3a62328a73ef4$",
+    "directorySource": "^framework$",
+    "projectName": "Framework",
+    "type": "EXECUTABLE",
+    "isGeneratorProvided": null,
+    "fileSets": null,
+    "sources": [
+        {
+            "path": "^empty\\.cxx$",
+            "isGenerated": null,
+            "fileSetName": null,
+            "sourceGroupName": "Source Files",
+            "compileGroupLanguage": "CXX",
+            "backtrace": [
+                {
+                    "file": "^framework/CMakeLists\\.txt$",
+                    "line": 16,
+                    "command": "add_executable",
+                    "hasParent": true
+                },
+                {
+                    "file": "^framework/CMakeLists\\.txt$",
+                    "line": null,
+                    "command": null,
+                    "hasParent": false
+                }
+            ]
+        }
+    ],
+    "sourceGroups": [
+        {
+            "name": "Source Files",
+            "sourcePaths": [
+                "^empty\\.cxx$"
+            ]
+        }
+    ],
+    "compileGroups": [
+        {
+            "language": "CXX",
+            "sourcePaths": [
+                "^empty\\.cxx$"
+            ],
+            "includes": null,
+            "frameworks": null,
+            "defines": null,
+            "compileCommandFragments": null
+        }
+    ],
+    "backtrace": [
+        {
+            "file": "^framework/CMakeLists\\.txt$",
+            "line": 16,
+            "command": "add_executable",
+            "hasParent": true
+        },
+        {
+            "file": "^framework/CMakeLists\\.txt$",
+            "line": null,
+            "command": null,
+            "hasParent": false
+        }
+    ],
+    "folder": null,
+    "nameOnDisk": "^exe_framework(\\.exe)?$",
+    "artifacts": [
+        {
+            "path": "^framework/((Debug|Release|RelWithDebInfo|MinSizeRel)/)?exe_framework(\\.exe)?$",
+            "_dllExtra": false
+        },
+        {
+            "path": "^framework/((Debug|Release|RelWithDebInfo|MinSizeRel)/)?exe_framework\\.pdb$",
+            "_dllExtra": true
+        }
+    ],
+    "build": "^framework$",
+    "source": "^framework$",
+    "install": null,
+    "link": {
+        "language": "CXX",
+        "lto": null,
+        "commandFragments": [
+            {
+                "fragment": ".*static_framework.*",
+                "role": "libraries",
+                "backtrace": [
+                    {
+                        "file": "^framework/CMakeLists\\.txt$",
+                        "line": 17,
+                        "command": "target_link_libraries",
+                        "hasParent": true
+                    },
+                    {
+                        "file": "^framework/CMakeLists\\.txt$",
+                        "line": null,
+                        "command": null,
+                        "hasParent": false
+                    }
+                ]
+            },
+            {
+                "fragment": ".*shared_framework.*",
+                "role": "libraries",
+                "backtrace": [
+                    {
+                        "file": "^framework/CMakeLists\\.txt$",
+                        "line": 17,
+                        "command": "target_link_libraries",
+                        "hasParent": true
+                    },
+                    {
+                        "file": "^framework/CMakeLists\\.txt$",
+                        "line": null,
+                        "command": null,
+                        "hasParent": false
+                    }
+                ]
+            }
+        ]
+    },
+    "archive": null,
+    "dependencies": [
+        {
+            "id": "^static_framework::@217a96c3a62328a73ef4$",
+            "backtrace": [
+                {
+                    "file": "^framework/CMakeLists\\.txt$",
+                    "line": 17,
+                    "command": "target_link_libraries",
+                    "hasParent": true
+                },
+                {
+                    "file": "^framework/CMakeLists\\.txt$",
+                    "line": null,
+                    "command": null,
+                    "hasParent": false
+                }
+            ]
+        },
+        {
+            "id": "^shared_framework::@217a96c3a62328a73ef4$",
+            "backtrace": [
+                {
+                    "file": "^framework/CMakeLists\\.txt$",
+                    "line": 17,
+                    "command": "target_link_libraries",
+                    "hasParent": true
+                },
+                {
+                    "file": "^framework/CMakeLists\\.txt$",
+                    "line": null,
+                    "command": null,
+                    "hasParent": false
+                }
+            ]
+        },
+        {
+            "id": "^ZERO_CHECK::@217a96c3a62328a73ef4$",
+            "backtrace": null
+        }
+    ]
+}
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/generated_exe.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/generated_exe.json
index 4b69682..f1ef8dd 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/generated_exe.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/generated_exe.json
@@ -108,6 +108,7 @@
                     ]
                 }
             ],
+            "frameworks": null,
             "defines": [
                 {
                     "define": "EMPTY_C=1",
@@ -223,6 +224,7 @@
                     ]
                 }
             ],
+            "frameworks": null,
             "defines": [
                 {
                     "define": "GENERATED_EXE=1",
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/interface_exe.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/interface_exe.json
index c0c3e79..521e464 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/interface_exe.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/interface_exe.json
@@ -68,6 +68,7 @@
                 "^empty\\.c$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": [
                 {
                     "define": "interface_exe_EXPORTS",
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/link_imported_exe.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/link_imported_exe.json
index 45fb0a5..531d8dc 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/link_imported_exe.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/link_imported_exe.json
@@ -44,6 +44,7 @@
                 "^empty\\.c$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": null,
             "compileCommandFragments": null
         }
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/link_imported_interface_exe.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/link_imported_interface_exe.json
index 74c179c..691edff 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/link_imported_interface_exe.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/link_imported_interface_exe.json
@@ -44,6 +44,7 @@
                 "^empty\\.c$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": null,
             "compileCommandFragments": null
         }
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/link_imported_object_exe.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/link_imported_object_exe.json
index 6771747..555accc 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/link_imported_object_exe.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/link_imported_object_exe.json
@@ -44,6 +44,7 @@
                 "^empty\\.c$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": null,
             "compileCommandFragments": null
         }
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/link_imported_shared_exe.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/link_imported_shared_exe.json
index 659e3fb..ead2362 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/link_imported_shared_exe.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/link_imported_shared_exe.json
@@ -44,6 +44,7 @@
                 "^empty\\.c$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": null,
             "compileCommandFragments": null
         }
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/link_imported_static_exe.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/link_imported_static_exe.json
index 7bdaffb..26dc9db 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/link_imported_static_exe.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/link_imported_static_exe.json
@@ -44,6 +44,7 @@
                 "^empty\\.c$"
             ],
             "includes": null,
+            "frameworks": null,
             "defines": null,
             "compileCommandFragments": null
         }
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/shared_framework.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/shared_framework.json
new file mode 100644
index 0000000..41b5605
--- /dev/null
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/shared_framework.json
@@ -0,0 +1,102 @@
+{
+    "name": "shared_framework",
+    "id": "^shared_framework::@217a96c3a62328a73ef4$",
+    "directorySource": "^framework$",
+    "projectName": "Framework",
+    "type": "SHARED_LIBRARY",
+    "isGeneratorProvided": null,
+    "fileSets": null,
+    "sources": [
+        {
+            "path": "^empty\\.cxx$",
+            "isGenerated": null,
+            "fileSetName": null,
+            "sourceGroupName": "Source Files",
+            "compileGroupLanguage": "CXX",
+            "backtrace": [
+                {
+                    "file": "^framework/CMakeLists\\.txt$",
+                    "line": 7,
+                    "command": "add_library",
+                    "hasParent": true
+                },
+                {
+                    "file": "^framework/CMakeLists\\.txt$",
+                    "line": null,
+                    "command": null,
+                    "hasParent": false
+                }
+            ]
+        }
+    ],
+    "sourceGroups": [
+        {
+            "name": "Source Files",
+            "sourcePaths": [
+                "^empty\\.cxx$"
+            ]
+        }
+    ],
+    "compileGroups": [
+        {
+            "language": "CXX",
+            "sourcePaths": [
+                "^empty\\.cxx$"
+            ],
+            "includes": null,
+            "frameworks": null,
+            "defines": [
+                {
+                    "define": "shared_framework_EXPORTS",
+                    "backtrace": null
+                }
+            ],
+            "compileCommandFragments": null
+        }
+    ],
+    "backtrace": [
+        {
+            "file": "^framework/CMakeLists\\.txt$",
+            "line": 7,
+            "command": "add_library",
+            "hasParent": true
+        },
+        {
+            "file": "^framework/CMakeLists\\.txt$",
+            "line": null,
+            "command": null,
+            "hasParent": false
+        }
+    ],
+    "folder": null,
+    "nameOnDisk": "^(lib|cyg|msys-)?shared_framework\\.(so|dylib|dll)$",
+    "artifacts": [
+        {
+            "path": "^framework/((Debug|Release|RelWithDebInfo|MinSizeRel)/)?(lib|cyg|msys-)?shared_framework\\.(so|dylib|dll)$",
+            "_dllExtra": false
+        },
+        {
+            "path": "^framework/((Debug|Release|RelWithDebInfo|MinSizeRel)/)?(lib)?shared_framework\\.(dll\\.a|lib)$",
+            "_dllExtra": true
+        },
+        {
+            "path": "^framework/((Debug|Release|RelWithDebInfo|MinSizeRel)/)?(lib|cyg|msys-)?shared_framework\\.pdb$",
+            "_dllExtra": true
+        }
+    ],
+    "build": "^framework$",
+    "source": "^framework$",
+    "install": null,
+    "link": {
+        "language": "CXX",
+        "lto": null,
+        "commandFragments": null
+    },
+    "archive": null,
+    "dependencies": [
+        {
+            "id": "^ZERO_CHECK::@217a96c3a62328a73ef4$",
+            "backtrace": null
+        }
+    ]
+}
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/static_framework.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/static_framework.json
new file mode 100644
index 0000000..00dd11e
--- /dev/null
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/static_framework.json
@@ -0,0 +1,87 @@
+{
+    "name": "static_framework",
+    "id": "^static_framework::@217a96c3a62328a73ef4$",
+    "directorySource": "^framework$",
+    "projectName": "Framework",
+    "type": "STATIC_LIBRARY",
+    "isGeneratorProvided": null,
+    "fileSets": null,
+    "sources": [
+        {
+            "path": "^empty\\.cxx$",
+            "isGenerated": null,
+            "fileSetName": null,
+            "sourceGroupName": "Source Files",
+            "compileGroupLanguage": "CXX",
+            "backtrace": [
+                {
+                    "file": "^framework/CMakeLists\\.txt$",
+                    "line": 4,
+                    "command": "add_library",
+                    "hasParent": true
+                },
+                {
+                    "file": "^framework/CMakeLists\\.txt$",
+                    "line": null,
+                    "command": null,
+                    "hasParent": false
+                }
+            ]
+        }
+    ],
+    "sourceGroups": [
+        {
+            "name": "Source Files",
+            "sourcePaths": [
+                "^empty\\.cxx$"
+            ]
+        }
+    ],
+    "compileGroups": [
+        {
+            "language": "CXX",
+            "sourcePaths": [
+                "^empty\\.cxx$"
+            ],
+            "includes": null,
+            "frameworks": null,
+            "defines": null,
+            "compileCommandFragments": null
+        }
+    ],
+    "backtrace": [
+        {
+            "file": "^framework/CMakeLists\\.txt$",
+            "line": 4,
+            "command": "add_library",
+            "hasParent": true
+        },
+        {
+            "file": "^framework/CMakeLists\\.txt$",
+            "line": null,
+            "command": null,
+            "hasParent": false
+        }
+    ],
+    "folder": null,
+    "nameOnDisk": "^(lib)?static_framework\\.(a|lib)$",
+    "artifacts": [
+        {
+            "path": "^framework/((Debug|Release|RelWithDebInfo|MinSizeRel)/)?(lib)?static_framework\\.(a|lib)$",
+            "_dllExtra": false
+        }
+    ],
+    "build": "^framework$",
+    "source": "^framework$",
+    "install": null,
+    "link": null,
+    "archive": {
+        "lto": null
+    },
+    "dependencies": [
+        {
+            "id": "^ZERO_CHECK::@217a96c3a62328a73ef4$",
+            "backtrace": null
+        }
+    ]
+}
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/zero_check_framework.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/zero_check_framework.json
new file mode 100644
index 0000000..6206517
--- /dev/null
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/zero_check_framework.json
@@ -0,0 +1,73 @@
+{
+    "name": "ZERO_CHECK",
+    "id": "^ZERO_CHECK::@217a96c3a62328a73ef4$",
+    "directorySource": "^framework$",
+    "projectName": "Framework",
+    "type": "UTILITY",
+    "isGeneratorProvided": true,
+    "fileSets": null,
+    "sources": [
+        {
+            "path": "^.*/Tests/RunCMake/FileAPI/codemodel-v2-build/framework/CMakeFiles/ZERO_CHECK$",
+            "isGenerated": true,
+            "fileSetName": null,
+            "sourceGroupName": "",
+            "compileGroupLanguage": null,
+            "backtrace": [
+                {
+                    "file": "^framework/CMakeLists\\.txt$",
+                    "line": null,
+                    "command": null,
+                    "hasParent": false
+                }
+            ]
+        },
+        {
+            "path": "^.*/Tests/RunCMake/FileAPI/codemodel-v2-build/framework/CMakeFiles/ZERO_CHECK\\.rule$",
+            "isGenerated": true,
+            "fileSetName": null,
+            "sourceGroupName": "CMake Rules",
+            "compileGroupLanguage": null,
+            "backtrace": [
+                {
+                    "file": "^framework/CMakeLists\\.txt$",
+                    "line": null,
+                    "command": null,
+                    "hasParent": false
+                }
+            ]
+        }
+    ],
+    "sourceGroups": [
+        {
+            "name": "",
+            "sourcePaths": [
+                "^.*/Tests/RunCMake/FileAPI/codemodel-v2-build/framework/CMakeFiles/ZERO_CHECK$"
+            ]
+        },
+        {
+            "name": "CMake Rules",
+            "sourcePaths": [
+                "^.*/Tests/RunCMake/FileAPI/codemodel-v2-build/framework/CMakeFiles/ZERO_CHECK\\.rule$"
+            ]
+        }
+    ],
+    "compileGroups": null,
+    "backtrace": [
+        {
+            "file": "^framework/CMakeLists\\.txt$",
+            "line": null,
+            "command": null,
+            "hasParent": false
+        }
+    ],
+    "folder": null,
+    "nameOnDisk": null,
+    "artifacts": null,
+    "build": "^framework$",
+    "source": "^framework$",
+    "install": null,
+    "link": null,
+    "archive": null,
+    "dependencies": null
+}
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2.cmake b/Tests/RunCMake/FileAPI/codemodel-v2.cmake
index 09db216..5f4019d 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2.cmake
+++ b/Tests/RunCMake/FileAPI/codemodel-v2.cmake
@@ -26,6 +26,7 @@
 add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../FileAPIExternalSource" "${CMAKE_CURRENT_BINARY_DIR}/../FileAPIExternalBuild")
 add_subdirectory(dir)
 add_subdirectory(fileset)
+add_subdirectory(framework)
 
 set_property(TARGET c_shared_lib PROPERTY LIBRARY_OUTPUT_DIRECTORY lib)
 set_property(TARGET c_shared_lib PROPERTY RUNTIME_OUTPUT_DIRECTORY lib)
diff --git a/Tests/RunCMake/FileAPI/framework/CMakeLists.txt b/Tests/RunCMake/FileAPI/framework/CMakeLists.txt
new file mode 100644
index 0000000..d69efbb
--- /dev/null
+++ b/Tests/RunCMake/FileAPI/framework/CMakeLists.txt
@@ -0,0 +1,17 @@
+project(Framework)
+enable_language(CXX)
+
+add_library(static_framework STATIC ../empty.cxx)
+set_property(TARGET static_framework PROPERTY FRAMEWORK ON)
+
+add_library(shared_framework SHARED ../empty.cxx)
+set_property(TARGET shared_framework PROPERTY FRAMEWORK ON)
+set_property(TARGET shared_framework PROPERTY SYSTEM ON)
+
+add_library(import_framework SHARED IMPORTED)
+set_property(TARGET import_framework PROPERTY FRAMEWORK ON)
+set_property(TARGET import_framework PROPERTY IMPORTED_LOCATION /usr/Frameworks/Foo.framework/Foo)
+set_property(TARGET import_framework PROPERTY IMPORTED_IMPLIB /usr/Frameworks/Foo.framework/Foo.lib)
+
+add_executable(exe_framework ../empty.cxx)
+target_link_libraries(exe_framework PRIVATE static_framework shared_framework import_framework)
diff --git a/Tests/RunCMake/File_Archive/CMakeLists.txt b/Tests/RunCMake/File_Archive/CMakeLists.txt
index 2897109..93ee9df 100644
--- a/Tests/RunCMake/File_Archive/CMakeLists.txt
+++ b/Tests/RunCMake/File_Archive/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/File_Generate/CMP0070-NEW-check.cmake b/Tests/RunCMake/File_Generate/CMP0070-NEW-check.cmake
index 05ec26e..6183635 100644
--- a/Tests/RunCMake/File_Generate/CMP0070-NEW-check.cmake
+++ b/Tests/RunCMake/File_Generate/CMP0070-NEW-check.cmake
@@ -5,7 +5,7 @@
   if(EXISTS "${f}")
     file(READ "${f}" content)
     if(NOT content MATCHES "^relative-input-NEW[\r\n]*$")
-      string(APPEND RunCMake_TEST_FAILED "File\n  ${f}\ndoes not have expected content.\n")
+      string(APPEND RunCMake_TEST_FAILED "File\n  ${f}\n" "does not have expected content.\n")
     endif()
   else()
     string(APPEND RunCMake_TEST_FAILED "Missing\n  ${f}\n")
diff --git a/Tests/RunCMake/File_Generate/CMP0070-OLD-check.cmake b/Tests/RunCMake/File_Generate/CMP0070-OLD-check.cmake
index a71d822..0f0fc09 100644
--- a/Tests/RunCMake/File_Generate/CMP0070-OLD-check.cmake
+++ b/Tests/RunCMake/File_Generate/CMP0070-OLD-check.cmake
@@ -5,7 +5,7 @@
   if(EXISTS "${f}")
     file(READ "${f}" content)
     if(NOT content MATCHES "^relative-input-OLD[\r\n]*$")
-      string(APPEND RunCMake_TEST_FAILED "File\n  ${f}\ndoes not have expected content.\n")
+      string(APPEND RunCMake_TEST_FAILED "File\n  ${f}\n" "does not have expected content.\n")
     endif()
   else()
     string(APPEND RunCMake_TEST_FAILED "Missing\n  ${f}\n")
diff --git a/Tests/RunCMake/File_Generate/CMP0070-WARN-check.cmake b/Tests/RunCMake/File_Generate/CMP0070-WARN-check.cmake
index 1488df0..0ee4a82 100644
--- a/Tests/RunCMake/File_Generate/CMP0070-WARN-check.cmake
+++ b/Tests/RunCMake/File_Generate/CMP0070-WARN-check.cmake
@@ -5,7 +5,7 @@
   if(EXISTS "${f}")
     file(READ "${f}" content)
     if(NOT content MATCHES "^relative-input-WARN[\r\n]*$")
-      string(APPEND RunCMake_TEST_FAILED "File\n  ${f}\ndoes not have expected content.\n")
+      string(APPEND RunCMake_TEST_FAILED "File\n  ${f}\n" "does not have expected content.\n")
     endif()
   else()
     string(APPEND RunCMake_TEST_FAILED "Missing\n  ${f}\n")
diff --git a/Tests/RunCMake/File_Generate/CMakeLists.txt b/Tests/RunCMake/File_Generate/CMakeLists.txt
index 3178de5..eec672f 100644
--- a/Tests/RunCMake/File_Generate/CMakeLists.txt
+++ b/Tests/RunCMake/File_Generate/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 if(NOT TEST_FILE)
   set(TEST_FILE ${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/File_Generate/SourceProperty-CMP0070-NEW-stderr.txt b/Tests/RunCMake/File_Generate/SourceProperty-CMP0070-NEW-stderr.txt
index 2c385c4..b47a137 100644
--- a/Tests/RunCMake/File_Generate/SourceProperty-CMP0070-NEW-stderr.txt
+++ b/Tests/RunCMake/File_Generate/SourceProperty-CMP0070-NEW-stderr.txt
@@ -1,11 +1,11 @@
-^CMake Error at SourceProperty-CMP0070-NEW.cmake:[0-9]+ \(add_library\):
+^CMake Error at SourceProperty-CMP0070-NEW.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 .*\/relative-output-NEW\.c
 
-  Tried extensions \.c \.C.*
-Call Stack \(most recent call first\):
+  Tried extensions ([^
+]+
+)+Call Stack \(most recent call first\):
   CMakeLists.txt:[0-9]+ \(include\)
-
-
++
 CMake Generate step failed.  Build files cannot be regenerated correctly.$
diff --git a/Tests/RunCMake/File_Generate/SourceProperty-CMP0070-NEW.cmake b/Tests/RunCMake/File_Generate/SourceProperty-CMP0070-NEW.cmake
index d2b3e0c..16d5563 100644
--- a/Tests/RunCMake/File_Generate/SourceProperty-CMP0070-NEW.cmake
+++ b/Tests/RunCMake/File_Generate/SourceProperty-CMP0070-NEW.cmake
@@ -1,5 +1,5 @@
 enable_language(C)
-add_library(foo)
+add_library(foo empty.c)
 
 cmake_policy(SET CMP0070 NEW)
 file(GENERATE OUTPUT relative-output-NEW.c CONTENT "")
diff --git a/Tests/RunCMake/File_Generate/SourceProperty-CMP0070-OLD-stderr.txt b/Tests/RunCMake/File_Generate/SourceProperty-CMP0070-OLD-stderr.txt
index fcb53a7..39735d7 100644
--- a/Tests/RunCMake/File_Generate/SourceProperty-CMP0070-OLD-stderr.txt
+++ b/Tests/RunCMake/File_Generate/SourceProperty-CMP0070-OLD-stderr.txt
@@ -8,16 +8,15 @@
   behavior and not rely on setting a policy to OLD.
 Call Stack \(most recent call first\):
   CMakeLists.txt:[0-9]+ \(include\)
-
-
-CMake Error at SourceProperty-CMP0070-OLD.cmake:[0-9]+ \(add_library\):
++
+CMake Error at SourceProperty-CMP0070-OLD.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
 .*\/relative-output-OLD\.c
 
-  Tried extensions \.c \.C.*
-Call Stack \(most recent call first\):
+  Tried extensions ([^
+]+
+)+Call Stack \(most recent call first\):
   CMakeLists.txt:[0-9]+ \(include\)
-
-
++
 CMake Generate step failed.  Build files cannot be regenerated correctly.$
diff --git a/Tests/RunCMake/File_Generate/SourceProperty-CMP0070-OLD.cmake b/Tests/RunCMake/File_Generate/SourceProperty-CMP0070-OLD.cmake
index 48eae1e..4f566b0 100644
--- a/Tests/RunCMake/File_Generate/SourceProperty-CMP0070-OLD.cmake
+++ b/Tests/RunCMake/File_Generate/SourceProperty-CMP0070-OLD.cmake
@@ -1,5 +1,5 @@
 enable_language(C)
-add_library(foo)
+add_library(foo empty.c)
 
 cmake_policy(SET CMP0070 OLD)
 file(GENERATE OUTPUT relative-output-OLD.c CONTENT "")
diff --git a/Tests/RunCMake/FindGTK2/FindGTK2RunTwice.cmake b/Tests/RunCMake/FindGTK2/FindGTK2RunTwice.cmake
index e0585ee..b20f824 100644
--- a/Tests/RunCMake/FindGTK2/FindGTK2RunTwice.cmake
+++ b/Tests/RunCMake/FindGTK2/FindGTK2RunTwice.cmake
@@ -1,5 +1,4 @@
-cmake_minimum_required(VERSION 3.7)
-project(testFindGTK2 C)
+enable_language(C)
 
 # First call
 find_package(GTK2 REQUIRED)
diff --git a/Tests/RunCMake/FindLua/CMakeLists.txt b/Tests/RunCMake/FindLua/CMakeLists.txt
index a2c4d98..e6c41a5 100644
--- a/Tests/RunCMake/FindLua/CMakeLists.txt
+++ b/Tests/RunCMake/FindLua/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.4)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} C)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/FindMatlab/CMakeLists.txt b/Tests/RunCMake/FindMatlab/CMakeLists.txt
index 1b9a957..93ee9df 100644
--- a/Tests/RunCMake/FindMatlab/CMakeLists.txt
+++ b/Tests/RunCMake/FindMatlab/CMakeLists.txt
@@ -1,3 +1,3 @@
-
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
+project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/FindMatlab/MatlabTest1.cmake b/Tests/RunCMake/FindMatlab/MatlabTest1.cmake
index b4cc741..8eaf903 100644
--- a/Tests/RunCMake/FindMatlab/MatlabTest1.cmake
+++ b/Tests/RunCMake/FindMatlab/MatlabTest1.cmake
@@ -1,7 +1,6 @@
-
-cmake_minimum_required (VERSION 2.8.12)
+enable_language(C)
+enable_language(CXX)
 enable_testing()
-project(test_should_fail)
 
 if(NOT "${matlab_root}" STREQUAL "")
   set(Matlab_ROOT_DIR ${matlab_root})
diff --git a/Tests/RunCMake/FindMatlab/MatlabTest2.cmake b/Tests/RunCMake/FindMatlab/MatlabTest2.cmake
index 4295d3c..95b1c22 100644
--- a/Tests/RunCMake/FindMatlab/MatlabTest2.cmake
+++ b/Tests/RunCMake/FindMatlab/MatlabTest2.cmake
@@ -1,6 +1,6 @@
-cmake_minimum_required (VERSION 2.8.12)
+enable_language(C)
+enable_language(CXX)
 enable_testing()
-project(findmatlab_runcmake_test2)
 
 if(NOT DEFINED matlab_required)
   set(matlab_required REQUIRED)
diff --git a/Tests/RunCMake/FindMatlab/RunCMakeTest.cmake b/Tests/RunCMake/FindMatlab/RunCMakeTest.cmake
index deebf89..45dc799 100644
--- a/Tests/RunCMake/FindMatlab/RunCMakeTest.cmake
+++ b/Tests/RunCMake/FindMatlab/RunCMakeTest.cmake
@@ -1,7 +1,5 @@
-
 include(RunCMake)
 
-
 if(NOT "${MCR_ROOT}" STREQUAL "")
     if(NOT EXISTS "${MCR_ROOT}")
         message(FATAL_ERROR "MCR does not exist ${MCR_ROOT}")
diff --git a/Tests/RunCMake/FindOpenSSL/version-exact.cmake b/Tests/RunCMake/FindOpenSSL/version-exact.cmake
index 29c2ce3..11826cf 100644
--- a/Tests/RunCMake/FindOpenSSL/version-exact.cmake
+++ b/Tests/RunCMake/FindOpenSSL/version-exact.cmake
@@ -1,5 +1,3 @@
-cmake_minimum_required (VERSION 3.19...3.20)
-
 find_package (OpenSSL REQUIRED COMPONENTS Crypto)
 # Store version without a possibly trailing letter.
 string (REGEX MATCH "^([0-9.]+)" version "${OPENSSL_VERSION}")
diff --git a/Tests/RunCMake/FindOpenSSL/version-range.cmake b/Tests/RunCMake/FindOpenSSL/version-range.cmake
index 9390032..f9689b6 100644
--- a/Tests/RunCMake/FindOpenSSL/version-range.cmake
+++ b/Tests/RunCMake/FindOpenSSL/version-range.cmake
@@ -1,5 +1,3 @@
-cmake_minimum_required (VERSION 3.19...3.20)
-
 find_package (OpenSSL REQUIRED COMPONENTS Crypto)
 # Store version without a possibly trailing letter.
 string (REGEX MATCH "^([0-9.]+)" version "${OPENSSL_VERSION}")
diff --git a/Tests/RunCMake/FindOpenSSL/version.cmake b/Tests/RunCMake/FindOpenSSL/version.cmake
index d06cd1f..3d151ab 100644
--- a/Tests/RunCMake/FindOpenSSL/version.cmake
+++ b/Tests/RunCMake/FindOpenSSL/version.cmake
@@ -1,5 +1,3 @@
-cmake_minimum_required (VERSION 3.19...3.20)
-
 find_package (OpenSSL REQUIRED COMPONENTS Crypto)
 # Store version without a possibly trailing letter.
 string (REGEX MATCH "^([0-9.]+)" version "${OPENSSL_VERSION}")
diff --git a/Tests/RunCMake/FindPkgConfig/CMakeLists.txt b/Tests/RunCMake/FindPkgConfig/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/FindPkgConfig/CMakeLists.txt
+++ b/Tests/RunCMake/FindPkgConfig/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/FindPkgConfig/FindPkgConfig_IMPORTED_TARGET.cmake b/Tests/RunCMake/FindPkgConfig/FindPkgConfig_IMPORTED_TARGET.cmake
index 69ab4da..457747f 100644
--- a/Tests/RunCMake/FindPkgConfig/FindPkgConfig_IMPORTED_TARGET.cmake
+++ b/Tests/RunCMake/FindPkgConfig/FindPkgConfig_IMPORTED_TARGET.cmake
@@ -1,6 +1,4 @@
-cmake_minimum_required(VERSION 3.12)
-
-project(FindPkgConfig_IMPORTED_TARGET C)
+enable_language(C)
 
 find_package(PkgConfig REQUIRED)
 pkg_check_modules(NCURSES IMPORTED_TARGET QUIET ncurses)
diff --git a/Tests/RunCMake/FindPkgConfig/FindPkgConfig_VERSION_OPERATORS.cmake b/Tests/RunCMake/FindPkgConfig/FindPkgConfig_VERSION_OPERATORS.cmake
index f7a9815..95a2e32 100644
--- a/Tests/RunCMake/FindPkgConfig/FindPkgConfig_VERSION_OPERATORS.cmake
+++ b/Tests/RunCMake/FindPkgConfig/FindPkgConfig_VERSION_OPERATORS.cmake
@@ -1,6 +1,4 @@
-cmake_minimum_required(VERSION 3.12)
-
-project(FindPkgConfig_IMPORTED_TARGET C)
+enable_language(C)
 
 find_package(PkgConfig REQUIRED)
 
diff --git a/Tests/RunCMake/FindPkgConfig/FindPkgConfig_cache_variables.cmake b/Tests/RunCMake/FindPkgConfig/FindPkgConfig_cache_variables.cmake
index d0046ca..15baa0d 100644
--- a/Tests/RunCMake/FindPkgConfig/FindPkgConfig_cache_variables.cmake
+++ b/Tests/RunCMake/FindPkgConfig/FindPkgConfig_cache_variables.cmake
@@ -1,5 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
-
 find_package(PkgConfig REQUIRED)
 pkg_check_modules(NCURSES QUIET ncurses)
 
diff --git a/Tests/RunCMake/FindSWIG/version-exact.cmake b/Tests/RunCMake/FindSWIG/version-exact.cmake
index ec3651f..98903ff 100644
--- a/Tests/RunCMake/FindSWIG/version-exact.cmake
+++ b/Tests/RunCMake/FindSWIG/version-exact.cmake
@@ -1,5 +1,3 @@
-cmake_minimum_required (VERSION 3.18...3.19)
-
 find_package (SWIG)
 if (NOT SWIG_FOUND)
   message (FATAL_ERROR "Failed to find SWIG")
diff --git a/Tests/RunCMake/FindSWIG/version-range.cmake b/Tests/RunCMake/FindSWIG/version-range.cmake
index 7ba1134..e776961 100644
--- a/Tests/RunCMake/FindSWIG/version-range.cmake
+++ b/Tests/RunCMake/FindSWIG/version-range.cmake
@@ -1,5 +1,3 @@
-cmake_minimum_required (VERSION 3.18...3.19)
-
 find_package (SWIG)
 if (NOT SWIG_FOUND)
   message (FATAL_ERROR "Failed to find SWIG")
diff --git a/Tests/RunCMake/FindSWIG/version.cmake b/Tests/RunCMake/FindSWIG/version.cmake
index a4f1c39..b5ed6a7 100644
--- a/Tests/RunCMake/FindSWIG/version.cmake
+++ b/Tests/RunCMake/FindSWIG/version.cmake
@@ -1,5 +1,3 @@
-cmake_minimum_required (VERSION 3.18...3.19)
-
 find_package (SWIG 1.0)
 if (NOT SWIG_FOUND)
   message (FATAL_ERROR "Failed to find SWIG with version 1.0")
diff --git a/Tests/RunCMake/Framework/CMakeLists.txt b/Tests/RunCMake/Framework/CMakeLists.txt
index 6dd8cdf..93ee9df 100644
--- a/Tests/RunCMake/Framework/CMakeLists.txt
+++ b/Tests/RunCMake/Framework/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.4)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/Framework/FrameworkConsumption.cmake b/Tests/RunCMake/Framework/FrameworkConsumption.cmake
index 58b70a3..2180cf9 100644
--- a/Tests/RunCMake/Framework/FrameworkConsumption.cmake
+++ b/Tests/RunCMake/Framework/FrameworkConsumption.cmake
@@ -1,5 +1,3 @@
-
-cmake_minimum_required(VERSION 3.22...3.24)
 enable_language(C)
 
 # Create framework and ensure header is placed in Headers
@@ -24,10 +22,14 @@
 )
 
 add_executable(app2 main2.c)
-set_target_properties(Gui2 PROPERTIES
-    PUBLIC_HEADER "${input_header}"
-    FRAMEWORK TRUE
+set_target_properties(app2 PROPERTIES
     RUNTIME_OUTPUT_DIRECTORY bin
 )
 
 target_link_libraries(app2 PRIVATE Gui2)
+
+
+# Same test with STATIC consumer
+add_library(Consumer STATIC consumer.c)
+
+target_link_libraries(Consumer PRIVATE Gui2)
diff --git a/Tests/RunCMake/Framework/FrameworkLayout.cmake b/Tests/RunCMake/Framework/FrameworkLayout.cmake
index 84012aa..d09e8a0 100644
--- a/Tests/RunCMake/Framework/FrameworkLayout.cmake
+++ b/Tests/RunCMake/Framework/FrameworkLayout.cmake
@@ -1,4 +1,3 @@
-cmake_minimum_required(VERSION 3.4)
 enable_language(C)
 
 set(CMAKE_CONFIGURATION_TYPES "Debug" CACHE INTERNAL "Supported configuration types")
diff --git a/Tests/RunCMake/Framework/FrameworkSystemIncludeTest.cmake b/Tests/RunCMake/Framework/FrameworkSystemIncludeTest.cmake
index d172281..bcf6c29 100644
--- a/Tests/RunCMake/Framework/FrameworkSystemIncludeTest.cmake
+++ b/Tests/RunCMake/Framework/FrameworkSystemIncludeTest.cmake
@@ -10,3 +10,15 @@
 add_library(testcase FrameworkSystemIncludeTest.c)
 target_compile_options(testcase PRIVATE "-Werror=#pragma-messages")
 target_link_libraries(testcase PRIVATE Example::Example)
+
+
+
+add_library(Example::Example2 SHARED IMPORTED)
+set_target_properties(Example::Example2 PROPERTIES
+  FRAMEWORK 1
+  IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/subdir/Example.framework/Example.tbd"
+)
+
+add_library(testcase2 FrameworkSystemIncludeTest.c)
+target_compile_options(testcase2 PRIVATE "-Werror=#pragma-messages")
+target_link_libraries(testcase2 PRIVATE Example::Example2)
diff --git a/Tests/RunCMake/Framework/consumer.c b/Tests/RunCMake/Framework/consumer.c
new file mode 100644
index 0000000..a578976
--- /dev/null
+++ b/Tests/RunCMake/Framework/consumer.c
@@ -0,0 +1,9 @@
+
+#include <Gui2/Gui.h>
+
+int consumer()
+{
+  foo();
+
+  return 0;
+}
diff --git a/Tests/RunCMake/GNUInstallDirs/CMakeLists.txt b/Tests/RunCMake/GNUInstallDirs/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/GNUInstallDirs/CMakeLists.txt
+++ b/Tests/RunCMake/GNUInstallDirs/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/GenEx-COMPILE_LANGUAGE/CMakeLists.txt b/Tests/RunCMake/GenEx-COMPILE_LANGUAGE/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/GenEx-COMPILE_LANGUAGE/CMakeLists.txt
+++ b/Tests/RunCMake/GenEx-COMPILE_LANGUAGE/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/GenEx-LINK_LIBRARY/RunCMakeTest.cmake b/Tests/RunCMake/GenEx-LINK_LIBRARY/RunCMakeTest.cmake
index 7df0e80..2ad45ba 100644
--- a/Tests/RunCMake/GenEx-LINK_LIBRARY/RunCMakeTest.cmake
+++ b/Tests/RunCMake/GenEx-LINK_LIBRARY/RunCMakeTest.cmake
@@ -29,7 +29,7 @@
 run_cmake(nested-incompatible-features)
 run_cmake(only-targets)
 
-# testing target propertes LINK_LIBRARY_OVERRIDE and LINK_LIBRARY_OVERRIDE_<LIBRARY>
+# testing target properties LINK_LIBRARY_OVERRIDE and LINK_LIBRARY_OVERRIDE_<LIBRARY>
 run_cmake(override-features1)
 run_cmake(override-features2)
 run_cmake(override-features3)
diff --git a/Tests/RunCMake/GenEx-LIST/APPEND.cmake.in b/Tests/RunCMake/GenEx-LIST/APPEND.cmake.in
new file mode 100644
index 0000000..19e1d12
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/APPEND.cmake.in
@@ -0,0 +1,34 @@
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(listvar a b c d)
+list(APPEND listvar e)
+set (output "$<LIST:APPEND,a;b;c;d,e>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar a b c d)
+list(APPEND listvar e f)
+set (output "$<LIST:APPEND,a;b;c;d,e,f>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar a b c d)
+list(APPEND listvar e f)
+set (output "$<LIST:APPEND,a;b;c;d,e;f>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+unset(listvar)
+list(APPEND listvar e f)
+set (output "$<LIST:APPEND,,e,f>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+
+check_errors("LIST:APPEND..." ${errors})
diff --git a/Tests/RunCMake/GenEx-LIST/CMakeLists.txt b/Tests/RunCMake/GenEx-LIST/CMakeLists.txt
new file mode 100644
index 0000000..5161b99
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/CMakeLists.txt
@@ -0,0 +1,5 @@
+cmake_minimum_required(VERSION 3.18...3.25)
+
+project(${RunCMake_TEST} NONE)
+
+include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/GenEx-LIST/FILTER-wrong-operator-result.txt b/Tests/RunCMake/GenEx-LIST/FILTER-wrong-operator-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/FILTER-wrong-operator-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/FILTER-wrong-operator-stderr.txt b/Tests/RunCMake/GenEx-LIST/FILTER-wrong-operator-stderr.txt
new file mode 100644
index 0000000..3064c00
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/FILTER-wrong-operator-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at FILTER-wrong-operator.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:FILTER,a;b;c,WRONG_OPERATOR,\^a>
+
+  sub-command FILTER does not recognize operator "WRONG_OPERATOR".  It must
+  be either INCLUDE or EXCLUDE.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/FILTER-wrong-operator.cmake b/Tests/RunCMake/GenEx-LIST/FILTER-wrong-operator.cmake
new file mode 100644
index 0000000..e01b4fe
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/FILTER-wrong-operator.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:FILTER,a;b;c,WRONG_OPERATOR,^a>")
diff --git a/Tests/RunCMake/GenEx-LIST/FILTER-wrong-regex-result.txt b/Tests/RunCMake/GenEx-LIST/FILTER-wrong-regex-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/FILTER-wrong-regex-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/FILTER-wrong-regex-stderr.txt b/Tests/RunCMake/GenEx-LIST/FILTER-wrong-regex-stderr.txt
new file mode 100644
index 0000000..b2f9b8a
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/FILTER-wrong-regex-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at FILTER-wrong-regex.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:FILTER,a;b;c,INCLUDE,\^\(a>
+
+  sub-command FILTER, failed to compile regex "\^\(a".
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/FILTER-wrong-regex.cmake b/Tests/RunCMake/GenEx-LIST/FILTER-wrong-regex.cmake
new file mode 100644
index 0000000..1311f31
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/FILTER-wrong-regex.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:FILTER,a;b;c,INCLUDE,^(a>")
diff --git a/Tests/RunCMake/GenEx-LIST/FIND.cmake.in b/Tests/RunCMake/GenEx-LIST/FIND.cmake.in
new file mode 100644
index 0000000..e2242c3
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/FIND.cmake.in
@@ -0,0 +1,20 @@
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(listvar a b c d)
+
+list(FIND listvar "c" reference)
+set (output "$<LIST:FIND,a;b;c;d,c>")
+if (NOT output EQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(FIND listvar "e" reference)
+set (output "$<LIST:FIND,a;b;c;d,e>")
+if (NOT output EQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+
+check_errors("LIST:FIND..." ${errors})
diff --git a/Tests/RunCMake/GenEx-LIST/GET-wrong-index1-result.txt b/Tests/RunCMake/GenEx-LIST/GET-wrong-index1-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/GET-wrong-index1-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/GET-wrong-index1-stderr.txt b/Tests/RunCMake/GenEx-LIST/GET-wrong-index1-stderr.txt
new file mode 100644
index 0000000..dcd1f2f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/GET-wrong-index1-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at GET-wrong-index1.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:GET,,0>
+
+  given empty list
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/GET-wrong-index1.cmake b/Tests/RunCMake/GenEx-LIST/GET-wrong-index1.cmake
new file mode 100644
index 0000000..4c395b3
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/GET-wrong-index1.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:GET,,0>")
diff --git a/Tests/RunCMake/GenEx-LIST/GET-wrong-index2-result.txt b/Tests/RunCMake/GenEx-LIST/GET-wrong-index2-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/GET-wrong-index2-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/GET-wrong-index2-stderr.txt b/Tests/RunCMake/GenEx-LIST/GET-wrong-index2-stderr.txt
new file mode 100644
index 0000000..20f1af4
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/GET-wrong-index2-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at GET-wrong-index2.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:GET,a;b,2>
+
+  index: 2 out of range \(-2, 1\)
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/GET-wrong-index2.cmake b/Tests/RunCMake/GenEx-LIST/GET-wrong-index2.cmake
new file mode 100644
index 0000000..28c97e2
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/GET-wrong-index2.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:GET,a;b,2>")
diff --git a/Tests/RunCMake/GenEx-LIST/GET-wrong-index3-result.txt b/Tests/RunCMake/GenEx-LIST/GET-wrong-index3-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/GET-wrong-index3-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/GET-wrong-index3-stderr.txt b/Tests/RunCMake/GenEx-LIST/GET-wrong-index3-stderr.txt
new file mode 100644
index 0000000..a5dccbe
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/GET-wrong-index3-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at GET-wrong-index3.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:GET,a;b,1,-3>
+
+  index: -3 out of range \(-2, 1\)
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/GET-wrong-index3.cmake b/Tests/RunCMake/GenEx-LIST/GET-wrong-index3.cmake
new file mode 100644
index 0000000..fd7be17
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/GET-wrong-index3.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:GET,a;b,1,-3>")
diff --git a/Tests/RunCMake/GenEx-LIST/GET.cmake.in b/Tests/RunCMake/GenEx-LIST/GET.cmake.in
new file mode 100644
index 0000000..102eb42
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/GET.cmake.in
@@ -0,0 +1,20 @@
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(listvar a b c d)
+
+list(GET listvar 0 2 reference)
+set (output "$<LIST:GET,a;b;c;d,0,2>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(GET listvar 0 -3 2 reference)
+set (output "$<LIST:GET,a;b;c;d,0,-3,2>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+
+check_errors("LIST:GET..." ${errors})
diff --git a/Tests/RunCMake/GenEx-LIST/INSERT-wrong-index1-result.txt b/Tests/RunCMake/GenEx-LIST/INSERT-wrong-index1-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/INSERT-wrong-index1-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/INSERT-wrong-index1-stderr.txt b/Tests/RunCMake/GenEx-LIST/INSERT-wrong-index1-stderr.txt
new file mode 100644
index 0000000..ce36fb1
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/INSERT-wrong-index1-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at INSERT-wrong-index1.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:INSERT,a;b;c,4,d>
+
+  index: 4 out of range \(-3, 3\)
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/INSERT-wrong-index1.cmake b/Tests/RunCMake/GenEx-LIST/INSERT-wrong-index1.cmake
new file mode 100644
index 0000000..ead5832
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/INSERT-wrong-index1.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:INSERT,a;b;c,4,d>")
diff --git a/Tests/RunCMake/GenEx-LIST/INSERT-wrong-index2-result.txt b/Tests/RunCMake/GenEx-LIST/INSERT-wrong-index2-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/INSERT-wrong-index2-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/INSERT-wrong-index2-stderr.txt b/Tests/RunCMake/GenEx-LIST/INSERT-wrong-index2-stderr.txt
new file mode 100644
index 0000000..f0ea1d7
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/INSERT-wrong-index2-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at INSERT-wrong-index2.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:INSERT,a;b;c,-4,d>
+
+  index: -4 out of range \(-3, 3\)
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/INSERT-wrong-index2.cmake b/Tests/RunCMake/GenEx-LIST/INSERT-wrong-index2.cmake
new file mode 100644
index 0000000..c057b87
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/INSERT-wrong-index2.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:INSERT,a;b;c,-4,d>")
diff --git a/Tests/RunCMake/GenEx-LIST/INSERT.cmake.in b/Tests/RunCMake/GenEx-LIST/INSERT.cmake.in
new file mode 100644
index 0000000..d3bb9b9
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/INSERT.cmake.in
@@ -0,0 +1,50 @@
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(listvar a b c d)
+
+list(INSERT listvar 1 e)
+set (output "$<LIST:INSERT,a;b;c;d,1,e>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar a b c d)
+
+list(INSERT listvar 2 e f)
+set (output "$<LIST:INSERT,a;b;c;d,2,e,f>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar a b c d)
+list(INSERT listvar 0 e f)
+set (output "$<LIST:INSERT,a;b;c;d,0,e;f>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar a b c d)
+list(INSERT listvar -2 e f)
+set (output "$<LIST:INSERT,a;b;c;d,-2,e;f>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar a b c d)
+list(INSERT listvar 3 e f)
+set (output "$<LIST:INSERT,a;b;c;d,3,e;f>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+unset(listvar)
+list(INSERT listvar 0 e f)
+set (output "$<LIST:INSERT,,0,e,f>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+
+check_errors("LIST:INSERT..." ${errors})
diff --git a/Tests/RunCMake/GenEx-LIST/JOIN.cmake.in b/Tests/RunCMake/GenEx-LIST/JOIN.cmake.in
new file mode 100644
index 0000000..0a56450
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/JOIN.cmake.in
@@ -0,0 +1,35 @@
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(listvar a b c d)
+
+list(JOIN listvar ":" reference)
+set (output "$<LIST:JOIN,a;b;c;d,:>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(JOIN listvar "" reference)
+set (output "$<LIST:JOIN,a;b;c;d,>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar a)
+
+list(JOIN listvar ":" reference)
+set (output "$<LIST:JOIN,a,:>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+unset(listvar)
+
+list(JOIN listvar ":" reference)
+set (output "$<LIST:JOIN,,:>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+check_errors("LIST:JOIN..." ${errors})
diff --git a/Tests/RunCMake/GenEx-LIST/LENGTH.cmake.in b/Tests/RunCMake/GenEx-LIST/LENGTH.cmake.in
new file mode 100644
index 0000000..0840e11
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/LENGTH.cmake.in
@@ -0,0 +1,30 @@
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(listvar a b c d)
+
+list(LENGTH listvar reference)
+set (output "$<LIST:LENGTH,a;b;c;d>")
+if (NOT output EQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar "")
+
+list(LENGTH listvar reference)
+set (output "$<LIST:LENGTH,>")
+if (NOT output EQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+unset(listvar)
+
+list(LENGTH listvar reference)
+set (output "$<LIST:LENGTH,>")
+if (NOT output EQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+
+check_errors("LIST:LENGTH..." ${errors})
diff --git a/Tests/RunCMake/GenEx-LIST/POP_BACK.cmake.in b/Tests/RunCMake/GenEx-LIST/POP_BACK.cmake.in
new file mode 100644
index 0000000..ba95c83
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/POP_BACK.cmake.in
@@ -0,0 +1,18 @@
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(listvar a b c d)
+list(POP_BACK listvar)
+set (output "$<LIST:POP_BACK,a;b;c;d>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set (output "$<LIST:POP_BACK,>")
+if (NOT output STREQUAL "")
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+
+check_errors("LIST:POP_BACK..." ${errors})
diff --git a/Tests/RunCMake/GenEx-LIST/POP_FRONT.cmake.in b/Tests/RunCMake/GenEx-LIST/POP_FRONT.cmake.in
new file mode 100644
index 0000000..0d676af
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/POP_FRONT.cmake.in
@@ -0,0 +1,18 @@
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(listvar a b c d)
+list(POP_FRONT listvar)
+set (output "$<LIST:POP_FRONT,a;b;c;d>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set (output "$<LIST:POP_FRONT,>")
+if (NOT output STREQUAL "")
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+
+check_errors("LIST:POP_FRONT..." ${errors})
diff --git a/Tests/RunCMake/GenEx-LIST/PREPEND.cmake.in b/Tests/RunCMake/GenEx-LIST/PREPEND.cmake.in
new file mode 100644
index 0000000..49b8f98
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/PREPEND.cmake.in
@@ -0,0 +1,34 @@
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(listvar a b c d)
+list(PREPEND listvar e)
+set (output "$<LIST:PREPEND,a;b;c;d,e>")
+if (NOT output STREQUAL listvar)
+  list (PREPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar a b c d)
+list(PREPEND listvar e f)
+set (output "$<LIST:PREPEND,a;b;c;d,e,f>")
+if (NOT output STREQUAL listvar)
+  list (PREPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar a b c d)
+list(PREPEND listvar e f)
+set (output "$<LIST:PREPEND,a;b;c;d,e;f>")
+if (NOT output STREQUAL listvar)
+  list (PREPEND errors "returns bad value: ${output}")
+endif()
+
+unset(listvar)
+list(PREPEND listvar e f)
+set (output "$<LIST:PREPEND,,e,f>")
+if (NOT output STREQUAL listvar)
+  list (PREPEND errors "returns bad value: ${output}")
+endif()
+
+
+check_errors("LIST:PREPEND..." ${errors})
diff --git a/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index1-result.txt b/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index1-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index1-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index1-stderr.txt b/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index1-stderr.txt
new file mode 100644
index 0000000..b874f1d
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index1-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at REMOVE_AT-wrong-index1.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:REMOVE_AT,,0>
+
+  index: 0 out of range \(0, 0\)
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index1.cmake b/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index1.cmake
new file mode 100644
index 0000000..d87559e
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index1.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:REMOVE_AT,,0>")
diff --git a/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index2-result.txt b/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index2-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index2-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index2-stderr.txt b/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index2-stderr.txt
new file mode 100644
index 0000000..509eed4
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index2-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at REMOVE_AT-wrong-index2.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:REMOVE_AT,a;b;c,3>
+
+  index: 3 out of range \(-3, 2\)
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index2.cmake b/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index2.cmake
new file mode 100644
index 0000000..7ecfad2
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index2.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:REMOVE_AT,a;b;c,3>")
diff --git a/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index3-result.txt b/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index3-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index3-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index3-stderr.txt b/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index3-stderr.txt
new file mode 100644
index 0000000..c8fc465
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index3-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at REMOVE_AT-wrong-index3.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:REMOVE_AT,a;b;c,-4>
+
+  index: -4 out of range \(-3, 2\)
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index3.cmake b/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index3.cmake
new file mode 100644
index 0000000..f8f9a3e
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/REMOVE_AT-wrong-index3.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:REMOVE_AT,a;b;c,-4>")
diff --git a/Tests/RunCMake/GenEx-LIST/REMOVE_AT.cmake.in b/Tests/RunCMake/GenEx-LIST/REMOVE_AT.cmake.in
new file mode 100644
index 0000000..42a9476
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/REMOVE_AT.cmake.in
@@ -0,0 +1,25 @@
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(listvar a b c d)
+list(REMOVE_AT listvar 1 3)
+set (output "$<LIST:REMOVE_AT,a;b;c;d,1,3>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar a b c d)
+list(REMOVE_AT listvar 1 -2)
+set (output "$<LIST:REMOVE_AT,a;b;c;d,1;-2>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set (output "$<LIST:REMOVE_AT,a;b;c;d,1,0,3;2>")
+if (NOT output STREQUAL "")
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+
+check_errors("LIST:REMOVE_AT..." ${errors})
diff --git a/Tests/RunCMake/GenEx-LIST/REMOVE_DUPLICATES.cmake.in b/Tests/RunCMake/GenEx-LIST/REMOVE_DUPLICATES.cmake.in
new file mode 100644
index 0000000..8785ae5
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/REMOVE_DUPLICATES.cmake.in
@@ -0,0 +1,20 @@
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(listvar a b c d)
+list(REMOVE_DUPLICATES listvar)
+set (output "$<LIST:REMOVE_DUPLICATES,a;b;c;d>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar "b;c;b;a;a;c;b;a;c;b")
+list(REMOVE_DUPLICATES listvar)
+set (output "$<LIST:REMOVE_DUPLICATES,b;c;b;a;a;c;b;a;c;b>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+
+check_errors("LIST:REMOVE_DUPLICATES..." ${errors})
diff --git a/Tests/RunCMake/GenEx-LIST/REMOVE_ITEM.cmake.in b/Tests/RunCMake/GenEx-LIST/REMOVE_ITEM.cmake.in
new file mode 100644
index 0000000..5434a5d
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/REMOVE_ITEM.cmake.in
@@ -0,0 +1,32 @@
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(listvar a b c d)
+list(REMOVE_ITEM listvar b d)
+set (output "$<LIST:REMOVE_ITEM,a;b;c;d,b,d>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar a b c d)
+list(REMOVE_ITEM listvar b e)
+set (output "$<LIST:REMOVE_ITEM,a;b;c;d,b,e>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar a b c d)
+list(REMOVE_ITEM listvar b a d c)
+set (output "$<LIST:REMOVE_ITEM,a;b;c;d,b;a;d;c>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set (output "$<LIST:REMOVE_ITEM,,b;a;d;c>")
+if (NOT output STREQUAL "")
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+
+check_errors("LIST:REMOVE_ITEM..." ${errors})
diff --git a/Tests/RunCMake/GenEx-LIST/REVERSE.cmake.in b/Tests/RunCMake/GenEx-LIST/REVERSE.cmake.in
new file mode 100644
index 0000000..295e1a3
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/REVERSE.cmake.in
@@ -0,0 +1,19 @@
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(listvar a b c d)
+
+list(REVERSE listvar)
+set (output "$<LIST:REVERSE,a;b;c;d>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set (output "$<LIST:REVERSE,>")
+if (NOT output STREQUAL "")
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+
+check_errors("LIST:REVERSE..." ${errors})
diff --git a/Tests/RunCMake/GenEx-LIST/RunCMakeTest.cmake b/Tests/RunCMake/GenEx-LIST/RunCMakeTest.cmake
new file mode 100644
index 0000000..1946e84
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/RunCMakeTest.cmake
@@ -0,0 +1,130 @@
+
+include(RunCMake)
+
+run_cmake(no-arguments)
+run_cmake(bad-option)
+
+function(check_list_syntax name test)
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${name}-${test}-build)
+  set(RunCMake_TEST_VARIANT_DESCRIPTION " - ${name}")
+  run_cmake_with_options(${test} ${ARGN})
+endfunction()
+
+## Unexpected arguments
+### sub-commands with one argument
+foreach (subcommand IN ITEMS LENGTH POP_BACK POP_FRONT REMOVE_DUPLICATES REVERSE)
+  check_list_syntax (${subcommand} unexpected-arg "-DLIST_ARGUMENTS=${subcommand},ARG1,ARG2")
+endforeach()
+
+### sub-commands with two arguments
+foreach (subcommand IN ITEMS FIND JOIN)
+  check_list_syntax (${subcommand} unexpected-arg "-DLIST_ARGUMENTS=${subcommand},ARG1,ARG2,ARG3")
+endforeach()
+
+### sub-commands with three arguments
+foreach (subcommand IN ITEMS SUBLIST FILTER)
+  check_list_syntax (${subcommand} unexpected-arg "-DLIST_ARGUMENTS=${subcommand},ARG1,ARG2,ARG3,ARG4")
+endforeach()
+
+# TRANSFORM sub-commands
+  set(RunCMake-stderr-file "TRANSFORM-unexpected-arg-stderr.txt")
+foreach (action IN ITEMS TOLOWER TOUPPER STRIP)
+  check_list_syntax (TRANSFORM-${action} unexpected-arg "-DLIST_ARGUMENTS=TRANSFORM,ARG1,${action},ARG2")
+endforeach()
+foreach (action IN ITEMS APPEND PREPEND)
+  check_list_syntax (TRANSFORM-${action} unexpected-arg "-DLIST_ARGUMENTS=TRANSFORM,ARG1,${action},ARG2,ARG3")
+endforeach()
+foreach (action IN ITEMS REPLACE)
+  check_list_syntax (TRANSFORM-${action} unexpected-arg "-DLIST_ARGUMENTS=TRANSFORM,ARG1,${action},ARG2,ARG3,ARG4")
+endforeach()
+check_list_syntax (TRANSFORM-SELECTOR-REGEX unexpected-arg "-DLIST_ARGUMENTS=TRANSFORM,ARG1,STRIP,REGEX,ARG2,ARG3")
+check_list_syntax (TRANSFORM-SELECTOR-FOR unexpected-arg "-DLIST_ARGUMENTS=TRANSFORM,ARG1,STRIP,FOR,1,2,3,4")
+unset(RunCMake-stderr-file)
+
+## Missing arguments
+### sub-command with, at least, two arguments
+foreach (subcommand IN ITEMS GET APPEND PREPEND REMOVE_ITEM REMOVE_AT TRANSFORM)
+  check_list_syntax (${subcommand} missing-arg "-DLIST_ARGUMENTS=${subcommand},ARG1")
+endforeach()
+
+### sub-command with, at least, three arguments
+foreach (subcommand IN ITEMS INSERT)
+  check_list_syntax (${subcommand} missing-arg "-DLIST_ARGUMENTS=${subcommand},ARG1,ARG2")
+endforeach()
+
+# TRANSFORM sub-commands
+set(RunCMake-stderr-file "TRANSFORM-missing-arg-stderr.txt")
+foreach (action IN ITEMS APPEND PREPEND)
+  check_list_syntax (TRANSFORM-${action} missing-arg "-DLIST_ARGUMENTS=TRANSFORM,ARG1,${action}")
+endforeach()
+check_list_syntax (TRANSFORM-REPLACE-1 missing-arg "-DLIST_ARGUMENTS=TRANSFORM,ARG1,REPLACE,ARG2")
+check_list_syntax (TRANSFORM-REPLACE-2 missing-arg "-DLIST_ARGUMENTS=TRANSFORM,ARG1,REPLACE")
+unset(RunCMake-stderr-file)
+
+
+run_cmake(GET-wrong-index1)
+run_cmake(GET-wrong-index2)
+run_cmake(GET-wrong-index3)
+run_cmake(SUBLIST-wrong-argument1)
+run_cmake(SUBLIST-wrong-argument2)
+run_cmake(INSERT-wrong-index1)
+run_cmake(INSERT-wrong-index2)
+run_cmake(REMOVE_AT-wrong-index1)
+run_cmake(REMOVE_AT-wrong-index2)
+run_cmake(REMOVE_AT-wrong-index3)
+run_cmake(FILTER-wrong-operator)
+run_cmake(FILTER-wrong-regex)
+run_cmake(TRANSFORM-wrong-action)
+run_cmake(TRANSFORM-REPLACE-wrong-regex)
+run_cmake(TRANSFORM-REPLACE-invalid-replace1)
+run_cmake(TRANSFORM-REPLACE-invalid-replace2)
+run_cmake(TRANSFORM-selector-REGEX-no-arguments)
+run_cmake(TRANSFORM-selector-REGEX-wrong-regex)
+run_cmake(TRANSFORM-selector-AT-no-arguments)
+run_cmake(TRANSFORM-selector-AT-wrong-argument)
+run_cmake(TRANSFORM-selector-AT-wrong-index)
+run_cmake(TRANSFORM-selector-FOR-no-arguments)
+run_cmake(TRANSFORM-selector-FOR-missing-arguments)
+run_cmake(TRANSFORM-selector-FOR-wrong-argument)
+run_cmake(TRANSFORM-selector-FOR-wrong-index)
+run_cmake(TRANSFORM-selector-FOR-zero-step)
+run_cmake(TRANSFORM-selector-FOR-negative-step)
+run_cmake(TRANSFORM-selector-FOR-backwards-range)
+run_cmake(SORT-wrong-option)
+run_cmake(SORT-wrong-COMPARE-option)
+run_cmake(SORT-wrong-CASE-option)
+run_cmake(SORT-wrong-ORDER-option)
+run_cmake(SORT-duplicate-COMPARE-option)
+run_cmake(SORT-duplicate-CASE-option)
+run_cmake(SORT-duplicate-ORDER-option)
+
+
+function(check_list_execution name)
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${name}-build)
+  set(RunCMake_TEST_NO_CLEAN 1)
+  set(RunCMake_TEST_VARIANT_DESCRIPTION " - ${name}")
+  run_cmake_with_options(generate -DLIST_TEST=${name})
+  run_cmake_command(check "${CMAKE_COMMAND}" "-DRunCMake_SOURCE_DIR=${RunCMake_SOURCE_DIR}" -P "${RunCMake_TEST_BINARY_DIR}/${name}.cmake")
+endfunction()
+
+check_list_execution (LENGTH)
+check_list_execution (GET)
+check_list_execution (JOIN)
+check_list_execution (SUBLIST)
+check_list_execution (FIND)
+check_list_execution (APPEND)
+check_list_execution (PREPEND)
+check_list_execution (INSERT)
+check_list_execution (POP_BACK)
+check_list_execution (POP_FRONT)
+check_list_execution (REMOVE_ITEM)
+check_list_execution (REMOVE_AT)
+check_list_execution (REMOVE_DUPLICATES)
+check_list_execution (TRANSFORM-TOUPPER)
+check_list_execution (TRANSFORM-TOLOWER)
+check_list_execution (TRANSFORM-STRIP)
+check_list_execution (TRANSFORM-APPEND)
+check_list_execution (TRANSFORM-PREPEND)
+check_list_execution (TRANSFORM-REPLACE)
+check_list_execution (REVERSE)
+check_list_execution (SORT)
diff --git a/Tests/RunCMake/GenEx-LIST/SORT-duplicate-CASE-option-result.txt b/Tests/RunCMake/GenEx-LIST/SORT-duplicate-CASE-option-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SORT-duplicate-CASE-option-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/SORT-duplicate-CASE-option-stderr.txt b/Tests/RunCMake/GenEx-LIST/SORT-duplicate-CASE-option-stderr.txt
new file mode 100644
index 0000000..4f3121a
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SORT-duplicate-CASE-option-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at SORT-duplicate-CASE-option.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:SORT,a;b,COMPARE:STRING,CASE:SENSITIVE,CASE:SENSITIVE>
+
+  sub-command SORT, CASE option has been specified multiple times.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/SORT-duplicate-CASE-option.cmake b/Tests/RunCMake/GenEx-LIST/SORT-duplicate-CASE-option.cmake
new file mode 100644
index 0000000..e09dc6c
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SORT-duplicate-CASE-option.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:SORT,a;b,COMPARE:STRING,CASE:SENSITIVE,CASE:SENSITIVE>")
diff --git a/Tests/RunCMake/GenEx-LIST/SORT-duplicate-COMPARE-option-result.txt b/Tests/RunCMake/GenEx-LIST/SORT-duplicate-COMPARE-option-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SORT-duplicate-COMPARE-option-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/SORT-duplicate-COMPARE-option-stderr.txt b/Tests/RunCMake/GenEx-LIST/SORT-duplicate-COMPARE-option-stderr.txt
new file mode 100644
index 0000000..fbb96bb
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SORT-duplicate-COMPARE-option-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at SORT-duplicate-COMPARE-option.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:SORT,a;b,COMPARE:STRING,COMPARE:NATURAL>
+
+  sub-command SORT, COMPARE option has been specified multiple times.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/SORT-duplicate-COMPARE-option.cmake b/Tests/RunCMake/GenEx-LIST/SORT-duplicate-COMPARE-option.cmake
new file mode 100644
index 0000000..cf8da0d
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SORT-duplicate-COMPARE-option.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:SORT,a;b,COMPARE:STRING,COMPARE:NATURAL>")
diff --git a/Tests/RunCMake/GenEx-LIST/SORT-duplicate-ORDER-option-result.txt b/Tests/RunCMake/GenEx-LIST/SORT-duplicate-ORDER-option-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SORT-duplicate-ORDER-option-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/SORT-duplicate-ORDER-option-stderr.txt b/Tests/RunCMake/GenEx-LIST/SORT-duplicate-ORDER-option-stderr.txt
new file mode 100644
index 0000000..b45034a
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SORT-duplicate-ORDER-option-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at SORT-duplicate-ORDER-option.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:SORT,a;b,ORDER:ASCENDING,COMPARE:STRING,ORDER:DESCENDING,CASE:SENSITIVE>
+
+  sub-command SORT, ORDER option has been specified multiple times.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/SORT-duplicate-ORDER-option.cmake b/Tests/RunCMake/GenEx-LIST/SORT-duplicate-ORDER-option.cmake
new file mode 100644
index 0000000..3826072
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SORT-duplicate-ORDER-option.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:SORT,a;b,ORDER:ASCENDING,COMPARE:STRING,ORDER:DESCENDING,CASE:SENSITIVE>")
diff --git a/Tests/RunCMake/GenEx-LIST/SORT-wrong-CASE-option-result.txt b/Tests/RunCMake/GenEx-LIST/SORT-wrong-CASE-option-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SORT-wrong-CASE-option-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/SORT-wrong-CASE-option-stderr.txt b/Tests/RunCMake/GenEx-LIST/SORT-wrong-CASE-option-stderr.txt
new file mode 100644
index 0000000..d36e63b
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SORT-wrong-CASE-option-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at SORT-wrong-CASE-option.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:SORT,a;b,COMPARE:STRING,CASE:WRONG_OPTION>
+
+  sub-command SORT, an invalid CASE option has been specified:
+  "WRONG_OPTION".
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/SORT-wrong-CASE-option.cmake b/Tests/RunCMake/GenEx-LIST/SORT-wrong-CASE-option.cmake
new file mode 100644
index 0000000..58df9ea
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SORT-wrong-CASE-option.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:SORT,a;b,COMPARE:STRING,CASE:WRONG_OPTION>")
diff --git a/Tests/RunCMake/GenEx-LIST/SORT-wrong-COMPARE-option-result.txt b/Tests/RunCMake/GenEx-LIST/SORT-wrong-COMPARE-option-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SORT-wrong-COMPARE-option-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/SORT-wrong-COMPARE-option-stderr.txt b/Tests/RunCMake/GenEx-LIST/SORT-wrong-COMPARE-option-stderr.txt
new file mode 100644
index 0000000..70a99c6
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SORT-wrong-COMPARE-option-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at SORT-wrong-COMPARE-option.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:SORT,a;b,COMPARE:WRONG_OPTION>
+
+  sub-command SORT, an invalid COMPARE option has been specified:
+  "WRONG_OPTION".
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/SORT-wrong-COMPARE-option.cmake b/Tests/RunCMake/GenEx-LIST/SORT-wrong-COMPARE-option.cmake
new file mode 100644
index 0000000..73727bb
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SORT-wrong-COMPARE-option.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:SORT,a;b,COMPARE:WRONG_OPTION>")
diff --git a/Tests/RunCMake/GenEx-LIST/SORT-wrong-ORDER-option-result.txt b/Tests/RunCMake/GenEx-LIST/SORT-wrong-ORDER-option-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SORT-wrong-ORDER-option-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/SORT-wrong-ORDER-option-stderr.txt b/Tests/RunCMake/GenEx-LIST/SORT-wrong-ORDER-option-stderr.txt
new file mode 100644
index 0000000..2e23d8c
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SORT-wrong-ORDER-option-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at SORT-wrong-ORDER-option.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:SORT,a;b,COMPARE:STRING,CASE:SENSITIVE,ORDER:WRONG_OPTION>
+
+  sub-command SORT, an invalid ORDER option has been specified:
+  "WRONG_OPTION".
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/SORT-wrong-ORDER-option.cmake b/Tests/RunCMake/GenEx-LIST/SORT-wrong-ORDER-option.cmake
new file mode 100644
index 0000000..135c935
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SORT-wrong-ORDER-option.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:SORT,a;b,COMPARE:STRING,CASE:SENSITIVE,ORDER:WRONG_OPTION>")
diff --git a/Tests/RunCMake/GenEx-LIST/SORT-wrong-option-result.txt b/Tests/RunCMake/GenEx-LIST/SORT-wrong-option-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SORT-wrong-option-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/SORT-wrong-option-stderr.txt b/Tests/RunCMake/GenEx-LIST/SORT-wrong-option-stderr.txt
new file mode 100644
index 0000000..3c2d492
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SORT-wrong-option-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at SORT-wrong-option.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:SORT,a;b,WRONG_OPTION>
+
+  sub-command SORT, option "WRONG_OPTION" is invalid.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/SORT-wrong-option.cmake b/Tests/RunCMake/GenEx-LIST/SORT-wrong-option.cmake
new file mode 100644
index 0000000..fca268b
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SORT-wrong-option.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:SORT,a;b,WRONG_OPTION>")
diff --git a/Tests/RunCMake/GenEx-LIST/SORT.cmake.in b/Tests/RunCMake/GenEx-LIST/SORT.cmake.in
new file mode 100644
index 0000000..aca6691
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SORT.cmake.in
@@ -0,0 +1,92 @@
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(source_unsorted c/B.h a/c.h B/a.h)
+
+set(listvar ${source_unsorted})
+list(SORT listvar)
+set (output "$<LIST:SORT,c/B.h;a/c.h;B/a.h>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar ${source_unsorted})
+list(SORT listvar CASE INSENSITIVE ORDER ASCENDING COMPARE STRING)
+set (output "$<LIST:SORT,c/B.h;a/c.h;B/a.h,CASE:INSENSITIVE,ORDER:ASCENDING,COMPARE:STRING>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar ${source_unsorted})
+list(SORT listvar CASE INSENSITIVE ORDER DESCENDING COMPARE STRING)
+set (output "$<LIST:SORT,c/B.h;a/c.h;B/a.h,CASE:INSENSITIVE,ORDER:DESCENDING,COMPARE:STRING>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar ${source_unsorted})
+list(SORT listvar CASE SENSITIVE ORDER ASCENDING COMPARE STRING)
+set (output "$<LIST:SORT,c/B.h;a/c.h;B/a.h,CASE:SENSITIVE,ORDER:ASCENDING,COMPARE:STRING>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar ${source_unsorted})
+list(SORT listvar CASE SENSITIVE ORDER DESCENDING COMPARE STRING)
+set (output "$<LIST:SORT,c/B.h;a/c.h;B/a.h,CASE:SENSITIVE,ORDER:DESCENDING,COMPARE:STRING>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar ${source_unsorted})
+list(SORT listvar CASE INSENSITIVE ORDER ASCENDING COMPARE FILE_BASENAME)
+set (output "$<LIST:SORT,c/B.h;a/c.h;B/a.h,CASE:INSENSITIVE,ORDER:ASCENDING,COMPARE:FILE_BASENAME>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar ${source_unsorted})
+list(SORT listvar CASE INSENSITIVE ORDER DESCENDING COMPARE FILE_BASENAME)
+set (output "$<LIST:SORT,c/B.h;a/c.h;B/a.h,CASE:INSENSITIVE,ORDER:DESCENDING,COMPARE:FILE_BASENAME>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar ${source_unsorted})
+list(SORT listvar CASE SENSITIVE ORDER ASCENDING COMPARE FILE_BASENAME)
+set (output "$<LIST:SORT,c/B.h;a/c.h;B/a.h,CASE:SENSITIVE,ORDER:ASCENDING,COMPARE:FILE_BASENAME>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar ${source_unsorted})
+list(SORT listvar CASE SENSITIVE ORDER DESCENDING COMPARE FILE_BASENAME)
+set (output "$<LIST:SORT,c/B.h;a/c.h;B/a.h,CASE:SENSITIVE,ORDER:DESCENDING,COMPARE:FILE_BASENAME>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar 10.0 1.1 2.1 8.0 2.0 3.1)
+list(SORT listvar CASE SENSITIVE ORDER DESCENDING COMPARE STRING)
+set (output "$<LIST:SORT,10.0;1.1;2.1;8.0;2.0;3.1,CASE:SENSITIVE,ORDER:DESCENDING,COMPARE:STRING>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar 10.0 1.1 2.1 8.0 2.0 3.1)
+list(SORT listvar CASE SENSITIVE ORDER DESCENDING COMPARE NATURAL)
+set (output "$<LIST:SORT,10.0;1.1;2.1;8.0;2.0;3.1,CASE:SENSITIVE,ORDER:DESCENDING,COMPARE:NATURAL>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+set(listvar 10.0 1.1 2.1 8.0 2.0 3.1)
+list(SORT listvar CASE SENSITIVE ORDER ASCENDING COMPARE NATURAL)
+set (output "$<LIST:SORT,10.0;1.1;2.1;8.0;2.0;3.1,CASE:SENSITIVE,ORDER:ASCENDING,COMPARE:NATURAL>")
+if (NOT output STREQUAL listvar)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+
+check_errors("LIST:SORT..." ${errors})
diff --git a/Tests/RunCMake/GenEx-LIST/SUBLIST-wrong-argument1-result.txt b/Tests/RunCMake/GenEx-LIST/SUBLIST-wrong-argument1-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SUBLIST-wrong-argument1-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/SUBLIST-wrong-argument1-stderr.txt b/Tests/RunCMake/GenEx-LIST/SUBLIST-wrong-argument1-stderr.txt
new file mode 100644
index 0000000..078f345
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SUBLIST-wrong-argument1-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at SUBLIST-wrong-argument1.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:SUBLIST,a;b;c,3,-1>
+
+  begin index: 3 is out of range 0 - 2
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/SUBLIST-wrong-argument1.cmake b/Tests/RunCMake/GenEx-LIST/SUBLIST-wrong-argument1.cmake
new file mode 100644
index 0000000..293c2ae
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SUBLIST-wrong-argument1.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:SUBLIST,a;b;c,3,-1>")
diff --git a/Tests/RunCMake/GenEx-LIST/SUBLIST-wrong-argument2-result.txt b/Tests/RunCMake/GenEx-LIST/SUBLIST-wrong-argument2-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SUBLIST-wrong-argument2-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/SUBLIST-wrong-argument2-stderr.txt b/Tests/RunCMake/GenEx-LIST/SUBLIST-wrong-argument2-stderr.txt
new file mode 100644
index 0000000..7271b30
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SUBLIST-wrong-argument2-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at SUBLIST-wrong-argument2.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:SUBLIST,a;b;c,1,-2>
+
+  length: -2 should be -1 or greater
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/SUBLIST-wrong-argument2.cmake b/Tests/RunCMake/GenEx-LIST/SUBLIST-wrong-argument2.cmake
new file mode 100644
index 0000000..9d1db53
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SUBLIST-wrong-argument2.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:SUBLIST,a;b;c,1,-2>")
diff --git a/Tests/RunCMake/GenEx-LIST/SUBLIST.cmake.in b/Tests/RunCMake/GenEx-LIST/SUBLIST.cmake.in
new file mode 100644
index 0000000..9fcb6d5
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/SUBLIST.cmake.in
@@ -0,0 +1,32 @@
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(listvar a b c d)
+
+list(SUBLIST listvar 1 2 reference)
+set (output "$<LIST:SUBLIST,a;b;c;d,1,2>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(SUBLIST listvar 1 -1 reference)
+set (output "$<LIST:SUBLIST,a;b;c;d,1,-1>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(SUBLIST listvar 1 0 reference)
+set (output "$<LIST:SUBLIST,a;b;c;d,1,0>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(SUBLIST listvar 1 5 reference)
+set (output "$<LIST:SUBLIST,a;b;c;d,1,5>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+
+check_errors("LIST:SUBLIST..." ${errors})
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-APPEND.cmake.in b/Tests/RunCMake/GenEx-LIST/TRANSFORM-APPEND.cmake.in
new file mode 100644
index 0000000..a6529ec
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-APPEND.cmake.in
@@ -0,0 +1,50 @@
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(listvar alpha bravo charlie delta)
+
+list(TRANSFORM listvar APPEND "_A" OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,APPEND,_A>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar APPEND "_A" AT 1 3 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,APPEND,_A,AT,1,3>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar APPEND "_A" AT 1 -2 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,APPEND,_A,AT,1,-2>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar APPEND "_A" FOR 1 2 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,APPEND,_A,FOR,1,2>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar APPEND "_A" FOR 1 -1 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,APPEND,_A,FOR,1,-1>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar APPEND "_A" FOR 1 -1 2 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,APPEND,_A,FOR,1,-1,2>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar APPEND "_A" REGEX "(r|t)a" OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,APPEND,_A,REGEX,(r|t)a>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+
+check_errors("LIST:TRANSFORM,APPEND..." ${errors})
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-PREPEND.cmake.in b/Tests/RunCMake/GenEx-LIST/TRANSFORM-PREPEND.cmake.in
new file mode 100644
index 0000000..5ec6acd
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-PREPEND.cmake.in
@@ -0,0 +1,50 @@
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(listvar alpha bravo charlie delta)
+
+list(TRANSFORM listvar PREPEND "P_" OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,PREPEND,P_>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar PREPEND "P_" AT 1 3 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,PREPEND,P_,AT,1,3>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar PREPEND "P_" AT 1 -2 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,PREPEND,P_,AT,1,-2>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar PREPEND "P_" FOR 1 2 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,PREPEND,P_,FOR,1,2>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar PREPEND "P_" FOR 1 -1 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,PREPEND,P_,FOR,1,-1>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar PREPEND "P_" FOR 1 -1 2 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,PREPEND,P_,FOR,1,-1,2>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar PREPEND "P_" REGEX "(r|t)a" OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,PREPEND,P_,REGEX,(r|t)a>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+
+check_errors("LIST:TRANSFORM,PREPEND..." ${errors})
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-invalid-replace1-result.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-invalid-replace1-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-invalid-replace1-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-invalid-replace1-stderr.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-invalid-replace1-stderr.txt
new file mode 100644
index 0000000..6674b30
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-invalid-replace1-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at TRANSFORM-REPLACE-invalid-replace1.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:TRANSFORM,a;b,REPLACE,\^a,b\\>
+
+  sub-command TRANSFORM, action REPLACE: replace-expression ends in a
+  backslash.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-invalid-replace1.cmake b/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-invalid-replace1.cmake
new file mode 100644
index 0000000..d45e1fd
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-invalid-replace1.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:TRANSFORM,a;b,REPLACE,^a,b\\>")
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-invalid-replace2-result.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-invalid-replace2-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-invalid-replace2-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-invalid-replace2-stderr.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-invalid-replace2-stderr.txt
new file mode 100644
index 0000000..2351b72
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-invalid-replace2-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at TRANSFORM-REPLACE-invalid-replace2.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:TRANSFORM,a;b,REPLACE,\^a,\\b>
+
+  sub-command TRANSFORM, action REPLACE: Unknown escape "\\b" in
+  replace-expression.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-invalid-replace2.cmake b/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-invalid-replace2.cmake
new file mode 100644
index 0000000..b800f24
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-invalid-replace2.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:TRANSFORM,a;b,REPLACE,^a,\\b>")
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-wrong-regex-result.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-wrong-regex-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-wrong-regex-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-wrong-regex-stderr.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-wrong-regex-stderr.txt
new file mode 100644
index 0000000..617c3d6
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-wrong-regex-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at TRANSFORM-REPLACE-wrong-regex.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:TRANSFORM,a;b,REPLACE,\(a,b>
+
+  sub-command TRANSFORM, action REPLACE: Failed to compile regex "\(a".
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-wrong-regex.cmake b/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-wrong-regex.cmake
new file mode 100644
index 0000000..941e3e7
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE-wrong-regex.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:TRANSFORM,a;b,REPLACE,(a,b>")
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE.cmake.in b/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE.cmake.in
new file mode 100644
index 0000000..5fba231
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-REPLACE.cmake.in
@@ -0,0 +1,50 @@
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(listvar alpha bravo charlie delta)
+
+list(TRANSFORM listvar REPLACE "(.+a)$" "\\1_\\1" OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,REPLACE,(.+a)$,\1_\1>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar REPLACE "(.+a)$" "\\1_\\1" AT 1 3 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,REPLACE,(.+a)$,\1_\1,AT,1,3>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar REPLACE "(.+a)$" "\\1_\\1" AT 1 -2 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,REPLACE,(.+a)$,\1_\1,AT,1,-2>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar REPLACE "(.+a)$" "\\1_\\1" FOR 1 2 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,REPLACE,(.+a)$,\1_\1,FOR,1,2>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar REPLACE "(.+a)$" "\\1_\\1" FOR 1 -1 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,REPLACE,(.+a)$,\1_\1,FOR,1,-1>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar REPLACE "(.+a)$" "\\1_\\1" FOR 1 -1 2 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,REPLACE,(.+a)$,\1_\1,FOR,1,-1,2>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar REPLACE "(.+a)$" "\\1_\\1" REGEX "(r|t)a" OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,REPLACE,(.+a)$,\1_\1,REGEX,(r|t)a>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+
+check_errors("LIST:TRANSFORM,REPLACE..." ${errors})
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-STRIP.cmake.in b/Tests/RunCMake/GenEx-LIST/TRANSFORM-STRIP.cmake.in
new file mode 100644
index 0000000..860faec
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-STRIP.cmake.in
@@ -0,0 +1,50 @@
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(listvar " alpha" "bravo " " charlie " delta)
+
+list(TRANSFORM listvar STRIP OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM, alpha;bravo ; charlie ;delta,STRIP>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar STRIP AT 1 3 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM, alpha;bravo ; charlie ;delta,STRIP,AT,1,3>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar STRIP AT 1 -2 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM, alpha;bravo ; charlie ;delta,STRIP,AT,1,-2>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar STRIP FOR 1 2 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM, alpha;bravo ; charlie ;delta,STRIP,FOR,1,2>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar STRIP FOR 1 -1 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM, alpha;bravo ; charlie ;delta,STRIP,FOR,1,-1>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar STRIP FOR 1 -1 2 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM, alpha;bravo ; charlie ;delta,STRIP,FOR,1,-1,2>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar STRIP REGEX "(r|t)a" OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM, alpha;bravo ; charlie ;delta,STRIP,REGEX,(r|t)a>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+
+check_errors("LIST:TRANSFORM,STRIP..." ${errors})
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-TOLOWER.cmake.in b/Tests/RunCMake/GenEx-LIST/TRANSFORM-TOLOWER.cmake.in
new file mode 100644
index 0000000..43e9955
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-TOLOWER.cmake.in
@@ -0,0 +1,50 @@
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(listvar ALPHA BRAVO CHARLIE DELTA)
+
+list(TRANSFORM listvar TOLOWER OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,ALPHA;BRAVO;CHARLIE;DELTA,TOLOWER>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar TOLOWER AT 1 3 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,ALPHA;BRAVO;CHARLIE;DELTA,TOLOWER,AT,1,3>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar TOLOWER AT 1 -2 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,ALPHA;BRAVO;CHARLIE;DELTA,TOLOWER,AT,1,-2>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar TOLOWER FOR 1 2 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,ALPHA;BRAVO;CHARLIE;DELTA,TOLOWER,FOR,1,2>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar TOLOWER FOR 1 -1 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,ALPHA;BRAVO;CHARLIE;DELTA,TOLOWER,FOR,1,-1>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar TOLOWER FOR 1 -1 2 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,ALPHA;BRAVO;CHARLIE;DELTA,TOLOWER,FOR,1,-1,2>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar TOLOWER REGEX "(R|T)A" OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,ALPHA;BRAVO;CHARLIE;DELTA,TOLOWER,REGEX,(R|T)A>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+
+check_errors("LIST:TRANSFORM,TOLOWER..." ${errors})
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-TOUPPER.cmake.in b/Tests/RunCMake/GenEx-LIST/TRANSFORM-TOUPPER.cmake.in
new file mode 100644
index 0000000..be2bc30
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-TOUPPER.cmake.in
@@ -0,0 +1,50 @@
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(listvar alpha bravo charlie delta)
+
+list(TRANSFORM listvar TOUPPER OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,TOUPPER>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar TOUPPER AT 1 3 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,TOUPPER,AT,1,3>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar TOUPPER AT 1 -2 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,TOUPPER,AT,1,-2>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar TOUPPER FOR 1 2 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,TOUPPER,FOR,1,2>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar TOUPPER FOR 1 -1 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,TOUPPER,FOR,1,-1>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar TOUPPER FOR 1 -1 2 OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,TOUPPER,FOR,1,-1,2>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+list(TRANSFORM listvar TOUPPER REGEX "(r|t)a" OUTPUT_VARIABLE reference)
+set (output "$<LIST:TRANSFORM,alpha;bravo;charlie;delta,TOUPPER,REGEX,(r|t)a>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "returns bad value: ${output}")
+endif()
+
+
+check_errors("LIST:TRANSFORM,TOUPPER..." ${errors})
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-missing-arg-stderr.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-missing-arg-stderr.txt
new file mode 100644
index 0000000..46e36c3
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-missing-arg-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at missing-arg.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:TRANSFORM,ARG1,[A-Z]+(,ARG[0-9])?>
+
+  sub-command TRANSFORM, action [A-Z]+ expects (1|2) argument\(s\).
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-no-arguments-result.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-no-arguments-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-no-arguments-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-no-arguments-stderr.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-no-arguments-stderr.txt
new file mode 100644
index 0000000..69b4915
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-no-arguments-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at TRANSFORM-selector-AT-no-arguments.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:TRANSFORM,a;b,TOUPPER,AT>
+
+  sub-command TRANSFORM, selector AT expects at least one numeric value.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-no-arguments.cmake b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-no-arguments.cmake
new file mode 100644
index 0000000..2447511
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-no-arguments.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:TRANSFORM,a;b,TOUPPER,AT>")
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-wrong-argument-result.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-wrong-argument-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-wrong-argument-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-wrong-argument-stderr.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-wrong-argument-stderr.txt
new file mode 100644
index 0000000..40007a1
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-wrong-argument-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at TRANSFORM-selector-AT-wrong-argument.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:TRANSFORM,a;b,TOUPPER,AT,0,1x,2>
+
+  sub-command TRANSFORM, selector AT: '1x': unexpected argument.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-wrong-argument.cmake b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-wrong-argument.cmake
new file mode 100644
index 0000000..49f4e90
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-wrong-argument.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:TRANSFORM,a;b,TOUPPER,AT,0,1x,2>")
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-wrong-index-result.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-wrong-index-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-wrong-index-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-wrong-index-stderr.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-wrong-index-stderr.txt
new file mode 100644
index 0000000..66085ab
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-wrong-index-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at TRANSFORM-selector-AT-wrong-index.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:TRANSFORM,a;b;c,TOUPPER,AT,0,3,2>
+
+  sub-command TRANSFORM, selector AT, index: 3 out of range \(-3, 2\).
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-wrong-index.cmake b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-wrong-index.cmake
new file mode 100644
index 0000000..fd6c3ed
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-AT-wrong-index.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:TRANSFORM,a;b;c,TOUPPER,AT,0,3,2>")
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-backwards-range-result.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-backwards-range-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-backwards-range-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-backwards-range-stderr.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-backwards-range-stderr.txt
new file mode 100644
index 0000000..77cffdc
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-backwards-range-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at TRANSFORM-selector-FOR-backwards-range.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:TRANSFORM,a;b;c,TOUPPER,FOR,2,0>
+
+  sub-command TRANSFORM, selector FOR expects <start> to be no greater than
+  <stop> \(2 > 0\)
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-backwards-range.cmake b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-backwards-range.cmake
new file mode 100644
index 0000000..17f57f5
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-backwards-range.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:TRANSFORM,a;b;c,TOUPPER,FOR,2,0>")
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-missing-arguments-result.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-missing-arguments-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-missing-arguments-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-missing-arguments-stderr.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-missing-arguments-stderr.txt
new file mode 100644
index 0000000..a9de7f8
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-missing-arguments-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at TRANSFORM-selector-FOR-missing-arguments.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:TRANSFORM,a;b,TOUPPER,FOR,0>
+
+  sub-command TRANSFORM, selector FOR expects, at least, two arguments.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-missing-arguments.cmake b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-missing-arguments.cmake
new file mode 100644
index 0000000..5ae481a
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-missing-arguments.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:TRANSFORM,a;b,TOUPPER,FOR,0>")
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-negative-step-result.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-negative-step-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-negative-step-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-negative-step-stderr.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-negative-step-stderr.txt
new file mode 100644
index 0000000..9c81de4
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-negative-step-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at TRANSFORM-selector-FOR-negative-step.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:TRANSFORM,a;b;c,TOUPPER,FOR,0,2,-1>
+
+  sub-command TRANSFORM, selector FOR expects positive numeric value for
+  <step>.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-negative-step.cmake b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-negative-step.cmake
new file mode 100644
index 0000000..b8e6433
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-negative-step.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:TRANSFORM,a;b;c,TOUPPER,FOR,0,2,-1>")
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-no-arguments-result.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-no-arguments-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-no-arguments-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-no-arguments-stderr.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-no-arguments-stderr.txt
new file mode 100644
index 0000000..d31f171
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-no-arguments-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at TRANSFORM-selector-FOR-no-arguments.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:TRANSFORM,a;b,TOUPPER,FOR>
+
+  sub-command TRANSFORM, selector FOR expects, at least, two arguments.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-no-arguments.cmake b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-no-arguments.cmake
new file mode 100644
index 0000000..6545ff9
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-no-arguments.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:TRANSFORM,a;b,TOUPPER,FOR>")
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-wrong-argument-result.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-wrong-argument-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-wrong-argument-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-wrong-argument-stderr.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-wrong-argument-stderr.txt
new file mode 100644
index 0000000..727be10
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-wrong-argument-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at TRANSFORM-selector-FOR-wrong-argument.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:TRANSFORM,a;b;c,TOUPPER,FOR,0,1x,2>
+
+  sub-command TRANSFORM, selector FOR expects, at least, two numeric values.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-wrong-argument.cmake b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-wrong-argument.cmake
new file mode 100644
index 0000000..a50b62c
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-wrong-argument.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:TRANSFORM,a;b;c,TOUPPER,FOR,0,1x,2>")
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-wrong-index-result.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-wrong-index-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-wrong-index-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-wrong-index-stderr.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-wrong-index-stderr.txt
new file mode 100644
index 0000000..664e35a
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-wrong-index-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at TRANSFORM-selector-FOR-wrong-index.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:TRANSFORM,a;b;c,TOUPPER,FOR,0,4,2>
+
+  sub-command TRANSFORM, selector FOR, index: 4 out of range \(-3, 2\).
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-wrong-index.cmake b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-wrong-index.cmake
new file mode 100644
index 0000000..f35bfbf
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-wrong-index.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:TRANSFORM,a;b;c,TOUPPER,FOR,0,4,2>")
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-zero-step-result.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-zero-step-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-zero-step-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-zero-step-stderr.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-zero-step-stderr.txt
new file mode 100644
index 0000000..e542fbf
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-zero-step-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at TRANSFORM-selector-FOR-zero-step.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:TRANSFORM,a;b;c,TOUPPER,FOR,0,2,0>
+
+  sub-command TRANSFORM, selector FOR expects positive numeric value for
+  <step>.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-zero-step.cmake b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-zero-step.cmake
new file mode 100644
index 0000000..2bfaab0
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-FOR-zero-step.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:TRANSFORM,a;b;c,TOUPPER,FOR,0,2,0>")
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-REGEX-no-arguments-result.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-REGEX-no-arguments-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-REGEX-no-arguments-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-REGEX-no-arguments-stderr.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-REGEX-no-arguments-stderr.txt
new file mode 100644
index 0000000..079172b
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-REGEX-no-arguments-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at TRANSFORM-selector-REGEX-no-arguments.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:TRANSFORM,a;b,TOUPPER,REGEX>
+
+  sub-command TRANSFORM, selector REGEX expects 'regular expression'
+  argument.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-REGEX-no-arguments.cmake b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-REGEX-no-arguments.cmake
new file mode 100644
index 0000000..1f802b7
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-REGEX-no-arguments.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:TRANSFORM,a;b,TOUPPER,REGEX>")
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-REGEX-wrong-regex-result.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-REGEX-wrong-regex-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-REGEX-wrong-regex-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-REGEX-wrong-regex-stderr.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-REGEX-wrong-regex-stderr.txt
new file mode 100644
index 0000000..3fe2b01
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-REGEX-wrong-regex-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at TRANSFORM-selector-REGEX-wrong-regex.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:TRANSFORM,a;b,TOUPPER,REGEX,\(a>
+
+  sub-command TRANSFORM, selector REGEX failed to compile regex "\(a".
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-REGEX-wrong-regex.cmake b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-REGEX-wrong-regex.cmake
new file mode 100644
index 0000000..1648092
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-selector-REGEX-wrong-regex.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:TRANSFORM,a;b,TOUPPER,REGEX,(a>")
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-unexpected-arg-stderr.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-unexpected-arg-stderr.txt
new file mode 100644
index 0000000..04e9df1
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-unexpected-arg-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at unexpected-arg.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:TRANSFORM,ARG1,[A-Z_]+(,[A-Z0-9]+)+>
+
+  sub-command TRANSFORM, '[A-Z0-9]+': unexpected argument\(s\).
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-wrong-action-result.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-wrong-action-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-wrong-action-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-wrong-action-stderr.txt b/Tests/RunCMake/GenEx-LIST/TRANSFORM-wrong-action-stderr.txt
new file mode 100644
index 0000000..1c30c4f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-wrong-action-stderr.txt
@@ -0,0 +1,7 @@
+CMake Error at TRANSFORM-wrong-action.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:TRANSFORM,a;b;c,WRONG_ACTION>
+   sub-command TRANSFORM, WRONG_ACTION invalid action.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/TRANSFORM-wrong-action.cmake b/Tests/RunCMake/GenEx-LIST/TRANSFORM-wrong-action.cmake
new file mode 100644
index 0000000..fba6c90
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/TRANSFORM-wrong-action.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:TRANSFORM,a;b;c,WRONG_ACTION>")
diff --git a/Tests/RunCMake/GenEx-LIST/bad-option-result.txt b/Tests/RunCMake/GenEx-LIST/bad-option-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/bad-option-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/bad-option-stderr.txt b/Tests/RunCMake/GenEx-LIST/bad-option-stderr.txt
new file mode 100644
index 0000000..37048c5
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/bad-option-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at bad-option.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:BAD_OPTION,ARG>
+
+  BAD_OPTION: invalid option.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/bad-option.cmake b/Tests/RunCMake/GenEx-LIST/bad-option.cmake
new file mode 100644
index 0000000..d1dc85f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/bad-option.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:BAD_OPTION,ARG>")
diff --git a/Tests/RunCMake/GenEx-LIST/check_errors.cmake b/Tests/RunCMake/GenEx-LIST/check_errors.cmake
new file mode 100644
index 0000000..7e60fc7
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/check_errors.cmake
@@ -0,0 +1,13 @@
+
+function (CHECK_ERRORS command)
+  set (errors ${ARGN})
+  set (command "$<${command}>")
+  if (errors)
+    string (LENGTH "${command}" length)
+    math (EXPR count "${length} + 2")
+    string (REPEAT " " ${count} shift)
+    list (TRANSFORM errors PREPEND "${shift}")
+    list (JOIN errors "\n" msg)
+    message (FATAL_ERROR "${command}: ${msg}")
+  endif()
+endfunction()
diff --git a/Tests/RunCMake/GenEx-LIST/generate.cmake b/Tests/RunCMake/GenEx-LIST/generate.cmake
new file mode 100644
index 0000000..92cdca2
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/generate.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT "${LIST_TEST}.cmake" INPUT "${LIST_TEST}.cmake.in")
diff --git a/Tests/RunCMake/GenEx-LIST/missing-arg-result.txt b/Tests/RunCMake/GenEx-LIST/missing-arg-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/missing-arg-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/missing-arg-stderr.txt b/Tests/RunCMake/GenEx-LIST/missing-arg-stderr.txt
new file mode 100644
index 0000000..c7cbe4b
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/missing-arg-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at missing-arg.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:[A-Z_]+,.+>
+
+  \$<LIST:[A-Z_]+> expression requires at least (two|three) parameters.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/missing-arg.cmake b/Tests/RunCMake/GenEx-LIST/missing-arg.cmake
new file mode 100644
index 0000000..48b26e1
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/missing-arg.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:${LIST_ARGUMENTS}>")
diff --git a/Tests/RunCMake/GenEx-LIST/no-arguments-result.txt b/Tests/RunCMake/GenEx-LIST/no-arguments-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/no-arguments-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/no-arguments-stderr.txt b/Tests/RunCMake/GenEx-LIST/no-arguments-stderr.txt
new file mode 100644
index 0000000..ee782ec
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/no-arguments-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at no-arguments.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:>
+
+  \$<LIST> expression requires at least two parameters.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/no-arguments.cmake b/Tests/RunCMake/GenEx-LIST/no-arguments.cmake
new file mode 100644
index 0000000..f704e2b
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/no-arguments.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:>")
diff --git a/Tests/RunCMake/GenEx-LIST/unexpected-arg-result.txt b/Tests/RunCMake/GenEx-LIST/unexpected-arg-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/unexpected-arg-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-LIST/unexpected-arg-stderr.txt b/Tests/RunCMake/GenEx-LIST/unexpected-arg-stderr.txt
new file mode 100644
index 0000000..a7b1f25
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/unexpected-arg-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at unexpected-arg.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<LIST:[A-Z_]+,.+>
+
+  \$<LIST:[A-Z_]+(,[A-Z_]+)?> expression requires exactly (one|two|three) parameters?.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-LIST/unexpected-arg.cmake b/Tests/RunCMake/GenEx-LIST/unexpected-arg.cmake
new file mode 100644
index 0000000..48b26e1
--- /dev/null
+++ b/Tests/RunCMake/GenEx-LIST/unexpected-arg.cmake
@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<LIST:${LIST_ARGUMENTS}>")
diff --git a/Tests/RunCMake/GenEx-PATH/ABSOLUTE_PATH.cmake.in b/Tests/RunCMake/GenEx-PATH/ABSOLUTE_PATH.cmake.in
index cc5ff54..d1cb61b 100644
--- a/Tests/RunCMake/GenEx-PATH/ABSOLUTE_PATH.cmake.in
+++ b/Tests/RunCMake/GenEx-PATH/ABSOLUTE_PATH.cmake.in
@@ -31,4 +31,28 @@
 endif()
 
 
+######################################
+## tests with list of paths
+######################################
+unset (reference)
+foreach(item IN ITEMS "../../a/d" "/a/d/../e")
+  cmake_path(ABSOLUTE_PATH item BASE_DIRECTORY "/x/y/a/f")
+  list(APPEND reference "${item}")
+endforeach()
+set(output "$<PATH:ABSOLUTE_PATH,../../a/d;/a/d/../e,/x/y/a/f>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "'${output}' instead of '${reference}'")
+endif()
+
+unset (reference)
+foreach(item IN ITEMS "../../a/d" "/a/d/../e")
+  cmake_path(ABSOLUTE_PATH item BASE_DIRECTORY "/x/y/a/f" NORMALIZE)
+  list(APPEND reference "${item}")
+endforeach()
+set(output "$<PATH:ABSOLUTE_PATH,NORMALIZE,../../a/d;/a/d/../e,/x/y/a/f>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "'${output}' instead of '${reference}'")
+endif()
+
+
 check_errors("PATH:ABSOLUTE_PATH" ${errors})
diff --git a/Tests/RunCMake/GenEx-PATH/APPEND.cmake.in b/Tests/RunCMake/GenEx-PATH/APPEND.cmake.in
index ab967a2..1955480 100644
--- a/Tests/RunCMake/GenEx-PATH/APPEND.cmake.in
+++ b/Tests/RunCMake/GenEx-PATH/APPEND.cmake.in
@@ -65,4 +65,39 @@
   endif()
 endif()
 
+
+######################################
+## tests with list of paths
+######################################
+unset(reference)
+foreach(item IN ITEMS "/a/b" "/x/y")
+  cmake_path (APPEND result "${item}" "c")
+  list(APPEND reference "${result}")
+endforeach()
+set(output "$<PATH:APPEND,/a/b;/x/y,c>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "'${output}' instead of '${reference}'")
+endif()
+
+unset(reference)
+foreach(item IN ITEMS "a" "c")
+  cmake_path (APPEND item "")
+  list(APPEND reference "${item}")
+endforeach()
+set(output "$<PATH:APPEND,a;c,>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "'${output}' instead of '${reference}'")
+endif()
+
+unset(reference)
+foreach(item IN ITEMS "a/" "c/")
+  cmake_path (APPEND item "/b")
+  list(APPEND reference "${item}")
+endforeach()
+set(output "$<PATH:APPEND,a/;c/,/b>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "'${output}' instead of '${reference}'")
+endif()
+
+
 check_errors ("PATH:APPEND" ${errors})
diff --git a/Tests/RunCMake/GenEx-PATH/CMAKE_PATH.cmake.in b/Tests/RunCMake/GenEx-PATH/CMAKE_PATH.cmake.in
index 41205fa..29ebf16 100644
--- a/Tests/RunCMake/GenEx-PATH/CMAKE_PATH.cmake.in
+++ b/Tests/RunCMake/GenEx-PATH/CMAKE_PATH.cmake.in
@@ -50,4 +50,46 @@
 endif()
 
 
+######################################
+## tests with list of paths
+######################################
+set(reference "/x/y/z/../../a/d;/x/y/z/../../b/e")
+set(output "$<PATH:CMAKE_PATH,/x/y/z/../../a/d;/x/y/z/../../b/e>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "'${output}' instead of '${reference}'")
+endif()
+
+unset(reference)
+foreach(path IN ITEMS "/x/y/z/../../a/d" "/x/y/z/../../b/e")
+  cmake_path(SET result NORMALIZE "${path}")
+  list(APPEND reference "${result}")
+endforeach()
+set(output "$<PATH:CMAKE_PATH,NORMALIZE,/x/y/z/../../a/d;/x/y/z/../../b/e>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "'${output}' instead of '${reference}'")
+endif()
+
+if (WIN32)
+  unset(reference)
+  foreach(path IN ITEMS "/x\\y/z\\..\\../a/d" "/x\\y/z\\..\\../b/e")
+    cmake_path(SET result "${path}")
+    list(APPEND reference "${result}")
+  endforeach()
+  set(output "$<PATH:CMAKE_PATH,/x\y/z\..\../a/d;/x\y/z\..\../b/e>")
+  if (NOT output STREQUAL reference)
+    list (APPEND errors "'${output}' instead of '${reference}'")
+  endif()
+
+  unset(reference)
+  foreach(path IN ITEMS "/x\\y/z\\..\\../a/d" "/x\\y/z\\..\\../b/e")
+    cmake_path(SET result NORMALIZE "${path}")
+    list(APPEND reference "${result}")
+  endforeach()
+  set(output "$<PATH:CMAKE_PATH,NORMALIZE,/x\y/z\..\../a/d;/x\y/z\..\../b/e>")
+  if (NOT output STREQUAL reference)
+    list (APPEND errors "'${output}' instead of '${reference}'")
+  endif()
+endif()
+
+
 check_errors("PATH:CMAKE_PATH" ${errors})
diff --git a/Tests/RunCMake/GenEx-PATH/CMakeLists.txt b/Tests/RunCMake/GenEx-PATH/CMakeLists.txt
index f9748e9..5161b99 100644
--- a/Tests/RunCMake/GenEx-PATH/CMakeLists.txt
+++ b/Tests/RunCMake/GenEx-PATH/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.18...3.24)
+cmake_minimum_required(VERSION 3.18...3.25)
 
 project(${RunCMake_TEST} NONE)
 
diff --git a/Tests/RunCMake/GenEx-PATH/GET_ITEM.cmake.in b/Tests/RunCMake/GenEx-PATH/GET_ITEM.cmake.in
index b58998c..e2acde4 100644
--- a/Tests/RunCMake/GenEx-PATH/GET_ITEM.cmake.in
+++ b/Tests/RunCMake/GenEx-PATH/GET_ITEM.cmake.in
@@ -1,3 +1,4 @@
+cmake_policy(SET CMP0140 NEW)
 
 include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
 unset (errors)
@@ -308,4 +309,126 @@
 endif()
 
 
+######################################
+## third, tests with list of paths
+######################################
+if (WIN32)
+  set (paths "C:/aa/bb/cc.ext1.ext2" "D:/xx/yy/zz.ext3.ext4")
+else()
+  set (paths "/aa/bb/cc.ext1.ext2" "/xx/yy/zz.ext3.ext4")
+endif()
+
+function (compute_reference action)
+  unset(reference)
+  foreach (path IN LISTS paths)
+    cmake_path(GET path ${ARGV} result)
+    list(APPEND reference "${result}")
+  endforeach()
+  if (reference STREQUAL "")
+    # define the list as 2 empty elements
+    set(reference ";")
+  endif()
+
+  return(PROPAGATE reference)
+endfunction()
+
+compute_reference(ROOT_NAME)
+if (WIN32)
+  set(output "$<PATH:GET_ROOT_NAME,C:/aa/bb/cc.ext1.ext2;D:/xx/yy/zz.ext3.ext4>")
+else()
+  set (output "$<PATH:GET_ROOT_NAME,/aa/bb/cc.ext1.ext2;/xx/yy/zz.ext3.ext4>")
+endif()
+if (NOT output STREQUAL reference)
+  list (APPEND errors "ROOT_NAME returns bad data: ${output}")
+endif()
+
+compute_reference(ROOT_DIRECTORY)
+if (WIN32)
+  set(output "$<PATH:GET_ROOT_DIRECTORY,C:/aa/bb/cc.ext1.ext2;D:/xx/yy/zz.ext3.ext4>")
+else()
+  set (output "$<PATH:GET_ROOT_DIRECTORY,/aa/bb/cc.ext1.ext2;/xx/yy/zz.ext3.ext4>")
+endif()
+if (NOT output STREQUAL reference)
+  list (APPEND errors "ROOT_DIRECTORY returns bad data: ${output}")
+endif()
+
+compute_reference(ROOT_PATH)
+if (WIN32)
+  set(output "$<PATH:GET_ROOT_PATH,C:/aa/bb/cc.ext1.ext2;D:/xx/yy/zz.ext3.ext4>")
+else()
+  set (output "$<PATH:GET_ROOT_PATH,/aa/bb/cc.ext1.ext2;/xx/yy/zz.ext3.ext4>")
+endif()
+if (NOT output STREQUAL reference)
+  list (APPEND errors "ROOT_PATH returns bad data: ${output}")
+endif()
+
+compute_reference(FILENAME)
+if (WIN32)
+  set(output "$<PATH:GET_FILENAME,C:/aa/bb/cc.ext1.ext2;D:/xx/yy/zz.ext3.ext4>")
+else()
+  set (output "$<PATH:GET_FILENAME,/aa/bb/cc.ext1.ext2;/xx/yy/zz.ext3.ext4>")
+endif()
+if (NOT output STREQUAL reference)
+  list (APPEND errors "FILENAME returns bad data: ${output}")
+endif()
+
+compute_reference(EXTENSION)
+if (WIN32)
+  set(output "$<PATH:GET_EXTENSION,C:/aa/bb/cc.ext1.ext2;D:/xx/yy/zz.ext3.ext4>")
+else()
+  set (output "$<PATH:GET_EXTENSION,/aa/bb/cc.ext1.ext2;/xx/yy/zz.ext3.ext4>")
+endif()
+if (NOT output STREQUAL reference)
+  list (APPEND errors "EXTENSION returns bad data: ${output}")
+endif()
+compute_reference(EXTENSION LAST_ONLY)
+if (WIN32)
+  set(output "$<PATH:GET_EXTENSION,LAST_ONLY,C:/aa/bb/cc.ext1.ext2;D:/xx/yy/zz.ext3.ext4>")
+else()
+  set (output "$<PATH:GET_EXTENSION,LAST_ONLY,/aa/bb/cc.ext1.ext2;/xx/yy/zz.ext3.ext4>")
+endif()
+if (NOT output STREQUAL reference)
+  list (APPEND errors "EXTENSION LAST_ONLY returns bad data: ${output}")
+endif()
+
+compute_reference(STEM)
+if (WIN32)
+  set(output "$<PATH:GET_STEM,C:/aa/bb/cc.ext1.ext2;D:/xx/yy/zz.ext3.ext4>")
+else()
+  set (output "$<PATH:GET_STEM,/aa/bb/cc.ext1.ext2;/xx/yy/zz.ext3.ext4>")
+endif()
+if (NOT output STREQUAL reference)
+  list (APPEND errors "STEM returns bad data: ${output}")
+endif()
+compute_reference(STEM LAST_ONLY)
+if (WIN32)
+  set(output "$<PATH:GET_STEM,LAST_ONLY,C:/aa/bb/cc.ext1.ext2;D:/xx/yy/zz.ext3.ext4>")
+else()
+  set (output "$<PATH:GET_STEM,LAST_ONLY,/aa/bb/cc.ext1.ext2;/xx/yy/zz.ext3.ext4>")
+endif()
+if (NOT output STREQUAL reference)
+  list (APPEND errors "STEM LAST_ONLY returns bad data: ${reference}")
+endif()
+
+compute_reference(RELATIVE_PART)
+if (WIN32)
+  set(output "$<PATH:GET_RELATIVE_PART,C:/aa/bb/cc.ext1.ext2;D:/xx/yy/zz.ext3.ext4>")
+else()
+  set (output "$<PATH:GET_RELATIVE_PART,/aa/bb/cc.ext1.ext2;/xx/yy/zz.ext3.ext4>")
+endif()
+if (NOT output STREQUAL reference)
+  list (APPEND errors "RELATIVE_PART returns bad data: ${output}")
+endif()
+
+compute_reference(PARENT_PATH)
+if (WIN32)
+  set(output "$<PATH:GET_PARENT_PATH,C:/aa/bb/cc.ext1.ext2;D:/xx/yy/zz.ext3.ext4>")
+else()
+  set (output "$<PATH:GET_PARENT_PATH,/aa/bb/cc.ext1.ext2;/xx/yy/zz.ext3.ext4>")
+endif()
+if (NOT output STREQUAL reference)
+  list (APPEND errors "PARENT_PATH returns bad data: ${output}")
+endif()
+
+
 check_errors("PATH:GET..." ${errors})
diff --git a/Tests/RunCMake/GenEx-PATH/NORMAL_PATH.cmake.in b/Tests/RunCMake/GenEx-PATH/NORMAL_PATH.cmake.in
index e6cc4a3..81e4c0d 100644
--- a/Tests/RunCMake/GenEx-PATH/NORMAL_PATH.cmake.in
+++ b/Tests/RunCMake/GenEx-PATH/NORMAL_PATH.cmake.in
@@ -40,4 +40,18 @@
 endif()
 
 
+######################################
+## tests with list of paths
+######################################
+unset (reference)
+foreach(item IN ITEMS "a/./b/.." "x/.//y/z//..")
+  cmake_path(NORMAL_PATH item)
+  list(APPEND reference "${item}")
+endforeach()
+set(output "$<PATH:NORMAL_PATH,a/./b/..;x/.//y/z//..>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "'${output}' instead of '${reference}'")
+endif()
+
+
 check_errors("PATH:NORMAL_PATH" ${errors})
diff --git a/Tests/RunCMake/GenEx-PATH/RELATIVE_PATH.cmake.in b/Tests/RunCMake/GenEx-PATH/RELATIVE_PATH.cmake.in
index 11d73ad..7670f4f 100644
--- a/Tests/RunCMake/GenEx-PATH/RELATIVE_PATH.cmake.in
+++ b/Tests/RunCMake/GenEx-PATH/RELATIVE_PATH.cmake.in
@@ -61,4 +61,18 @@
 endif()
 
 
+######################################
+## tests with list of paths
+######################################
+unset (reference)
+foreach(item IN ITEMS "/a//d" "/a/b/e")
+  cmake_path(RELATIVE_PATH item BASE_DIRECTORY "/a/b/c")
+  list(APPEND reference "${item}")
+endforeach()
+set(output "$<PATH:RELATIVE_PATH,/a//d;/a/b/e,/a/b/c>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "'${output}' instead of '${reference}'")
+endif()
+
+
 check_errors("PATH:RELATIVE_PATH" ${errors})
diff --git a/Tests/RunCMake/GenEx-PATH/REMOVE_ITEM.cmake.in b/Tests/RunCMake/GenEx-PATH/REMOVE_ITEM.cmake.in
index cce4143..a365efe 100644
--- a/Tests/RunCMake/GenEx-PATH/REMOVE_ITEM.cmake.in
+++ b/Tests/RunCMake/GenEx-PATH/REMOVE_ITEM.cmake.in
@@ -1,3 +1,4 @@
+cmake_policy(SET CMP0140 NEW)
 
 include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
 unset (errors)
@@ -62,4 +63,39 @@
 endif()
 
 
+######################################
+## tests with list of paths
+######################################
+function (compute_reference action)
+  unset(reference)
+  foreach (path IN LISTS paths)
+    cmake_path(${action} path ${ARGN})
+    list(APPEND reference "${path}")
+  endforeach()
+
+  return(PROPAGATE reference)
+endfunction()
+
+set (paths "a/b/c.e.f" "g/h/i.j.k")
+compute_reference(REMOVE_FILENAME)
+set(output "$<PATH:REMOVE_FILENAME,a/b/c.e.f;g/h/i.j.k>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "FILENAME: '${output}' instead of '${reference}'")
+endif()
+
+set (paths "a/b/c.e.f" "g/h/i.j.k")
+compute_reference(REMOVE_EXTENSION)
+set(output "$<PATH:REMOVE_EXTENSION,a/b/c.e.f;g/h/i.j.k>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "EXTENSION: '${output}' instead of '${reference}'")
+endif()
+
+set (reference "a/b/c.e.f" "g/h/i.j.k")
+compute_reference(REMOVE_EXTENSION LAST_ONLY)
+set(output "$<PATH:REMOVE_EXTENSION,LAST_ONLY,a/b/c.e.f;g/h/i.j.k>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "EXTENSION: '${output}' instead of '${reference}'")
+endif()
+
+
 check_errors("PATH:REMOVE..." ${errors})
diff --git a/Tests/RunCMake/GenEx-PATH/REPLACE_ITEM.cmake.in b/Tests/RunCMake/GenEx-PATH/REPLACE_ITEM.cmake.in
index 5bb04c3..2d02152 100644
--- a/Tests/RunCMake/GenEx-PATH/REPLACE_ITEM.cmake.in
+++ b/Tests/RunCMake/GenEx-PATH/REPLACE_ITEM.cmake.in
@@ -1,3 +1,4 @@
+cmake_policy(SET CMP0140 NEW)
 
 include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
 unset (errors)
@@ -70,4 +71,39 @@
 endif()
 
 
+######################################
+## tests with list of paths
+######################################
+function (compute_reference action new_value)
+  unset(reference)
+  foreach (path IN LISTS paths)
+    cmake_path(${action} path "${new_value}" ${ARGN})
+    list(APPEND reference "${path}")
+  endforeach()
+
+  return(PROPAGATE reference)
+endfunction()
+
+set (paths "a/b/c.e.f" "g/h/i.j.k")
+compute_reference(REPLACE_FILENAME "x.y")
+set(output "$<PATH:REPLACE_FILENAME,a/b/c.e.f;g/h/i.j.k,x.y>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "FILENAME: '${output}' instead of '${reference}'")
+endif()
+
+set (paths "a/b/c.e.f" "g/h/i.j.k")
+compute_reference(REPLACE_EXTENSION ".x")
+set(output "$<PATH:REPLACE_EXTENSION,a/b/c.e.f;g/h/i.j.k,.x>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "EXTENSION: '${output}' instead of '${reference}'")
+endif()
+
+set (paths "a/b/c.e.f" "g/h/i.j.k")
+compute_reference(REPLACE_EXTENSION ".x" LAST_ONLY)
+set(output "$<PATH:REPLACE_EXTENSION,LAST_ONLY,a/b/c.e.f;g/h/i.j.k,.x>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "EXTENSION: '${output}' instead of '${reference}'")
+endif()
+
+
 check_errors("PATH:REPLACE..." ${errors})
diff --git a/Tests/RunCMake/GenEx-TARGET_FILE/CMakeLists.txt b/Tests/RunCMake/GenEx-TARGET_FILE/CMakeLists.txt
index 4b3de84..93ee9df 100644
--- a/Tests/RunCMake/GenEx-TARGET_FILE/CMakeLists.txt
+++ b/Tests/RunCMake/GenEx-TARGET_FILE/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/GenEx-TARGET_FILE/TARGET_FILE_BASE_NAME-imported-target.cmake b/Tests/RunCMake/GenEx-TARGET_FILE/TARGET_FILE_BASE_NAME-imported-target.cmake
index 7eec527..187e7d6 100644
--- a/Tests/RunCMake/GenEx-TARGET_FILE/TARGET_FILE_BASE_NAME-imported-target.cmake
+++ b/Tests/RunCMake/GenEx-TARGET_FILE/TARGET_FILE_BASE_NAME-imported-target.cmake
@@ -1,6 +1,3 @@
-
-cmake_minimum_required(VERSION 3.14)
-
 enable_language (C)
 
 set (GENERATE_CONTENT [[
diff --git a/Tests/RunCMake/GenEx-TARGET_FILE/TARGET_FILE_BASE_NAME.cmake b/Tests/RunCMake/GenEx-TARGET_FILE/TARGET_FILE_BASE_NAME.cmake
index 1963244..d550431 100644
--- a/Tests/RunCMake/GenEx-TARGET_FILE/TARGET_FILE_BASE_NAME.cmake
+++ b/Tests/RunCMake/GenEx-TARGET_FILE/TARGET_FILE_BASE_NAME.cmake
@@ -1,6 +1,3 @@
-
-cmake_minimum_required(VERSION 3.14)
-
 enable_language (C)
 
 set (GENERATE_CONTENT [[
diff --git a/Tests/RunCMake/GenEx-TARGET_FILE/TARGET_FILE_PREFIX-imported-target.cmake b/Tests/RunCMake/GenEx-TARGET_FILE/TARGET_FILE_PREFIX-imported-target.cmake
index cc9cd5a..01926fe 100644
--- a/Tests/RunCMake/GenEx-TARGET_FILE/TARGET_FILE_PREFIX-imported-target.cmake
+++ b/Tests/RunCMake/GenEx-TARGET_FILE/TARGET_FILE_PREFIX-imported-target.cmake
@@ -1,6 +1,3 @@
-
-cmake_minimum_required(VERSION 3.14)
-
 enable_language (C)
 
 set (win_platforms Windows CYGWIN MSYS)
diff --git a/Tests/RunCMake/GenEx-TARGET_FILE/TARGET_FILE_PREFIX.cmake b/Tests/RunCMake/GenEx-TARGET_FILE/TARGET_FILE_PREFIX.cmake
index edfb40c..a9a76bf 100644
--- a/Tests/RunCMake/GenEx-TARGET_FILE/TARGET_FILE_PREFIX.cmake
+++ b/Tests/RunCMake/GenEx-TARGET_FILE/TARGET_FILE_PREFIX.cmake
@@ -1,6 +1,3 @@
-
-cmake_minimum_required(VERSION 3.14)
-
 enable_language (C)
 
 set (win_platforms Windows CYGWIN MSYS)
diff --git a/Tests/RunCMake/GenEx-TARGET_FILE/TARGET_FILE_SUFFIX-imported-target.cmake b/Tests/RunCMake/GenEx-TARGET_FILE/TARGET_FILE_SUFFIX-imported-target.cmake
index 3ee42a5..ddf3887 100644
--- a/Tests/RunCMake/GenEx-TARGET_FILE/TARGET_FILE_SUFFIX-imported-target.cmake
+++ b/Tests/RunCMake/GenEx-TARGET_FILE/TARGET_FILE_SUFFIX-imported-target.cmake
@@ -1,6 +1,3 @@
-
-cmake_minimum_required(VERSION 3.14)
-
 enable_language (C)
 
 set (win_platforms Windows CYGWIN MSYS)
diff --git a/Tests/RunCMake/GenEx-TARGET_FILE/TARGET_FILE_SUFFIX.cmake b/Tests/RunCMake/GenEx-TARGET_FILE/TARGET_FILE_SUFFIX.cmake
index 1fe75d9..9c5d932 100644
--- a/Tests/RunCMake/GenEx-TARGET_FILE/TARGET_FILE_SUFFIX.cmake
+++ b/Tests/RunCMake/GenEx-TARGET_FILE/TARGET_FILE_SUFFIX.cmake
@@ -1,6 +1,3 @@
-
-cmake_minimum_required(VERSION 3.14)
-
 enable_language (C)
 
 set (win_platforms Windows CYGWIN MSYS)
diff --git a/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/CMakeLists.txt b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/CMakeLists.txt
new file mode 100644
index 0000000..93ee9df
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/CMakeLists.txt
@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.5)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/RunCMakeTest.cmake b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/RunCMakeTest.cmake
new file mode 100644
index 0000000..04ff640
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/RunCMakeTest.cmake
@@ -0,0 +1,21 @@
+include(RunCMake)
+
+cmake_policy(SET CMP0057 NEW)
+
+function(run_cmake_with_config test)
+  if (NOT RunCMake_GENERATOR_IS_MULTI_CONFIG)
+    set(RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Release)
+  endif()
+  run_cmake(${test})
+endfunction()
+
+run_cmake(TARGET_LINKER_IMPORT_FILE-non-valid-target)
+run_cmake(TARGET_LINKER_LIBRARY_FILE-non-valid-target)
+run_cmake_with_config(TARGET_IMPORT_FILE)
+run_cmake_with_config(TARGET_IMPORT_FILE_SUFFIX)
+
+set (Windows_platforms Windows CYGWIN MSYS)
+if (NOT CMAKE_HOST_SYSTEM_NAME IN_LIST Windows_platforms)
+  run_cmake(TARGET_SONAME_IMPORT_FILE-non-valid-target)
+  run_cmake_with_config(TARGET_SONAME_IMPORT_FILE)
+endif()
diff --git a/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_IMPORT_FILE-check.cmake b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_IMPORT_FILE-check.cmake
new file mode 100644
index 0000000..9a101fc
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_IMPORT_FILE-check.cmake
@@ -0,0 +1 @@
+include ("${RunCMake_TEST_BINARY_DIR}/TARGET_IMPORT_FILE-Release-generated.cmake")
diff --git a/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_IMPORT_FILE.cmake b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_IMPORT_FILE.cmake
new file mode 100644
index 0000000..08765a6
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_IMPORT_FILE.cmake
@@ -0,0 +1,47 @@
+enable_language(C)
+
+set (platforms_with_import Windows CYGWIN MSYS)
+
+set (GENERATE_CONTENT [[
+macro (CHECK_VALUE test_msg value expected)
+  if (NOT "${value}" STREQUAL "${expected}")
+    string (APPEND RunCMake_TEST_FAILED "${test_msg}: actual result:\n [${value}]\nbut expected:\n [${expected}]\n")
+  endif()
+endmacro()
+]])
+
+add_library (shared1 SHARED empty.c)
+add_library (static1 STATIC empty.c)
+add_executable (exec1 empty.c)
+
+
+string (APPEND GENERATE_CONTENT
+"\ncheck_value (\"TARGET_IMPORT_FILE shared library\" \"$<TARGET_IMPORT_FILE:shared1>\" \"$<IF:$<IN_LIST:$<PLATFORM_ID>,${platforms_with_import}>,$<TARGET_LINKER_IMPORT_FILE:shared1>,>\")
+check_value (\"TARGET_LINKER_FILE shared library\" \"$<TARGET_LINKER_FILE:shared1>\" \"$<IF:$<IN_LIST:$<PLATFORM_ID>,${platforms_with_import}>,$<TARGET_LINKER_IMPORT_FILE:shared1>,$<TARGET_LINKER_LIBRARY_FILE:shared1>>\")
+check_value (\"TARGET_IMPORT_FILE static library\" \"$<TARGET_IMPORT_FILE:static1>\" \"\")
+check_value (\"TARGET_IMPORT_FILE executable\" \"$<TARGET_IMPORT_FILE:exec1>\" \"\")\n")
+
+
+set(lib_with_import ${platforms_with_import})
+set(exec_with_import ${platforms_with_import})
+if (APPLE AND CMAKE_TAPI)
+  list(APPEND lib_with_import Darwin)
+endif()
+if (CMAKE_SYSTEM_NAME STREQUAL "AIX")
+  list(APPEND exec_with_import "AIX")
+endif()
+set(CMAKE_SHARED_LIBRARY_ENABLE_EXPORTS TRUE)
+set(CMAKE_EXECUTABLE_ENABLE_EXPORTS TRUE)
+
+add_library (shared2 SHARED empty.c)
+add_executable (exec2 empty.c)
+
+
+string (APPEND GENERATE_CONTENT
+"\ncheck_value (\"TARGET_IMPORT_FILE shared library\" \"$<TARGET_IMPORT_FILE:shared2>\" \"$<IF:$<IN_LIST:$<PLATFORM_ID>,${lib_with_import}>,$<TARGET_LINKER_IMPORT_FILE:shared2>,>\")
+check_value (\"TARGET_LINKER_FILE shared library\" \"$<TARGET_LINKER_FILE:shared2>\" \"$<IF:$<IN_LIST:$<PLATFORM_ID>,${lib_with_import}>,$<TARGET_LINKER_IMPORT_FILE:shared2>,$<TARGET_LINKER_LIBRARY_FILE:shared2>>\")
+check_value (\"TARGET_IMPORT_FILE executable\" \"$<TARGET_IMPORT_FILE:exec2>\" \"$<IF:$<IN_LIST:$<PLATFORM_ID>,${exec_with_import}>,$<TARGET_LINKER_IMPORT_FILE:exec2>,>\")\n")
+
+
+file (GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/TARGET_IMPORT_FILE-$<CONFIG>-generated.cmake"
+  CONTENT "${GENERATE_CONTENT}")
diff --git a/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_IMPORT_FILE_SUFFIX-check.cmake b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_IMPORT_FILE_SUFFIX-check.cmake
new file mode 100644
index 0000000..2a1357a
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_IMPORT_FILE_SUFFIX-check.cmake
@@ -0,0 +1 @@
+include ("${RunCMake_TEST_BINARY_DIR}/TARGET_IMPORT_FILE_SUFFIX-Release-generated.cmake")
diff --git a/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_IMPORT_FILE_SUFFIX.cmake b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_IMPORT_FILE_SUFFIX.cmake
new file mode 100644
index 0000000..933471b
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_IMPORT_FILE_SUFFIX.cmake
@@ -0,0 +1,44 @@
+enable_language (C)
+
+set (platforms_with_import Windows CYGWIN MSYS)
+
+set (GENERATE_CONTENT [[
+macro (CHECK_VALUE test_msg value expected)
+  if (NOT "${value}" STREQUAL "${expected}")
+    string (APPEND RunCMake_TEST_FAILED "${test_msg}: actual result:\n [${value}]\nbut expected:\n [${expected}]\n")
+  endif()
+endmacro()
+]])
+
+add_library (shared1 SHARED empty.c)
+add_library (static1 STATIC empty.c)
+add_executable (exec1 empty.c)
+
+string (APPEND GENERATE_CONTENT
+"\ncheck_value (\"TARGET_IMPORT_FILE_SUFFIX executable default\" \"$<TARGET_IMPORT_FILE_SUFFIX:exec1>\" \"\")
+check_value (\"TARGET_IMPORT_FILE_SUFFIX shared default\" \"$<TARGET_IMPORT_FILE_SUFFIX:shared1>\" \"$<IF:$<IN_LIST:$<PLATFORM_ID>,${platforms_with_import}>,$<TARGET_LINKER_IMPORT_FILE_SUFFIX:shared1>,>\")
+check_value (\"TARGET_FILE_SUFFIX static default\" \"$<TARGET_IMPORT_FILE_SUFFIX:static1>\" \"\")
+check_value (\"TARGET_IMPORT_FILE_SUFFIX executable default\" \"$<TARGET_IMPORT_FILE_SUFFIX:exec1>\" \"\")\n")
+
+
+
+if (APPLE AND CMAKE_TAPI)
+  list(APPEND platforms_with_import Darwin)
+endif()
+if (CMAKE_SYSTEM_NAME STREQUAL "AIX")
+  list(APPEND platforms_with_import AIX)
+endif()
+set(CMAKE_SHARED_LIBRARY_ENABLE_EXPORTS TRUE)
+set(CMAKE_EXECUTABLE_ENABLE_EXPORTS TRUE)
+
+add_library (shared2 SHARED empty.c)
+add_executable (exec2 empty.c)
+
+string (APPEND GENERATE_CONTENT
+"\ncheck_value (\"TARGET_IMPORT_FILE_SUFFIX executable default\" \"$<TARGET_IMPORT_FILE_SUFFIX:exec2>\" \"$<IF:$<IN_LIST:$<PLATFORM_ID>,${platforms_with_import}>,$<TARGET_LINKER_IMPORT_FILE_SUFFIX:exec2>,>\")
+check_value (\"TARGET_IMPORT_FILE_SUFFIX shared default\" \"$<TARGET_IMPORT_FILE_SUFFIX:shared2>\" \"$<IF:$<IN_LIST:$<PLATFORM_ID>,${platforms_with_import}>,$<TARGET_LINKER_IMPORT_FILE_SUFFIX:shared2>,>\")\n")
+
+
+
+file (GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/TARGET_IMPORT_FILE_SUFFIX-$<CONFIG>-generated.cmake"
+  CONTENT "${GENERATE_CONTENT}")
diff --git a/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_LINKER_IMPORT_FILE-non-valid-target-result.txt b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_LINKER_IMPORT_FILE-non-valid-target-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_LINKER_IMPORT_FILE-non-valid-target-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_LINKER_IMPORT_FILE-non-valid-target-stderr.txt b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_LINKER_IMPORT_FILE-non-valid-target-stderr.txt
new file mode 100644
index 0000000..8ba2223
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_LINKER_IMPORT_FILE-non-valid-target-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at TARGET_LINKER_IMPORT_FILE-non-valid-target.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<TARGET_LINKER_IMPORT_FILE:exe1>
+
+  TARGET_LINKER_IMPORT_FILE is allowed only for libraries and executables
+  with ENABLE_EXPORTS.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_LINKER_IMPORT_FILE-non-valid-target.cmake b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_LINKER_IMPORT_FILE-non-valid-target.cmake
new file mode 100644
index 0000000..3f060cd
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_LINKER_IMPORT_FILE-non-valid-target.cmake
@@ -0,0 +1,9 @@
+
+enable_language(C)
+
+add_executable(exe1 empty.c)
+
+file(GENERATE
+  OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/test.txt"
+  CONTENT "[$<TARGET_LINKER_IMPORT_FILE:exe1>]"
+)
diff --git a/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_LINKER_LIBRARY_FILE-non-valid-target-result.txt b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_LINKER_LIBRARY_FILE-non-valid-target-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_LINKER_LIBRARY_FILE-non-valid-target-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_LINKER_LIBRARY_FILE-non-valid-target-stderr.txt b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_LINKER_LIBRARY_FILE-non-valid-target-stderr.txt
new file mode 100644
index 0000000..06e7b3a
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_LINKER_LIBRARY_FILE-non-valid-target-stderr.txt
@@ -0,0 +1,9 @@
+CMake Error at TARGET_LINKER_LIBRARY_FILE-non-valid-target.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<TARGET_LINKER_LIBRARY_FILE:exe1>
+
+  TARGET_LINKER_LIBRARY_FILE is allowed only for libraries with
+  ENABLE_EXPORTS.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_LINKER_LIBRARY_FILE-non-valid-target.cmake b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_LINKER_LIBRARY_FILE-non-valid-target.cmake
new file mode 100644
index 0000000..bb95546
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_LINKER_LIBRARY_FILE-non-valid-target.cmake
@@ -0,0 +1,9 @@
+
+enable_language(C)
+
+add_executable(exe1 empty.c)
+
+file(GENERATE
+  OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/test.txt"
+  CONTENT "[$<TARGET_LINKER_LIBRARY_FILE:exe1>]"
+)
diff --git a/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_SONAME_IMPORT_FILE-check.cmake b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_SONAME_IMPORT_FILE-check.cmake
new file mode 100644
index 0000000..ab4443e
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_SONAME_IMPORT_FILE-check.cmake
@@ -0,0 +1 @@
+include ("${RunCMake_TEST_BINARY_DIR}/TARGET_SONAME_IMPORT_FILE-Release-generated.cmake")
diff --git a/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_SONAME_IMPORT_FILE-non-valid-target-result.txt b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_SONAME_IMPORT_FILE-non-valid-target-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_SONAME_IMPORT_FILE-non-valid-target-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_SONAME_IMPORT_FILE-non-valid-target-stderr.txt b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_SONAME_IMPORT_FILE-non-valid-target-stderr.txt
new file mode 100644
index 0000000..0640088
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_SONAME_IMPORT_FILE-non-valid-target-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at TARGET_SONAME_IMPORT_FILE-non-valid-target.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<TARGET_SONAME_IMPORT_FILE:static1>
+
+  TARGET_SONAME_IMPORT_FILE is allowed only for SHARED libraries.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_SONAME_IMPORT_FILE-non-valid-target.cmake b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_SONAME_IMPORT_FILE-non-valid-target.cmake
new file mode 100644
index 0000000..cc79580
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_SONAME_IMPORT_FILE-non-valid-target.cmake
@@ -0,0 +1,8 @@
+enable_language(C)
+
+add_library (static1 STATIC empty.c)
+set_property (TARGET static1 PROPERTY VERSION 2.5.0)
+set_property (TARGET static1 PROPERTY SOVERSION 2.0.0)
+
+file (GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/test.txt"
+  CONTENT "[$<TARGET_SONAME_IMPORT_FILE:static1>]")
diff --git a/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_SONAME_IMPORT_FILE.cmake b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_SONAME_IMPORT_FILE.cmake
new file mode 100644
index 0000000..02ba513
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/TARGET_SONAME_IMPORT_FILE.cmake
@@ -0,0 +1,32 @@
+enable_language(C)
+
+set (GENERATE_CONTENT [[
+macro (CHECK_VALUE test_msg value expected)
+  if (NOT "${value}" STREQUAL "${expected}")
+    string (APPEND RunCMake_TEST_FAILED "${test_msg}: actual result:\n [${value}]\nbut expected:\n [${expected}]\n")
+  endif()
+endmacro()
+]])
+
+add_library (shared1 SHARED empty.c)
+set_property (TARGET shared1 PROPERTY VERSION 2.5.0)
+set_property (TARGET shared1 PROPERTY SOVERSION 2.0.0)
+
+
+string (APPEND GENERATE_CONTENT
+"\ncheck_value (\"TARGET_SONAME_IMPORT_FILE shared library\" \"$<TARGET_SONAME_IMPORT_FILE:shared1>\" \"\")\n")
+
+
+
+add_library (shared2 SHARED empty.c)
+set_property(TARGET shared2 PROPERTY ENABLE_EXPORTS ON)
+set_property (TARGET shared2 PROPERTY VERSION 2.5.0)
+set_property (TARGET shared2 PROPERTY SOVERSION 2.0.0)
+
+
+string (APPEND GENERATE_CONTENT
+"\ncheck_value (\"TARGET_SONAME_IMPORT_FILE shared library\" \"$<TARGET_SONAME_IMPORT_FILE:shared2>\" \"$<$<BOOL:${CMAKE_TAPI}>:$<PATH:REPLACE_EXTENSION,LAST_ONLY,$<TARGET_SONAME_FILE:shared2>,.tbd>>\")\n")
+
+
+file (GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/TARGET_SONAME_IMPORT_FILE-$<CONFIG>-generated.cmake"
+  CONTENT "${GENERATE_CONTENT}")
diff --git a/Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt b/Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/empty.c
similarity index 100%
copy from Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt
copy to Tests/RunCMake/GenEx-TARGET_IMPORT_FILE/empty.c
diff --git a/Tests/RunCMake/GenEx-TARGET_PROPERTY/ALIAS_GLOBAL.cmake b/Tests/RunCMake/GenEx-TARGET_PROPERTY/ALIAS_GLOBAL.cmake
index 212c034..eed194b 100644
--- a/Tests/RunCMake/GenEx-TARGET_PROPERTY/ALIAS_GLOBAL.cmake
+++ b/Tests/RunCMake/GenEx-TARGET_PROPERTY/ALIAS_GLOBAL.cmake
@@ -1,6 +1,3 @@
-
-cmake_minimum_required(VERSION 3.17)
-
 add_library(lib-global SHARED IMPORTED GLOBAL)
 add_library(alias-lib-global ALIAS lib-global)
 
diff --git a/Tests/RunCMake/GenEx-TARGET_PROPERTY/CMakeLists.txt b/Tests/RunCMake/GenEx-TARGET_PROPERTY/CMakeLists.txt
index 26a73f9..32d92d8 100644
--- a/Tests/RunCMake/GenEx-TARGET_PROPERTY/CMakeLists.txt
+++ b/Tests/RunCMake/GenEx-TARGET_PROPERTY/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.10)
 if(RunCMake_TEST STREQUAL "LOCATION")
   cmake_minimum_required(VERSION 2.8.12) # Leave CMP0026 unset.
 endif()
diff --git a/Tests/RunCMake/GenEx-TARGET_PROPERTY/INCLUDE_DIRECTORIES.cmake b/Tests/RunCMake/GenEx-TARGET_PROPERTY/INCLUDE_DIRECTORIES.cmake
index e9855be..0f0c399 100644
--- a/Tests/RunCMake/GenEx-TARGET_PROPERTY/INCLUDE_DIRECTORIES.cmake
+++ b/Tests/RunCMake/GenEx-TARGET_PROPERTY/INCLUDE_DIRECTORIES.cmake
@@ -1,4 +1,3 @@
-cmake_minimum_required(VERSION 3.14)
 enable_language(C)
 
 add_library(foo1 STATIC empty.c)
diff --git a/Tests/RunCMake/GenEx-TARGET_PROPERTY/LOCATION-stderr.txt b/Tests/RunCMake/GenEx-TARGET_PROPERTY/LOCATION-stderr.txt
index a4c8dcd..fab2ce2 100644
--- a/Tests/RunCMake/GenEx-TARGET_PROPERTY/LOCATION-stderr.txt
+++ b/Tests/RunCMake/GenEx-TARGET_PROPERTY/LOCATION-stderr.txt
@@ -1,3 +1,10 @@
+^CMake Deprecation Warning at CMakeLists\.txt:3 \(cmake_minimum_required\):
+  Compatibility with CMake < 3\.5 will be removed from a future version of
+  CMake\.
+
+  Update the VERSION argument <min> value or use a \.\.\.<max> suffix to tell
+  CMake that the project does not need compatibility with older versions\.
++
 CMake Warning \(dev\) in CMakeLists\.txt:
   Policy CMP0026 is not set: Disallow use of the LOCATION target property.
   Run "cmake --help-policy CMP0026" for policy details.  Use the cmake_policy
diff --git a/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/check.cmake b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/check.cmake
deleted file mode 100644
index e19598e..0000000
--- a/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/check.cmake
+++ /dev/null
@@ -1,15 +0,0 @@
-function(check_genex expected actual)
-  if(NOT expected STREQUAL actual)
-    string(APPEND RunCMake_TEST_FAILED "Expected DLLs:\n")
-    foreach(dll IN LISTS expected)
-      string(APPEND RunCMake_TEST_FAILED "  ${dll}\n")
-    endforeach()
-    string(APPEND RunCMake_TEST_FAILED "Actual DLLs:\n")
-    foreach(dll IN LISTS actual)
-      string(APPEND RunCMake_TEST_FAILED "  ${dll}\n")
-    endforeach()
-  endif()
-  set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE)
-endfunction()
-
-include("${RunCMake_TEST_BINARY_DIR}/dlls.cmake")
diff --git a/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/shared-check.cmake b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/shared-check.cmake
new file mode 100644
index 0000000..6b05b4e
--- /dev/null
+++ b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/shared-check.cmake
@@ -0,0 +1,15 @@
+function(check_genex expected actual)
+  if(NOT expected STREQUAL actual)
+    string(APPEND RunCMake_TEST_FAILED "Expected items:\n")
+    foreach(item IN LISTS expected)
+      string(APPEND RunCMake_TEST_FAILED "  ${item}\n")
+    endforeach()
+    string(APPEND RunCMake_TEST_FAILED "Actual items:\n")
+    foreach(item IN LISTS actual)
+      string(APPEND RunCMake_TEST_FAILED "  ${item}\n")
+    endforeach()
+  endif()
+  set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE)
+endfunction()
+
+include("${RunCMake_TEST_BINARY_DIR}/dlls.cmake")
diff --git a/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/shared.cmake b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/shared.cmake
index 806f0b6..c38fa39 100644
--- a/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/shared.cmake
+++ b/Tests/RunCMake/GenEx-TARGET_RUNTIME_DLLS/shared.cmake
@@ -4,6 +4,10 @@
 add_library(lib1 SHARED lib1.c)
 add_library(lib2 SHARED lib2.c)
 add_library(lib3 SHARED lib3.c)
+if(WIN32 OR CYGWIN)
+  set_property(TARGET lib3 PROPERTY RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/SomeSubDir/")
+endif()
+
 add_library(static STATIC static.c)
 add_library(imported SHARED IMPORTED)
 set_property(TARGET imported PROPERTY IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/imported.dll")
@@ -26,9 +30,16 @@
     "$<TARGET_FILE:lib3>"
     "$<TARGET_FILE:lib2>"
     )
+  set(expected_dll_dirs
+    "$<PATH:GET_PARENT_PATH,$<TARGET_FILE:lib2>>"
+    "$<PATH:GET_PARENT_PATH,$<TARGET_FILE:imported>>"
+    "$<PATH:GET_PARENT_PATH,$<TARGET_FILE:lib3>>"
+    )
 endif()
 
-set(content "check_genex(\"${expected_dlls}\" \"$<TARGET_RUNTIME_DLLS:exe>\")\n")
+set(content "check_genex(\"${expected_dlls}\" \"$<TARGET_RUNTIME_DLLS:exe>\")
+check_genex(\"${expected_dll_dirs}\" \"$<TARGET_RUNTIME_DLL_DIRS:exe>\")\n")
+
 set(condition)
 get_property(multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
 if(multi_config)
diff --git a/Tests/RunCMake/GenerateExportHeader/GEH.cmake b/Tests/RunCMake/GenerateExportHeader/GEH.cmake
index bf9c302..3e35aa3 100644
--- a/Tests/RunCMake/GenerateExportHeader/GEH.cmake
+++ b/Tests/RunCMake/GenerateExportHeader/GEH.cmake
@@ -100,7 +100,9 @@
     set(_platform Win32-Clang)
   elseif(MSVC AND COMPILER_HAS_DEPRECATED)
     set(_platform Win32)
-  elseif((MINGW OR CYGWIN) AND COMPILER_HAS_DEPRECATED)
+  elseif(CYGWIN AND COMPILER_HAS_DEPRECATED)
+    set(_platform Cygwin)
+  elseif(MINGW AND COMPILER_HAS_DEPRECATED)
     set(_platform MinGW)
   else()
     set(_platform WinEmpty)
diff --git a/Tests/RunCMake/GenerateExportHeader/reference/Cygwin/libshared_export.h b/Tests/RunCMake/GenerateExportHeader/reference/Cygwin/libshared_export.h
new file mode 100644
index 0000000..dac4fda
--- /dev/null
+++ b/Tests/RunCMake/GenerateExportHeader/reference/Cygwin/libshared_export.h
@@ -0,0 +1,42 @@
+
+#ifndef LIBSHARED_EXPORT_H
+#define LIBSHARED_EXPORT_H
+
+#ifdef LIBSHARED_STATIC_DEFINE
+#  define LIBSHARED_EXPORT
+#  define LIBSHARED_NO_EXPORT
+#else
+#  ifndef LIBSHARED_EXPORT
+#    ifdef libshared_EXPORTS
+        /* We are building this library */
+#      define LIBSHARED_EXPORT __declspec(dllexport)
+#    else
+        /* We are using this library */
+#      define LIBSHARED_EXPORT __declspec(dllimport)
+#    endif
+#  endif
+
+#  ifndef LIBSHARED_NO_EXPORT
+#    define LIBSHARED_NO_EXPORT
+#  endif
+#endif
+
+#ifndef LIBSHARED_DEPRECATED
+#  define LIBSHARED_DEPRECATED __attribute__ ((__deprecated__))
+#endif
+
+#ifndef LIBSHARED_DEPRECATED_EXPORT
+#  define LIBSHARED_DEPRECATED_EXPORT LIBSHARED_EXPORT LIBSHARED_DEPRECATED
+#endif
+
+#ifndef LIBSHARED_DEPRECATED_NO_EXPORT
+#  define LIBSHARED_DEPRECATED_NO_EXPORT LIBSHARED_NO_EXPORT LIBSHARED_DEPRECATED
+#endif
+
+#if 0 /* DEFINE_NO_DEPRECATED */
+#  ifndef LIBSHARED_NO_DEPRECATED
+#    define LIBSHARED_NO_DEPRECATED
+#  endif
+#endif
+
+#endif /* LIBSHARED_EXPORT_H */
diff --git a/Tests/RunCMake/GenerateExportHeader/reference/Cygwin/libstatic_export.h b/Tests/RunCMake/GenerateExportHeader/reference/Cygwin/libstatic_export.h
new file mode 100644
index 0000000..b6e2a4a
--- /dev/null
+++ b/Tests/RunCMake/GenerateExportHeader/reference/Cygwin/libstatic_export.h
@@ -0,0 +1,42 @@
+
+#ifndef LIBSTATIC_EXPORT_H
+#define LIBSTATIC_EXPORT_H
+
+#ifdef LIBSTATIC_STATIC_DEFINE
+#  define LIBSTATIC_EXPORT
+#  define LIBSTATIC_NO_EXPORT
+#else
+#  ifndef LIBSTATIC_EXPORT
+#    ifdef libstatic_EXPORTS
+        /* We are building this library */
+#      define LIBSTATIC_EXPORT
+#    else
+        /* We are using this library */
+#      define LIBSTATIC_EXPORT
+#    endif
+#  endif
+
+#  ifndef LIBSTATIC_NO_EXPORT
+#    define LIBSTATIC_NO_EXPORT
+#  endif
+#endif
+
+#ifndef LIBSTATIC_DEPRECATED
+#  define LIBSTATIC_DEPRECATED __attribute__ ((__deprecated__))
+#endif
+
+#ifndef LIBSTATIC_DEPRECATED_EXPORT
+#  define LIBSTATIC_DEPRECATED_EXPORT LIBSTATIC_EXPORT LIBSTATIC_DEPRECATED
+#endif
+
+#ifndef LIBSTATIC_DEPRECATED_NO_EXPORT
+#  define LIBSTATIC_DEPRECATED_NO_EXPORT LIBSTATIC_NO_EXPORT LIBSTATIC_DEPRECATED
+#endif
+
+#if 0 /* DEFINE_NO_DEPRECATED */
+#  ifndef LIBSTATIC_NO_DEPRECATED
+#    define LIBSTATIC_NO_DEPRECATED
+#  endif
+#endif
+
+#endif /* LIBSTATIC_EXPORT_H */
diff --git a/Tests/RunCMake/GenerateExportHeader/reference/MinGW/libshared_export.h b/Tests/RunCMake/GenerateExportHeader/reference/MinGW/libshared_export.h
index dac4fda..3ba2d2e 100644
--- a/Tests/RunCMake/GenerateExportHeader/reference/MinGW/libshared_export.h
+++ b/Tests/RunCMake/GenerateExportHeader/reference/MinGW/libshared_export.h
@@ -22,7 +22,7 @@
 #endif
 
 #ifndef LIBSHARED_DEPRECATED
-#  define LIBSHARED_DEPRECATED __attribute__ ((__deprecated__))
+#  define LIBSHARED_DEPRECATED __declspec(deprecated)
 #endif
 
 #ifndef LIBSHARED_DEPRECATED_EXPORT
diff --git a/Tests/RunCMake/GenerateExportHeader/reference/MinGW/libstatic_export.h b/Tests/RunCMake/GenerateExportHeader/reference/MinGW/libstatic_export.h
index b6e2a4a..3c7e093 100644
--- a/Tests/RunCMake/GenerateExportHeader/reference/MinGW/libstatic_export.h
+++ b/Tests/RunCMake/GenerateExportHeader/reference/MinGW/libstatic_export.h
@@ -22,7 +22,7 @@
 #endif
 
 #ifndef LIBSTATIC_DEPRECATED
-#  define LIBSTATIC_DEPRECATED __attribute__ ((__deprecated__))
+#  define LIBSTATIC_DEPRECATED __declspec(deprecated)
 #endif
 
 #ifndef LIBSTATIC_DEPRECATED_EXPORT
diff --git a/Tests/RunCMake/GenerateExportHeader/reference/Win32-Clang/libshared_export.h b/Tests/RunCMake/GenerateExportHeader/reference/Win32-Clang/libshared_export.h
index dac4fda..3ba2d2e 100644
--- a/Tests/RunCMake/GenerateExportHeader/reference/Win32-Clang/libshared_export.h
+++ b/Tests/RunCMake/GenerateExportHeader/reference/Win32-Clang/libshared_export.h
@@ -22,7 +22,7 @@
 #endif
 
 #ifndef LIBSHARED_DEPRECATED
-#  define LIBSHARED_DEPRECATED __attribute__ ((__deprecated__))
+#  define LIBSHARED_DEPRECATED __declspec(deprecated)
 #endif
 
 #ifndef LIBSHARED_DEPRECATED_EXPORT
diff --git a/Tests/RunCMake/GenerateExportHeader/reference/Win32-Clang/libstatic_export.h b/Tests/RunCMake/GenerateExportHeader/reference/Win32-Clang/libstatic_export.h
index b6e2a4a..3c7e093 100644
--- a/Tests/RunCMake/GenerateExportHeader/reference/Win32-Clang/libstatic_export.h
+++ b/Tests/RunCMake/GenerateExportHeader/reference/Win32-Clang/libstatic_export.h
@@ -22,7 +22,7 @@
 #endif
 
 #ifndef LIBSTATIC_DEPRECATED
-#  define LIBSTATIC_DEPRECATED __attribute__ ((__deprecated__))
+#  define LIBSTATIC_DEPRECATED __declspec(deprecated)
 #endif
 
 #ifndef LIBSTATIC_DEPRECATED_EXPORT
diff --git a/Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling-result.txt b/Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling-stderr.txt b/Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling-stderr.txt
new file mode 100644
index 0000000..8c93d59
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at COMPILE_ONLY-not-compiling.cmake:1 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<COMPILE_ONLY:something>
+
+  \$<COMPILE_ONLY:...> may only be used for linking
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling.cmake b/Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling.cmake
new file mode 100644
index 0000000..1bc75f9
--- /dev/null
+++ b/Tests/RunCMake/GeneratorExpression/COMPILE_ONLY-not-compiling.cmake
@@ -0,0 +1 @@
+add_custom_target(Custom ALL COMMAND ${CMAKE_COMMAND} -E echo $<COMPILE_ONLY:something>)
diff --git a/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake b/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake
index 2d545d9..3fd9947 100644
--- a/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake
+++ b/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 run_cmake(BadIF)
 run_cmake(BadCONFIG)
@@ -21,6 +22,7 @@
 run_cmake(NonValidTarget-Fortran_COMPILER_VERSION)
 run_cmake(NonValidTarget-TARGET_PROPERTY)
 run_cmake(NonValidTarget-TARGET_POLICY)
+run_cmake(COMPILE_ONLY-not-compiling)
 run_cmake(LINK_ONLY-not-linking)
 run_cmake(TARGET_EXISTS-no-arg)
 run_cmake(TARGET_EXISTS-empty-arg)
diff --git a/Tests/RunCMake/GeneratorPlatform/BadFieldNoComma-result.txt b/Tests/RunCMake/GeneratorPlatform/BadFieldNoComma-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorPlatform/BadFieldNoComma-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GeneratorPlatform/BadFieldNoComma-stderr.txt b/Tests/RunCMake/GeneratorPlatform/BadFieldNoComma-stderr.txt
new file mode 100644
index 0000000..b2098bd
--- /dev/null
+++ b/Tests/RunCMake/GeneratorPlatform/BadFieldNoComma-stderr.txt
@@ -0,0 +1,11 @@
+^CMake Error at CMakeLists.txt:[0-9]+ \(project\):
+  Generator
+
+    Visual Studio [^
+]+
+
+  given platform specification
+
+    Test Platform,nocomma
+
+  that contains a field after the first ',' with no '='\.$
diff --git a/Tests/RunCMake/GeneratorPlatform/BadFieldNoComma.cmake b/Tests/RunCMake/GeneratorPlatform/BadFieldNoComma.cmake
new file mode 100644
index 0000000..2fc38e5
--- /dev/null
+++ b/Tests/RunCMake/GeneratorPlatform/BadFieldNoComma.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached!")
diff --git a/Tests/RunCMake/GeneratorPlatform/BadFieldUnknown-result.txt b/Tests/RunCMake/GeneratorPlatform/BadFieldUnknown-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorPlatform/BadFieldUnknown-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GeneratorPlatform/BadFieldUnknown-stderr.txt b/Tests/RunCMake/GeneratorPlatform/BadFieldUnknown-stderr.txt
new file mode 100644
index 0000000..654f920
--- /dev/null
+++ b/Tests/RunCMake/GeneratorPlatform/BadFieldUnknown-stderr.txt
@@ -0,0 +1,11 @@
+^CMake Error at CMakeLists.txt:[0-9]+ \(project\):
+  Generator
+
+    Visual Studio [^
+]+
+
+  given platform specification
+
+    Test Platform,unknown=
+
+  that contains invalid field 'unknown='\.$
diff --git a/Tests/RunCMake/GeneratorPlatform/BadFieldUnknown.cmake b/Tests/RunCMake/GeneratorPlatform/BadFieldUnknown.cmake
new file mode 100644
index 0000000..2fc38e5
--- /dev/null
+++ b/Tests/RunCMake/GeneratorPlatform/BadFieldUnknown.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached!")
diff --git a/Tests/RunCMake/GeneratorPlatform/BadVersionEmpty-result.txt b/Tests/RunCMake/GeneratorPlatform/BadVersionEmpty-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorPlatform/BadVersionEmpty-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GeneratorPlatform/BadVersionEmpty-stderr.txt b/Tests/RunCMake/GeneratorPlatform/BadVersionEmpty-stderr.txt
new file mode 100644
index 0000000..1b7804d
--- /dev/null
+++ b/Tests/RunCMake/GeneratorPlatform/BadVersionEmpty-stderr.txt
@@ -0,0 +1,11 @@
+^CMake Error at CMakeLists.txt:[0-9]+ \(project\):
+  Generator
+
+    Visual Studio [^
+]+
+
+  given platform specification with empty
+
+    version=
+
+  field\.$
diff --git a/Tests/RunCMake/GeneratorPlatform/BadVersionEmpty.cmake b/Tests/RunCMake/GeneratorPlatform/BadVersionEmpty.cmake
new file mode 100644
index 0000000..2fc38e5
--- /dev/null
+++ b/Tests/RunCMake/GeneratorPlatform/BadVersionEmpty.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached!")
diff --git a/Tests/RunCMake/GeneratorPlatform/BadVersionMissing-result.txt b/Tests/RunCMake/GeneratorPlatform/BadVersionMissing-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorPlatform/BadVersionMissing-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GeneratorPlatform/BadVersionMissing-stderr.txt b/Tests/RunCMake/GeneratorPlatform/BadVersionMissing-stderr.txt
new file mode 100644
index 0000000..d82eb0b
--- /dev/null
+++ b/Tests/RunCMake/GeneratorPlatform/BadVersionMissing-stderr.txt
@@ -0,0 +1,11 @@
+^CMake Error at CMakeLists.txt:[0-9]+ \(project\):
+  Generator
+
+    Visual Studio [^
+]+
+
+  given platform specification with
+
+    version=1\.2\.3\.4
+
+  field, but no Windows SDK with that version was found\.$
diff --git a/Tests/RunCMake/GeneratorPlatform/BadVersionMissing.cmake b/Tests/RunCMake/GeneratorPlatform/BadVersionMissing.cmake
new file mode 100644
index 0000000..2fc38e5
--- /dev/null
+++ b/Tests/RunCMake/GeneratorPlatform/BadVersionMissing.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached!")
diff --git a/Tests/RunCMake/GeneratorPlatform/BadVersionPlatform-result.txt b/Tests/RunCMake/GeneratorPlatform/BadVersionPlatform-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/GeneratorPlatform/BadVersionPlatform-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/GeneratorPlatform/BadVersionPlatform-stderr.txt b/Tests/RunCMake/GeneratorPlatform/BadVersionPlatform-stderr.txt
new file mode 100644
index 0000000..d3c62e3
--- /dev/null
+++ b/Tests/RunCMake/GeneratorPlatform/BadVersionPlatform-stderr.txt
@@ -0,0 +1,19 @@
+^CMake Error at CMakeLists.txt:[0-9]+ \(project\):
+  Generator
+
+    Visual Studio [^
+]+
+
+  given platform specification (containing a
+
+    version=8\.1
+
+  field\.  The version field is not supported when targeting
+
+    Windows 8\.1(
+
+  with the Windows 8\.1 SDK installed\.)?|with
+
+    version=8\.1
+
+  field, but no Windows SDK with that version was found\.)$
diff --git a/Tests/RunCMake/GeneratorPlatform/BadVersionPlatform.cmake b/Tests/RunCMake/GeneratorPlatform/BadVersionPlatform.cmake
new file mode 100644
index 0000000..2fc38e5
--- /dev/null
+++ b/Tests/RunCMake/GeneratorPlatform/BadVersionPlatform.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached!")
diff --git a/Tests/RunCMake/GeneratorPlatform/CMakeLists.txt b/Tests/RunCMake/GeneratorPlatform/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/GeneratorPlatform/CMakeLists.txt
+++ b/Tests/RunCMake/GeneratorPlatform/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/GeneratorPlatform/RunCMakeTest.cmake b/Tests/RunCMake/GeneratorPlatform/RunCMakeTest.cmake
index a7519c3..233eb0a 100644
--- a/Tests/RunCMake/GeneratorPlatform/RunCMakeTest.cmake
+++ b/Tests/RunCMake/GeneratorPlatform/RunCMakeTest.cmake
@@ -26,3 +26,86 @@
   run_cmake(BadPlatformToolchain)
   unset(RunCMake_TEST_OPTIONS)
 endif()
+
+if("${RunCMake_GENERATOR}" MATCHES "^Visual Studio (1[4567])( 20[0-9][0-9])?$")
+  unset(ENV{WindowsSDKVersion})
+
+  set(RunCMake_GENERATOR_PLATFORM "Test Platform,nocomma")
+  run_cmake(BadFieldNoComma)
+  set(RunCMake_GENERATOR_PLATFORM "Test Platform,unknown=")
+  run_cmake(BadFieldUnknown)
+  set(RunCMake_GENERATOR_PLATFORM "version=")
+  run_cmake(BadVersionEmpty)
+  set(RunCMake_GENERATOR_PLATFORM "version=1.2.3.4")
+  run_cmake(BadVersionMissing)
+  set(RunCMake_GENERATOR_PLATFORM "version=8.1")
+  run_cmake_with_options(BadVersionPlatform -DCMAKE_SYSTEM_VERSION=8.1)
+
+  if(NOT RunCMake_GENERATOR MATCHES "^Visual Studio (1[45]) ")
+    set(expect_version "10.0")
+    set(RunCMake_GENERATOR_PLATFORM "version=${expect_version}")
+    set(RunCMake_TEST_VARIANT_DESCRIPTION "-${expect_version}")
+    run_cmake_with_options(VersionExists -DCMAKE_SYSTEM_VERSION=10.0)
+    unset(RunCMake_GENERATOR_PLATFORM)
+  endif()
+
+  set(kits "")
+  cmake_host_system_information(RESULT kitsRoot10
+    QUERY WINDOWS_REGISTRY "HKLM/SOFTWARE/Microsoft/Windows Kits/Installed Roots"
+    VALUE "KitsRoot10"
+    VIEW 64_32
+    ERROR_VARIABLE kitsRoot10Error
+    )
+  if(NOT kitsRoot10Error AND IS_DIRECTORY "${kitsRoot10}/include")
+    cmake_path(SET kitsInclude "${kitsRoot10}/include")
+    file(GLOB kits RELATIVE "${kitsInclude}" "${kitsInclude}/*/um/windows.h")
+    list(TRANSFORM kits REPLACE "/.*" "")
+  endif()
+  if(kits)
+    message(STATUS "Available Kits: ${kits}")
+    if(RunCMake_GENERATOR MATCHES "^Visual Studio 14 ")
+      set(kitMax 10.0.14393.0)
+    else()
+      set(kitMax "")
+    endif()
+    if(kitMax)
+      set(kitsIn "${kits}")
+      set(kits "")
+      foreach(kit IN LISTS kitsIn)
+        if(kit VERSION_LESS_EQUAL "${kitMax}")
+          list(APPEND kits "${kit}")
+        else()
+          message(STATUS "Excluding Kit ${kit} > ${kitMax}")
+        endif()
+      endforeach()
+    endif()
+  elseif(NOT RunCMake_GENERATOR MATCHES "^Visual Studio 14 ")
+    message(FATAL_ERROR "Could not find any Windows SDKs to drive test cases.")
+  endif()
+
+  if(kits)
+    foreach(expect_version IN LISTS kits)
+      set(RunCMake_GENERATOR_PLATFORM "version=${expect_version}")
+      set(RunCMake_TEST_VARIANT_DESCRIPTION "-${expect_version}")
+      run_cmake_with_options(VersionExists -DCMAKE_SYSTEM_VERSION=10.0)
+      unset(RunCMake_GENERATOR_PLATFORM)
+    endforeach()
+    foreach(expect_version IN LISTS kits)
+      set(RunCMake_TEST_VARIANT_DESCRIPTION "-CMP0149-OLD-${expect_version}")
+      run_cmake_with_options(VersionExists -DCMAKE_SYSTEM_VERSION=${expect_version} -DCMAKE_POLICY_DEFAULT_CMP0149=OLD)
+    endforeach()
+    if(kits MATCHES "(^|;)([0-9.]+)$")
+      set(expect_version "${CMAKE_MATCH_2}")
+      foreach(test_version IN LISTS kits)
+        set(RunCMake_TEST_VARIANT_DESCRIPTION "-CMP0149-NEW-${test_version}")
+        run_cmake_with_options(VersionExists -DCMAKE_SYSTEM_VERSION=${test_version} -DCMAKE_POLICY_DEFAULT_CMP0149=NEW)
+      endforeach()
+    endif()
+    foreach(expect_version IN LISTS kits)
+      set(RunCMake_TEST_VARIANT_DESCRIPTION "-WindowsSDKVersion-${expect_version}")
+      set(ENV{WindowsSDKVersion} "${expect_version}\\")
+      run_cmake_with_options(VersionExists -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_POLICY_DEFAULT_CMP0149=NEW)
+      unset(ENV{WindowsSDKVersion})
+    endforeach()
+  endif()
+endif()
diff --git a/Tests/RunCMake/GeneratorPlatform/VersionExists-check.cmake b/Tests/RunCMake/GeneratorPlatform/VersionExists-check.cmake
new file mode 100644
index 0000000..6c3c8e5
--- /dev/null
+++ b/Tests/RunCMake/GeneratorPlatform/VersionExists-check.cmake
@@ -0,0 +1,9 @@
+if(actual_stdout MATCHES "CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION='([^']+)'")
+  set(actual_version "${CMAKE_MATCH_1}")
+  if(NOT "${actual_version}" STREQUAL "${expect_version}")
+    set(RunCMake_TEST_FAILED "Actual SDK version '${actual_version}' did not match expected '${expect_version}'")
+    return()
+  endif()
+else()
+  set(RunCMake_TEST_FAILED "No CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION found in output.")
+endif()
diff --git a/Tests/RunCMake/GeneratorPlatform/VersionExists.cmake b/Tests/RunCMake/GeneratorPlatform/VersionExists.cmake
new file mode 100644
index 0000000..5c30e2b
--- /dev/null
+++ b/Tests/RunCMake/GeneratorPlatform/VersionExists.cmake
@@ -0,0 +1,5 @@
+cmake_policy(GET CMP0149 cmp0149)
+message(STATUS "CMP0149='${cmp0149}'")
+message(STATUS "CMAKE_SYSTEM_VERSION='${CMAKE_SYSTEM_VERSION}'")
+message(STATUS "ENV{WindowsSDKVersion}='$ENV{WindowsSDKVersion}'")
+message(STATUS "CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION='${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}'")
diff --git a/Tests/RunCMake/GeneratorToolset/CMakeLists.txt b/Tests/RunCMake/GeneratorToolset/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/GeneratorToolset/CMakeLists.txt
+++ b/Tests/RunCMake/GeneratorToolset/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/GeneratorToolset/TestToolsetCustomFlagTableDir-check.cmake b/Tests/RunCMake/GeneratorToolset/TestToolsetCustomFlagTableDir-check.cmake
index 79752b1..f95173b 100644
--- a/Tests/RunCMake/GeneratorToolset/TestToolsetCustomFlagTableDir-check.cmake
+++ b/Tests/RunCMake/GeneratorToolset/TestToolsetCustomFlagTableDir-check.cmake
@@ -1,6 +1,6 @@
 set(vcProjectFile "${RunCMake_TEST_BINARY_DIR}/main.vcxproj")
 if(NOT EXISTS "${vcProjectFile}")
-  set(RunCMake_TEST_FAILED "Project file\n  ${vcProjectFile}\ndoes not exist.")
+  string(CONCAT RunCMake_TEST_FAILED "Project file\n  ${vcProjectFile}\n" "does not exist.")
   return()
 endif()
 
diff --git a/Tests/RunCMake/GoogleTest/GoogleTestXML.cmake b/Tests/RunCMake/GoogleTest/GoogleTestXML.cmake
index 53eedc0..308bdbf 100644
--- a/Tests/RunCMake/GoogleTest/GoogleTestXML.cmake
+++ b/Tests/RunCMake/GoogleTest/GoogleTestXML.cmake
@@ -5,13 +5,13 @@
 
 include(xcode_sign_adhoc.cmake)
 
-# This creates the folder structure for the paramterized tests
+# This creates the folder structure for the parameterized tests
 # to avoid handling missing folders in C++
 #
 # This must match the match the name defined in xml_output.cpp
 # for every instance of tests with GetParam.
 #
-# The folder name is created fom the test name (output of the line
+# The folder name is created from the test name (output of the line
 # without leading spaces: "GoogleTestXMLSpecial/cases.") and
 # the parts until the last slash ("case/"). These parts are concatenated.
 file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/GoogleTestXMLSpecial/cases.case")
diff --git a/Tests/RunCMake/IfacePaths/BinInInstallPrefix-CMP0052-OLD-stderr.txt b/Tests/RunCMake/IfacePaths/BinInInstallPrefix-CMP0052-OLD-stderr.txt
index 37747a1..d0aef2c 100644
--- a/Tests/RunCMake/IfacePaths/BinInInstallPrefix-CMP0052-OLD-stderr.txt
+++ b/Tests/RunCMake/IfacePaths/BinInInstallPrefix-CMP0052-OLD-stderr.txt
@@ -1,6 +1,13 @@
-^CMake Deprecation Warning at CMakeLists.txt:1 \(cmake_minimum_required\):
+^CMake Deprecation Warning at CMakeLists\.txt:[0-9]+ \(cmake_minimum_required\):
+  Compatibility with CMake < 3\.5 will be removed from a future version of
+  CMake\.
+
+  Update the VERSION argument <min> value or use a \.\.\.<max> suffix to tell
+  CMake that the project does not need compatibility with older versions\.
++
+CMake Deprecation Warning at CMakeLists.txt:[0-9]+ \(cmake_minimum_required\):
   The OLD behavior for policy CMP0052 will be removed from a future version
-  of CMake.
+  of CMake\.
 
   The cmake-policies\(7\) manual explains that the OLD behaviors of all
   policies are deprecated and that a policy should be set to OLD only under
diff --git a/Tests/RunCMake/IfacePaths/CMakeLists.txt b/Tests/RunCMake/IfacePaths/CMakeLists.txt
index 5cd4825..0d707f0 100644
--- a/Tests/RunCMake/IfacePaths/CMakeLists.txt
+++ b/Tests/RunCMake/IfacePaths/CMakeLists.txt
@@ -1,4 +1,8 @@
-cmake_minimum_required(VERSION 3.0)
+if(RunCMake_TEST MATCHES "-CMP0052")
+  cmake_minimum_required(VERSION 3.0)
+else()
+  cmake_minimum_required(VERSION 3.5)
+endif()
 project(${RunCMake_TEST} NONE)
 if(NOT TEST_FILE)
   set(TEST_FILE ${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/IfacePaths/SrcInInstallPrefix-CMP0052-OLD-stderr.txt b/Tests/RunCMake/IfacePaths/SrcInInstallPrefix-CMP0052-OLD-stderr.txt
index 37747a1..4db8209 100644
--- a/Tests/RunCMake/IfacePaths/SrcInInstallPrefix-CMP0052-OLD-stderr.txt
+++ b/Tests/RunCMake/IfacePaths/SrcInInstallPrefix-CMP0052-OLD-stderr.txt
@@ -1,6 +1,13 @@
-^CMake Deprecation Warning at CMakeLists.txt:1 \(cmake_minimum_required\):
+^CMake Deprecation Warning at CMakeLists\.txt:[0-9]+ \(cmake_minimum_required\):
+  Compatibility with CMake < 3\.5 will be removed from a future version of
+  CMake\.
+
+  Update the VERSION argument <min> value or use a \.\.\.<max> suffix to tell
+  CMake that the project does not need compatibility with older versions\.
++
+CMake Deprecation Warning at CMakeLists\.txt:[0-9]+ \(cmake_minimum_required\):
   The OLD behavior for policy CMP0052 will be removed from a future version
-  of CMake.
+  of CMake\.
 
   The cmake-policies\(7\) manual explains that the OLD behaviors of all
   policies are deprecated and that a policy should be set to OLD only under
diff --git a/Tests/RunCMake/IncludeWhatYouUse/C-error-Build-result.txt b/Tests/RunCMake/IncludeWhatYouUse/C-error-Build-result.txt
new file mode 100644
index 0000000..d197c91
--- /dev/null
+++ b/Tests/RunCMake/IncludeWhatYouUse/C-error-Build-result.txt
@@ -0,0 +1 @@
+[^0]
diff --git a/Tests/RunCMake/IncludeWhatYouUse/C-error-Build-stdout.txt b/Tests/RunCMake/IncludeWhatYouUse/C-error-Build-stdout.txt
new file mode 100644
index 0000000..cb74677
--- /dev/null
+++ b/Tests/RunCMake/IncludeWhatYouUse/C-error-Build-stdout.txt
@@ -0,0 +1,4 @@
+Warning: include-what-you-use reported diagnostics:
+should add these lines:
+*
+#include <\.\.\.>
diff --git a/Tests/RunCMake/IncludeWhatYouUse/C-error.cmake b/Tests/RunCMake/IncludeWhatYouUse/C-error.cmake
new file mode 100644
index 0000000..d5230bb
--- /dev/null
+++ b/Tests/RunCMake/IncludeWhatYouUse/C-error.cmake
@@ -0,0 +1,3 @@
+enable_language(C)
+set(CMAKE_C_INCLUDE_WHAT_YOU_USE "${PSEUDO_IWYU}" -Xiwyu --error)
+add_executable(main main.c)
diff --git a/Tests/RunCMake/IncludeWhatYouUse/CMakeLists.txt b/Tests/RunCMake/IncludeWhatYouUse/CMakeLists.txt
index 18dfd26..93ee9df 100644
--- a/Tests/RunCMake/IncludeWhatYouUse/CMakeLists.txt
+++ b/Tests/RunCMake/IncludeWhatYouUse/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.2)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/IncludeWhatYouUse/CXX-error-Build-result.txt b/Tests/RunCMake/IncludeWhatYouUse/CXX-error-Build-result.txt
new file mode 100644
index 0000000..d197c91
--- /dev/null
+++ b/Tests/RunCMake/IncludeWhatYouUse/CXX-error-Build-result.txt
@@ -0,0 +1 @@
+[^0]
diff --git a/Tests/RunCMake/IncludeWhatYouUse/CXX-error-Build-stdout.txt b/Tests/RunCMake/IncludeWhatYouUse/CXX-error-Build-stdout.txt
new file mode 100644
index 0000000..cb74677
--- /dev/null
+++ b/Tests/RunCMake/IncludeWhatYouUse/CXX-error-Build-stdout.txt
@@ -0,0 +1,4 @@
+Warning: include-what-you-use reported diagnostics:
+should add these lines:
+*
+#include <\.\.\.>
diff --git a/Tests/RunCMake/IncludeWhatYouUse/CXX-error.cmake b/Tests/RunCMake/IncludeWhatYouUse/CXX-error.cmake
new file mode 100644
index 0000000..1d10a55
--- /dev/null
+++ b/Tests/RunCMake/IncludeWhatYouUse/CXX-error.cmake
@@ -0,0 +1,3 @@
+enable_language(CXX)
+set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "$<1:${PSEUDO_IWYU}>" -Xiwyu --error)
+add_executable(main main.cxx)
diff --git a/Tests/RunCMake/IncludeWhatYouUse/CXX.cmake b/Tests/RunCMake/IncludeWhatYouUse/CXX.cmake
index 896930c..8780bb6 100644
--- a/Tests/RunCMake/IncludeWhatYouUse/CXX.cmake
+++ b/Tests/RunCMake/IncludeWhatYouUse/CXX.cmake
@@ -1,3 +1,3 @@
 enable_language(CXX)
-set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "${PSEUDO_IWYU}" -some -args)
+set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "$<1:${PSEUDO_IWYU}>" -some -args)
 add_executable(main main.cxx)
diff --git a/Tests/RunCMake/IncludeWhatYouUse/RunCMakeTest.cmake b/Tests/RunCMake/IncludeWhatYouUse/RunCMakeTest.cmake
index 8f99eb1..8ec24be 100644
--- a/Tests/RunCMake/IncludeWhatYouUse/RunCMakeTest.cmake
+++ b/Tests/RunCMake/IncludeWhatYouUse/RunCMakeTest.cmake
@@ -16,6 +16,8 @@
 
 run_iwyu(C)
 run_iwyu(CXX)
+run_iwyu(C-error)
+run_iwyu(CXX-error)
 if (NOT RunCMake_GENERATOR STREQUAL "Watcom WMake")
   run_iwyu(C-launch)
   run_iwyu(CXX-launch)
diff --git a/Tests/RunCMake/InterfaceLibrary/CMakeLists.txt b/Tests/RunCMake/InterfaceLibrary/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/InterfaceLibrary/CMakeLists.txt
+++ b/Tests/RunCMake/InterfaceLibrary/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/InterfaceLibrary/genex_link.cmake b/Tests/RunCMake/InterfaceLibrary/genex_link.cmake
index 0dbf029..3445864 100644
--- a/Tests/RunCMake/InterfaceLibrary/genex_link.cmake
+++ b/Tests/RunCMake/InterfaceLibrary/genex_link.cmake
@@ -1,7 +1,4 @@
-
-cmake_minimum_required(VERSION 2.8.12.20131125 FATAL_ERROR)
-
-project(genex_link)
+enable_language(CXX)
 
 set(_main_cpp ${CMAKE_CURRENT_BINARY_DIR}/main.cpp)
 file(WRITE ${_main_cpp}
diff --git a/Tests/RunCMake/InterfaceLibrary/global-interface-stderr.txt b/Tests/RunCMake/InterfaceLibrary/global-interface-stderr.txt
index 38585eb..352bb68 100644
--- a/Tests/RunCMake/InterfaceLibrary/global-interface-stderr.txt
+++ b/Tests/RunCMake/InterfaceLibrary/global-interface-stderr.txt
@@ -3,7 +3,7 @@
 
     GLOBAL
 
-  Tried extensions \.c \.C .*
-.*
-Call Stack \(most recent call first\):
+  Tried extensions ([^
+]+
+)+Call Stack \(most recent call first\):
   CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/InterfaceLibrary/invalid_name.cmake b/Tests/RunCMake/InterfaceLibrary/invalid_name.cmake
index 575fcc6..4a8ca37 100644
--- a/Tests/RunCMake/InterfaceLibrary/invalid_name.cmake
+++ b/Tests/RunCMake/InterfaceLibrary/invalid_name.cmake
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_policy(SET CMP0037 OLD)
 add_library(if$ace INTERFACE)
 
 add_library(iface::target INTERFACE)
diff --git a/Tests/RunCMake/InterfaceLibrary/no_shared_libs.cmake b/Tests/RunCMake/InterfaceLibrary/no_shared_libs.cmake
index ed81878..eae8f57 100644
--- a/Tests/RunCMake/InterfaceLibrary/no_shared_libs.cmake
+++ b/Tests/RunCMake/InterfaceLibrary/no_shared_libs.cmake
@@ -1,5 +1,3 @@
-
-cmake_minimum_required(VERSION 2.8.12.20131009)
 set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS FALSE)
 add_library(foo INTERFACE)
 target_compile_definitions(foo INTERFACE FOO_DEFINE)
diff --git a/Tests/RunCMake/Languages/CMakeLists.txt b/Tests/RunCMake/Languages/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/Languages/CMakeLists.txt
+++ b/Tests/RunCMake/Languages/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/LinkItemValidation/CMP0028-OLD-iface-stderr.txt b/Tests/RunCMake/LinkItemValidation/CMP0028-OLD-iface-stderr.txt
index b7a0755..55aa1bb 100644
--- a/Tests/RunCMake/LinkItemValidation/CMP0028-OLD-iface-stderr.txt
+++ b/Tests/RunCMake/LinkItemValidation/CMP0028-OLD-iface-stderr.txt
@@ -1,6 +1,13 @@
-^CMake Deprecation Warning at CMP0028-OLD-iface.cmake:[0-9]+ \(cmake_policy\):
+^CMake Deprecation Warning at CMakeLists\.txt:3 \(cmake_minimum_required\):
+  Compatibility with CMake < 3\.5 will be removed from a future version of
+  CMake\.
+
+  Update the VERSION argument <min> value or use a \.\.\.<max> suffix to tell
+  CMake that the project does not need compatibility with older versions\.
++
+CMake Deprecation Warning at CMP0028-OLD-iface\.cmake:[0-9]+ \(cmake_policy\):
   The OLD behavior for policy CMP0028 will be removed from a future version
-  of CMake.
+  of CMake\.
 
   The cmake-policies\(7\) manual explains that the OLD behaviors of all
   policies are deprecated and that a policy should be set to OLD only under
diff --git a/Tests/RunCMake/LinkItemValidation/CMP0028-OLD-stderr.txt b/Tests/RunCMake/LinkItemValidation/CMP0028-OLD-stderr.txt
index 586a876..f11d8f5 100644
--- a/Tests/RunCMake/LinkItemValidation/CMP0028-OLD-stderr.txt
+++ b/Tests/RunCMake/LinkItemValidation/CMP0028-OLD-stderr.txt
@@ -1,6 +1,13 @@
-^CMake Deprecation Warning at CMP0028-OLD.cmake:[0-9]+ \(cmake_policy\):
+^CMake Deprecation Warning at CMakeLists\.txt:3 \(cmake_minimum_required\):
+  Compatibility with CMake < 3\.5 will be removed from a future version of
+  CMake\.
+
+  Update the VERSION argument <min> value or use a \.\.\.<max> suffix to tell
+  CMake that the project does not need compatibility with older versions\.
++
+CMake Deprecation Warning at CMP0028-OLD\.cmake:[0-9]+ \(cmake_policy\):
   The OLD behavior for policy CMP0028 will be removed from a future version
-  of CMake.
+  of CMake\.
 
   The cmake-policies\(7\) manual explains that the OLD behaviors of all
   policies are deprecated and that a policy should be set to OLD only under
diff --git a/Tests/RunCMake/LinkItemValidation/CMakeLists.txt b/Tests/RunCMake/LinkItemValidation/CMakeLists.txt
index 185cd91..6e1f8a2 100644
--- a/Tests/RunCMake/LinkItemValidation/CMakeLists.txt
+++ b/Tests/RunCMake/LinkItemValidation/CMakeLists.txt
@@ -1,6 +1,6 @@
-cmake_minimum_required(VERSION 2.8.12)
-if(NOT RunCMake_TEST MATCHES "^CMP0028")
-  cmake_minimum_required(VERSION 3.22)
+cmake_minimum_required(VERSION 3.5)
+if(RunCMake_TEST MATCHES "^CMP0028")
+  cmake_minimum_required(VERSION 2.8.12)
 endif()
 project(${RunCMake_TEST} CXX)
 include(${RunCMake_TEST}.cmake NO_POLICY_SCOPE) # policy used at end of dir
diff --git a/Tests/RunCMake/LinkStatic/CMakeLists.txt b/Tests/RunCMake/LinkStatic/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/LinkStatic/CMakeLists.txt
+++ b/Tests/RunCMake/LinkStatic/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/LinkWhatYouUse/CMakeLists.txt b/Tests/RunCMake/LinkWhatYouUse/CMakeLists.txt
index 18dfd26..93ee9df 100644
--- a/Tests/RunCMake/LinkWhatYouUse/CMakeLists.txt
+++ b/Tests/RunCMake/LinkWhatYouUse/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.2)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/LinkerLauncher/C-env-Build-stdout.txt b/Tests/RunCMake/LinkerLauncher/C-env-Build-stdout.txt
index 3313e31..544b65f 100644
--- a/Tests/RunCMake/LinkerLauncher/C-env-Build-stdout.txt
+++ b/Tests/RunCMake/LinkerLauncher/C-env-Build-stdout.txt
@@ -1 +1 @@
-.*-E env USED_LAUNCHER=1.*
+.*-E env USED_LAUNCHER=1 TARGET_NAME=main LANGUAGE=C.*
diff --git a/Tests/RunCMake/LinkerLauncher/C-launch-env-Build-stdout.txt b/Tests/RunCMake/LinkerLauncher/C-launch-env-Build-stdout.txt
index 3313e31..544b65f 100644
--- a/Tests/RunCMake/LinkerLauncher/C-launch-env-Build-stdout.txt
+++ b/Tests/RunCMake/LinkerLauncher/C-launch-env-Build-stdout.txt
@@ -1 +1 @@
-.*-E env USED_LAUNCHER=1.*
+.*-E env USED_LAUNCHER=1 TARGET_NAME=main LANGUAGE=C.*
diff --git a/Tests/RunCMake/LinkerLauncher/CXX-env-Build-stdout.txt b/Tests/RunCMake/LinkerLauncher/CXX-env-Build-stdout.txt
index 3313e31..082c7b5 100644
--- a/Tests/RunCMake/LinkerLauncher/CXX-env-Build-stdout.txt
+++ b/Tests/RunCMake/LinkerLauncher/CXX-env-Build-stdout.txt
@@ -1 +1 @@
-.*-E env USED_LAUNCHER=1.*
+.*-E env USED_LAUNCHER=1 TARGET_NAME=main LANGUAGE=CXX.*
diff --git a/Tests/RunCMake/LinkerLauncher/CXX-launch-env-Build-stdout.txt b/Tests/RunCMake/LinkerLauncher/CXX-launch-env-Build-stdout.txt
index 3313e31..082c7b5 100644
--- a/Tests/RunCMake/LinkerLauncher/CXX-launch-env-Build-stdout.txt
+++ b/Tests/RunCMake/LinkerLauncher/CXX-launch-env-Build-stdout.txt
@@ -1 +1 @@
-.*-E env USED_LAUNCHER=1.*
+.*-E env USED_LAUNCHER=1 TARGET_NAME=main LANGUAGE=CXX.*
diff --git a/Tests/RunCMake/LinkerLauncher/OBJC-env-Build-stdout.txt b/Tests/RunCMake/LinkerLauncher/OBJC-env-Build-stdout.txt
index 3313e31..d2efd3d 100644
--- a/Tests/RunCMake/LinkerLauncher/OBJC-env-Build-stdout.txt
+++ b/Tests/RunCMake/LinkerLauncher/OBJC-env-Build-stdout.txt
@@ -1 +1 @@
-.*-E env USED_LAUNCHER=1.*
+.*-E env USED_LAUNCHER=1 TARGET_NAME=main LANGUAGE=OBJC.*
diff --git a/Tests/RunCMake/LinkerLauncher/OBJC-launch-env-Build-stdout.txt b/Tests/RunCMake/LinkerLauncher/OBJC-launch-env-Build-stdout.txt
index 3313e31..d2efd3d 100644
--- a/Tests/RunCMake/LinkerLauncher/OBJC-launch-env-Build-stdout.txt
+++ b/Tests/RunCMake/LinkerLauncher/OBJC-launch-env-Build-stdout.txt
@@ -1 +1 @@
-.*-E env USED_LAUNCHER=1.*
+.*-E env USED_LAUNCHER=1 TARGET_NAME=main LANGUAGE=OBJC.*
diff --git a/Tests/RunCMake/LinkerLauncher/OBJCXX-env-Build-stdout.txt b/Tests/RunCMake/LinkerLauncher/OBJCXX-env-Build-stdout.txt
index 3313e31..0082ab2 100644
--- a/Tests/RunCMake/LinkerLauncher/OBJCXX-env-Build-stdout.txt
+++ b/Tests/RunCMake/LinkerLauncher/OBJCXX-env-Build-stdout.txt
@@ -1 +1 @@
-.*-E env USED_LAUNCHER=1.*
+.*-E env USED_LAUNCHER=1 TARGET_NAME=main LANGUAGE=OBJCXX.*
diff --git a/Tests/RunCMake/LinkerLauncher/OBJCXX-launch-env-Build-stdout.txt b/Tests/RunCMake/LinkerLauncher/OBJCXX-launch-env-Build-stdout.txt
index 3313e31..0082ab2 100644
--- a/Tests/RunCMake/LinkerLauncher/OBJCXX-launch-env-Build-stdout.txt
+++ b/Tests/RunCMake/LinkerLauncher/OBJCXX-launch-env-Build-stdout.txt
@@ -1 +1 @@
-.*-E env USED_LAUNCHER=1.*
+.*-E env USED_LAUNCHER=1 TARGET_NAME=main LANGUAGE=OBJCXX.*
diff --git a/Tests/RunCMake/LinkerLauncher/RunCMakeTest.cmake b/Tests/RunCMake/LinkerLauncher/RunCMakeTest.cmake
index 8f2bf63..025f367 100644
--- a/Tests/RunCMake/LinkerLauncher/RunCMakeTest.cmake
+++ b/Tests/RunCMake/LinkerLauncher/RunCMakeTest.cmake
@@ -17,7 +17,8 @@
 
 function(run_linker_launcher_env lang)
   string(REGEX REPLACE "-.*" "" core_lang "${lang}")
-  set(ENV{CMAKE_${core_lang}_LINKER_LAUNCHER} "${CMAKE_COMMAND};-E;env;USED_LAUNCHER=1")
+  # Use the noop genexp $<PATH:...> genexp to validate genexp support.
+  set(ENV{CMAKE_${core_lang}_LINKER_LAUNCHER} "$<PATH:CMAKE_PATH,${CMAKE_COMMAND}>;-E;env;USED_LAUNCHER=1;TARGET_NAME=$<TARGET_PROPERTY:NAME>;LANGUAGE=$<LINK_LANGUAGE>")
   run_linker_launcher(${lang})
   unset(ENV{CMAKE_${core_lang}_LINKER_LAUNCHER})
 endfunction()
diff --git a/Tests/RunCMake/MacOSVersions/MacOSVersions-build-check.cmake b/Tests/RunCMake/MacOSVersions/MacOSVersions-build-check.cmake
index c4faa8b..3eff573 100644
--- a/Tests/RunCMake/MacOSVersions/MacOSVersions-build-check.cmake
+++ b/Tests/RunCMake/MacOSVersions/MacOSVersions-build-check.cmake
@@ -21,7 +21,7 @@
     [[compatibility version 2\.1\.0]]
     )
   if(NOT "${out}" MATCHES "( |\n)${ver}( |\n)")
-    set(RunCMake_TEST_FAILED "Library file:\n  ${lib}\ndoes not contain '${ver}'")
+    string(CONCAT RunCMake_TEST_FAILED "Library file:\n  ${lib}\n" "does not contain '${ver}'")
     return()
   endif()
 endforeach()
diff --git a/Tests/RunCMake/Make/CMP0113-OLD-stderr.txt b/Tests/RunCMake/Make/CMP0113-OLD-stderr.txt
new file mode 100644
index 0000000..42742f7
--- /dev/null
+++ b/Tests/RunCMake/Make/CMP0113-OLD-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at CMP0113-OLD.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0113 will be removed from a future version
+  of CMake.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/Make/CMakeLists.txt b/Tests/RunCMake/Make/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/Make/CMakeLists.txt
+++ b/Tests/RunCMake/Make/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/MaxRecursionDepth/RunCMakeTest.cmake b/Tests/RunCMake/MaxRecursionDepth/RunCMakeTest.cmake
index c5a859d..fdf418f 100644
--- a/Tests/RunCMake/MaxRecursionDepth/RunCMakeTest.cmake
+++ b/Tests/RunCMake/MaxRecursionDepth/RunCMakeTest.cmake
@@ -1,26 +1,42 @@
 include(RunCMake)
 include(RunCTest)
 
-function(run_cmake_recursive name)
-  set(RunCMake_TEST_OPTIONS "-DCMAKE_MODULE_PATH=${CMAKE_CURRENT_LIST_DIR}" -DTEST_NAME=${name})
-  run_cmake(${name}-default)
-  unset(RunCMake_TEST_OPTIONS)
-  set(RunCMake_TEST_OPTIONS "-DCMAKE_MODULE_PATH=${CMAKE_CURRENT_LIST_DIR}" -DTEST_NAME=${name} -DCMAKE_MAXIMUM_RECURSION_DEPTH=10)
-  run_cmake(${name}-var)
-  unset(RunCMake_TEST_OPTIONS)
-  set(RunCMake_TEST_OPTIONS "-DCMAKE_MODULE_PATH=${CMAKE_CURRENT_LIST_DIR}" -DTEST_NAME=${name} -DCMAKE_MAXIMUM_RECURSION_DEPTH=a)
-  run_cmake(${name}-invalid-var)
-  unset(RunCMake_TEST_OPTIONS)
+# Isolate this test from the caller's environment.
+unset(ENV{CMAKE_MAXIMUM_RECURSION_DEPTH})
 
+function(run_cmake_recursive name)
+  run_cmake_with_options(${name}-default "-DCMAKE_MODULE_PATH=${CMAKE_CURRENT_LIST_DIR}" -DTEST_NAME=${name})
   run_cmake_command(${name}-default-script ${CMAKE_COMMAND} "-DCMAKE_MODULE_PATH=${CMAKE_CURRENT_LIST_DIR}" -DTEST_NAME=${name} -P "${CMAKE_CURRENT_LIST_DIR}/CMakeLists.txt")
+
+  set(ENV{CMAKE_MAXIMUM_RECURSION_DEPTH} 5) # overridden, not used
+  run_cmake_with_options(${name}-var "-DCMAKE_MODULE_PATH=${CMAKE_CURRENT_LIST_DIR}" -DTEST_NAME=${name} -DCMAKE_MAXIMUM_RECURSION_DEPTH=10)
+  run_cmake_with_options(${name}-invalid-var "-DCMAKE_MODULE_PATH=${CMAKE_CURRENT_LIST_DIR}" -DTEST_NAME=${name} -DCMAKE_MAXIMUM_RECURSION_DEPTH=a)
   run_cmake_command(${name}-var-script ${CMAKE_COMMAND} "-DCMAKE_MODULE_PATH=${CMAKE_CURRENT_LIST_DIR}" -DTEST_NAME=${name} -DCMAKE_MAXIMUM_RECURSION_DEPTH=10 -P "${CMAKE_CURRENT_LIST_DIR}/CMakeLists.txt")
   run_cmake_command(${name}-invalid-var-script ${CMAKE_COMMAND} "-DCMAKE_MODULE_PATH=${CMAKE_CURRENT_LIST_DIR}" -DTEST_NAME=${name} -DCMAKE_MAXIMUM_RECURSION_DEPTH=a -P "${CMAKE_CURRENT_LIST_DIR}/CMakeLists.txt")
+
+  set(ENV{CMAKE_MAXIMUM_RECURSION_DEPTH} 10)
+  run_cmake_with_options(${name}-env "-DCMAKE_MODULE_PATH=${CMAKE_CURRENT_LIST_DIR}" -DTEST_NAME=${name})
+  run_cmake_command(${name}-env-script ${CMAKE_COMMAND} "-DCMAKE_MODULE_PATH=${CMAKE_CURRENT_LIST_DIR}" -DTEST_NAME=${name} -P "${CMAKE_CURRENT_LIST_DIR}/CMakeLists.txt")
+
+  set(ENV{CMAKE_MAXIMUM_RECURSION_DEPTH} a)
+  run_cmake_with_options(${name}-invalid-env "-DCMAKE_MODULE_PATH=${CMAKE_CURRENT_LIST_DIR}" -DTEST_NAME=${name})
+  run_cmake_command(${name}-invalid-env-script ${CMAKE_COMMAND} "-DCMAKE_MODULE_PATH=${CMAKE_CURRENT_LIST_DIR}" -DTEST_NAME=${name} -P "${CMAKE_CURRENT_LIST_DIR}/CMakeLists.txt")
+
+  unset(ENV{CMAKE_MAXIMUM_RECURSION_DEPTH})
 endfunction()
 
 function(run_ctest_recursive name)
   run_ctest(${name}-default "-DCMAKE_MODULE_PATH=${CMAKE_CURRENT_LIST_DIR}" -DTEST_NAME=${name})
   run_ctest(${name}-var "-DCMAKE_MODULE_PATH=${CMAKE_CURRENT_LIST_DIR}" -DTEST_NAME=${name} -DCMAKE_MAXIMUM_RECURSION_DEPTH=10)
   run_ctest(${name}-invalid-var "-DCMAKE_MODULE_PATH=${CMAKE_CURRENT_LIST_DIR}" -DTEST_NAME=${name} -DCMAKE_MAXIMUM_RECURSION_DEPTH=a)
+
+  set(ENV{CMAKE_MAXIMUM_RECURSION_DEPTH} 10)
+  run_ctest(${name}-env "-DCMAKE_MODULE_PATH=${CMAKE_CURRENT_LIST_DIR}" -DTEST_NAME=${name})
+
+  set(ENV{CMAKE_MAXIMUM_RECURSION_DEPTH} a)
+  run_ctest(${name}-invalid-env "-DCMAKE_MODULE_PATH=${CMAKE_CURRENT_LIST_DIR}" -DTEST_NAME=${name})
+
+  unset(ENV{CMAKE_MAXIMUM_RECURSION_DEPTH})
 endfunction()
 
 run_cmake_recursive(function)
@@ -32,12 +48,8 @@
 # We run these tests separately and only with a small limit because they are
 # taxing and slow. The "implicit" and "invalid" cases are already thoroughly
 # covered by the other tests above.
-set(RunCMake_TEST_OPTIONS "-DCMAKE_MODULE_PATH=${CMAKE_CURRENT_LIST_DIR}" -DTEST_NAME=add_subdirectory -DCMAKE_MAXIMUM_RECURSION_DEPTH=10)
-run_cmake(add_subdirectory-var)
-unset(RunCMake_TEST_OPTIONS)
-set(RunCMake_TEST_OPTIONS "-DCMAKE_MODULE_PATH=${CMAKE_CURRENT_LIST_DIR}" -DTEST_NAME=try_compile -DCMAKE_MAXIMUM_RECURSION_DEPTH=10)
-run_cmake(try_compile-var)
-unset(RunCMake_TEST_OPTIONS)
+run_cmake_with_options(add_subdirectory-var "-DCMAKE_MODULE_PATH=${CMAKE_CURRENT_LIST_DIR}" -DTEST_NAME=add_subdirectory -DCMAKE_MAXIMUM_RECURSION_DEPTH=10)
+run_cmake_with_options(try_compile-var "-DCMAKE_MODULE_PATH=${CMAKE_CURRENT_LIST_DIR}" -DTEST_NAME=try_compile -DCMAKE_MAXIMUM_RECURSION_DEPTH=10)
 
 run_ctest_recursive(ctest_read_custom_files)
 
diff --git a/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-env-result.txt b/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-env-result.txt
new file mode 100644
index 0000000..b57e2de
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-env-result.txt
@@ -0,0 +1 @@
+(-1|255)
diff --git a/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-env-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-env-stderr.txt
new file mode 100644
index 0000000..b664fa0
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-env-stderr.txt
@@ -0,0 +1,34 @@
+^2
+3
+4
+5
+6
+7
+8
+9
+10
+CMake Error at .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake:1 \(message\):
+  Maximum recursion depth of 10 exceeded
+Call Stack \(most recent call first\):
+  .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake:3 \(ctest_read_custom_files\)
+  .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake:3 \(ctest_read_custom_files\)
+  .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake:3 \(ctest_read_custom_files\)
+  .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake:3 \(ctest_read_custom_files\)
+  .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake:3 \(ctest_read_custom_files\)
+  .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake:3 \(ctest_read_custom_files\)
+  .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake:3 \(ctest_read_custom_files\)
+  .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake:3 \(ctest_read_custom_files\)
+  .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake:3 \(ctest_read_custom_files\)
+  .*/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-env/test\.cmake:10 \(ctest_read_custom_files\)
+
+
+Problem reading custom configuration: .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake
+Problem reading custom configuration: .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake
+Problem reading custom configuration: .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake
+Problem reading custom configuration: .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake
+Problem reading custom configuration: .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake
+Problem reading custom configuration: .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake
+Problem reading custom configuration: .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake
+Problem reading custom configuration: .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake
+Problem reading custom configuration: .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake
+Problem reading custom configuration: .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake$
diff --git a/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-invalid-env-result.txt b/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-invalid-env-result.txt
new file mode 100644
index 0000000..b57e2de
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-invalid-env-result.txt
@@ -0,0 +1 @@
+(-1|255)
diff --git a/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-invalid-env-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-invalid-env-stderr.txt
new file mode 100644
index 0000000..7dbbb3e
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-invalid-env-stderr.txt
@@ -0,0 +1,5 @@
+[0-9]+
+CMake Error at .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake:1 \(message\):
+  Maximum recursion depth of [0-9]+ exceeded
+Call Stack \(most recent call first\):
+  .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake:3 \(ctest_read_custom_files\)
diff --git a/Tests/RunCMake/MaxRecursionDepth/find_package-default-script-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/find_package-default-script-stderr.txt
index b8557ab..4e965e8 100644
--- a/Tests/RunCMake/MaxRecursionDepth/find_package-default-script-stderr.txt
+++ b/Tests/RunCMake/MaxRecursionDepth/find_package-default-script-stderr.txt
@@ -1,5 +1,7 @@
 [0-9]+
-CMake Error at .*/FindRecursivePackage\.cmake:1 \(message\):
-  Maximum recursion depth of [0-9]+ exceeded
+CMake Error at [^
+]*/Tests/RunCMake/MaxRecursionDepth/FindRecursivePackage.cmake:[0-9]+ \(find_package\):
+  find_package maximum nesting depth of [0-9]+ exceeded.
 Call Stack \(most recent call first\):
-  .*/FindRecursivePackage\.cmake:3 \(find_package\)
+  [^
+]*/Tests/RunCMake/MaxRecursionDepth/FindRecursivePackage.cmake:[0-9]+ \(find_package\)
diff --git a/Tests/RunCMake/MaxRecursionDepth/find_package-default-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/find_package-default-stderr.txt
index 5d31e29..0119953 100644
--- a/Tests/RunCMake/MaxRecursionDepth/find_package-default-stderr.txt
+++ b/Tests/RunCMake/MaxRecursionDepth/find_package-default-stderr.txt
@@ -1,5 +1,5 @@
 [0-9]+
-CMake Error at FindRecursivePackage\.cmake:1 \(message\):
-  Maximum recursion depth of [0-9]+ exceeded
+CMake Error at FindRecursivePackage.cmake:[0-9]+ \(find_package\):
+  find_package maximum nesting depth of [0-9]+ exceeded.
 Call Stack \(most recent call first\):
-  FindRecursivePackage\.cmake:3 \(find_package\)
+  FindRecursivePackage.cmake:[0-9]+ \(find_package\)
diff --git a/Tests/RunCMake/MaxRecursionDepth/find_package-env-result.txt b/Tests/RunCMake/MaxRecursionDepth/find_package-env-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/find_package-env-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/MaxRecursionDepth/find_package-env-script-result.txt b/Tests/RunCMake/MaxRecursionDepth/find_package-env-script-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/find_package-env-script-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/MaxRecursionDepth/find_package-env-script-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/find_package-env-script-stderr.txt
new file mode 100644
index 0000000..5314551
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/find_package-env-script-stderr.txt
@@ -0,0 +1,21 @@
+^3
+4
+5
+6
+7
+8
+9
+10
+CMake Error at .*/FindRecursivePackage\.cmake:1 \(message\):
+  Maximum recursion depth of 10 exceeded
+Call Stack \(most recent call first\):
+  .*/FindRecursivePackage\.cmake:3 \(find_package\)
+  .*/FindRecursivePackage\.cmake:3 \(find_package\)
+  .*/FindRecursivePackage\.cmake:3 \(find_package\)
+  .*/FindRecursivePackage\.cmake:3 \(find_package\)
+  .*/FindRecursivePackage\.cmake:3 \(find_package\)
+  .*/FindRecursivePackage\.cmake:3 \(find_package\)
+  .*/FindRecursivePackage\.cmake:3 \(find_package\)
+  .*/FindRecursivePackage\.cmake:3 \(find_package\)
+  .*/find_package\.cmake:2 \(find_package\)
+  .*/CMakeLists\.txt:5 \(include\)$
diff --git a/Tests/RunCMake/MaxRecursionDepth/find_package-env-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/find_package-env-stderr.txt
new file mode 100644
index 0000000..b47a13a
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/find_package-env-stderr.txt
@@ -0,0 +1,21 @@
+^3
+4
+5
+6
+7
+8
+9
+10
+CMake Error at FindRecursivePackage\.cmake:1 \(message\):
+  Maximum recursion depth of 10 exceeded
+Call Stack \(most recent call first\):
+  FindRecursivePackage\.cmake:3 \(find_package\)
+  FindRecursivePackage\.cmake:3 \(find_package\)
+  FindRecursivePackage\.cmake:3 \(find_package\)
+  FindRecursivePackage\.cmake:3 \(find_package\)
+  FindRecursivePackage\.cmake:3 \(find_package\)
+  FindRecursivePackage\.cmake:3 \(find_package\)
+  FindRecursivePackage\.cmake:3 \(find_package\)
+  FindRecursivePackage\.cmake:3 \(find_package\)
+  find_package\.cmake:2 \(find_package\)
+  CMakeLists\.txt:5 \(include\)$
diff --git a/Tests/RunCMake/MaxRecursionDepth/find_package-invalid-env-result.txt b/Tests/RunCMake/MaxRecursionDepth/find_package-invalid-env-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/find_package-invalid-env-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/MaxRecursionDepth/find_package-invalid-env-script-result.txt b/Tests/RunCMake/MaxRecursionDepth/find_package-invalid-env-script-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/find_package-invalid-env-script-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/MaxRecursionDepth/find_package-invalid-env-script-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/find_package-invalid-env-script-stderr.txt
new file mode 100644
index 0000000..4e965e8
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/find_package-invalid-env-script-stderr.txt
@@ -0,0 +1,7 @@
+[0-9]+
+CMake Error at [^
+]*/Tests/RunCMake/MaxRecursionDepth/FindRecursivePackage.cmake:[0-9]+ \(find_package\):
+  find_package maximum nesting depth of [0-9]+ exceeded.
+Call Stack \(most recent call first\):
+  [^
+]*/Tests/RunCMake/MaxRecursionDepth/FindRecursivePackage.cmake:[0-9]+ \(find_package\)
diff --git a/Tests/RunCMake/MaxRecursionDepth/find_package-invalid-env-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/find_package-invalid-env-stderr.txt
new file mode 100644
index 0000000..0119953
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/find_package-invalid-env-stderr.txt
@@ -0,0 +1,5 @@
+[0-9]+
+CMake Error at FindRecursivePackage.cmake:[0-9]+ \(find_package\):
+  find_package maximum nesting depth of [0-9]+ exceeded.
+Call Stack \(most recent call first\):
+  FindRecursivePackage.cmake:[0-9]+ \(find_package\)
diff --git a/Tests/RunCMake/MaxRecursionDepth/find_package-invalid-var-script-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/find_package-invalid-var-script-stderr.txt
index b8557ab..4e965e8 100644
--- a/Tests/RunCMake/MaxRecursionDepth/find_package-invalid-var-script-stderr.txt
+++ b/Tests/RunCMake/MaxRecursionDepth/find_package-invalid-var-script-stderr.txt
@@ -1,5 +1,7 @@
 [0-9]+
-CMake Error at .*/FindRecursivePackage\.cmake:1 \(message\):
-  Maximum recursion depth of [0-9]+ exceeded
+CMake Error at [^
+]*/Tests/RunCMake/MaxRecursionDepth/FindRecursivePackage.cmake:[0-9]+ \(find_package\):
+  find_package maximum nesting depth of [0-9]+ exceeded.
 Call Stack \(most recent call first\):
-  .*/FindRecursivePackage\.cmake:3 \(find_package\)
+  [^
+]*/Tests/RunCMake/MaxRecursionDepth/FindRecursivePackage.cmake:[0-9]+ \(find_package\)
diff --git a/Tests/RunCMake/MaxRecursionDepth/find_package-invalid-var-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/find_package-invalid-var-stderr.txt
index 5d31e29..0119953 100644
--- a/Tests/RunCMake/MaxRecursionDepth/find_package-invalid-var-stderr.txt
+++ b/Tests/RunCMake/MaxRecursionDepth/find_package-invalid-var-stderr.txt
@@ -1,5 +1,5 @@
 [0-9]+
-CMake Error at FindRecursivePackage\.cmake:1 \(message\):
-  Maximum recursion depth of [0-9]+ exceeded
+CMake Error at FindRecursivePackage.cmake:[0-9]+ \(find_package\):
+  find_package maximum nesting depth of [0-9]+ exceeded.
 Call Stack \(most recent call first\):
-  FindRecursivePackage\.cmake:3 \(find_package\)
+  FindRecursivePackage.cmake:[0-9]+ \(find_package\)
diff --git a/Tests/RunCMake/MaxRecursionDepth/function-env-result.txt b/Tests/RunCMake/MaxRecursionDepth/function-env-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/function-env-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/MaxRecursionDepth/function-env-script-result.txt b/Tests/RunCMake/MaxRecursionDepth/function-env-script-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/function-env-script-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/MaxRecursionDepth/function-env-script-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/function-env-script-stderr.txt
new file mode 100644
index 0000000..61304b1
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/function-env-script-stderr.txt
@@ -0,0 +1,21 @@
+^3
+4
+5
+6
+7
+8
+9
+10
+CMake Error at .*/function\.cmake:2 \(message\):
+  Maximum recursion depth of 10 exceeded
+Call Stack \(most recent call first\):
+  .*/function\.cmake:4 \(recursive\)
+  .*/function\.cmake:4 \(recursive\)
+  .*/function\.cmake:4 \(recursive\)
+  .*/function\.cmake:4 \(recursive\)
+  .*/function\.cmake:4 \(recursive\)
+  .*/function\.cmake:4 \(recursive\)
+  .*/function\.cmake:4 \(recursive\)
+  .*/function\.cmake:4 \(recursive\)
+  .*/function\.cmake:7 \(recursive\)
+  .*/CMakeLists\.txt:5 \(include\)$
diff --git a/Tests/RunCMake/MaxRecursionDepth/function-env-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/function-env-stderr.txt
new file mode 100644
index 0000000..54e72af
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/function-env-stderr.txt
@@ -0,0 +1,21 @@
+^3
+4
+5
+6
+7
+8
+9
+10
+CMake Error at function\.cmake:2 \(message\):
+  Maximum recursion depth of 10 exceeded
+Call Stack \(most recent call first\):
+  function\.cmake:4 \(recursive\)
+  function\.cmake:4 \(recursive\)
+  function\.cmake:4 \(recursive\)
+  function\.cmake:4 \(recursive\)
+  function\.cmake:4 \(recursive\)
+  function\.cmake:4 \(recursive\)
+  function\.cmake:4 \(recursive\)
+  function\.cmake:4 \(recursive\)
+  function\.cmake:7 \(recursive\)
+  CMakeLists\.txt:5 \(include\)$
diff --git a/Tests/RunCMake/MaxRecursionDepth/function-invalid-env-result.txt b/Tests/RunCMake/MaxRecursionDepth/function-invalid-env-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/function-invalid-env-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/MaxRecursionDepth/function-invalid-env-script-result.txt b/Tests/RunCMake/MaxRecursionDepth/function-invalid-env-script-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/function-invalid-env-script-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/MaxRecursionDepth/function-invalid-env-script-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/function-invalid-env-script-stderr.txt
new file mode 100644
index 0000000..92de1fb
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/function-invalid-env-script-stderr.txt
@@ -0,0 +1,5 @@
+[0-9]+
+CMake Error at .*/function\.cmake:2 \(message\):
+  Maximum recursion depth of [0-9]+ exceeded
+Call Stack \(most recent call first\):
+  .*/function\.cmake:4 \(recursive\)
diff --git a/Tests/RunCMake/MaxRecursionDepth/function-invalid-env-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/function-invalid-env-stderr.txt
new file mode 100644
index 0000000..5c25c4b
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/function-invalid-env-stderr.txt
@@ -0,0 +1,5 @@
+[0-9]+
+CMake Error at function\.cmake:2 \(message\):
+  Maximum recursion depth of [0-9]+ exceeded
+Call Stack \(most recent call first\):
+  function\.cmake:4 \(recursive\)
diff --git a/Tests/RunCMake/MaxRecursionDepth/include-env-result.txt b/Tests/RunCMake/MaxRecursionDepth/include-env-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/include-env-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/MaxRecursionDepth/include-env-script-result.txt b/Tests/RunCMake/MaxRecursionDepth/include-env-script-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/include-env-script-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/MaxRecursionDepth/include-env-script-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/include-env-script-stderr.txt
new file mode 100644
index 0000000..f55f505
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/include-env-script-stderr.txt
@@ -0,0 +1,21 @@
+^3
+4
+5
+6
+7
+8
+9
+10
+CMake Error at .*/include_recursive\.cmake:1 \(message\):
+  Maximum recursion depth of 10 exceeded
+Call Stack \(most recent call first\):
+  .*/include_recursive\.cmake:3 \(include\)
+  .*/include_recursive\.cmake:3 \(include\)
+  .*/include_recursive\.cmake:3 \(include\)
+  .*/include_recursive\.cmake:3 \(include\)
+  .*/include_recursive\.cmake:3 \(include\)
+  .*/include_recursive\.cmake:3 \(include\)
+  .*/include_recursive\.cmake:3 \(include\)
+  .*/include_recursive\.cmake:3 \(include\)
+  .*/include\.cmake:2 \(include\)
+  .*/CMakeLists\.txt:5 \(include\)$
diff --git a/Tests/RunCMake/MaxRecursionDepth/include-env-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/include-env-stderr.txt
new file mode 100644
index 0000000..ff33985
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/include-env-stderr.txt
@@ -0,0 +1,21 @@
+^3
+4
+5
+6
+7
+8
+9
+10
+CMake Error at include_recursive\.cmake:1 \(message\):
+  Maximum recursion depth of 10 exceeded
+Call Stack \(most recent call first\):
+  include_recursive\.cmake:3 \(include\)
+  include_recursive\.cmake:3 \(include\)
+  include_recursive\.cmake:3 \(include\)
+  include_recursive\.cmake:3 \(include\)
+  include_recursive\.cmake:3 \(include\)
+  include_recursive\.cmake:3 \(include\)
+  include_recursive\.cmake:3 \(include\)
+  include_recursive\.cmake:3 \(include\)
+  include\.cmake:2 \(include\)
+  CMakeLists\.txt:5 \(include\)$
diff --git a/Tests/RunCMake/MaxRecursionDepth/include-invalid-env-result.txt b/Tests/RunCMake/MaxRecursionDepth/include-invalid-env-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/include-invalid-env-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/MaxRecursionDepth/include-invalid-env-script-result.txt b/Tests/RunCMake/MaxRecursionDepth/include-invalid-env-script-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/include-invalid-env-script-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/MaxRecursionDepth/include-invalid-env-script-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/include-invalid-env-script-stderr.txt
new file mode 100644
index 0000000..0510e7c
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/include-invalid-env-script-stderr.txt
@@ -0,0 +1,5 @@
+[0-9]+
+CMake Error at .*/include_recursive\.cmake:1 \(message\):
+  Maximum recursion depth of [0-9]+ exceeded
+Call Stack \(most recent call first\):
+  .*/include_recursive\.cmake:3 \(include\)
diff --git a/Tests/RunCMake/MaxRecursionDepth/include-invalid-env-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/include-invalid-env-stderr.txt
new file mode 100644
index 0000000..b1494a8
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/include-invalid-env-stderr.txt
@@ -0,0 +1,5 @@
+[0-9]+
+CMake Error at include_recursive\.cmake:1 \(message\):
+  Maximum recursion depth of [0-9]+ exceeded
+Call Stack \(most recent call first\):
+  include_recursive\.cmake:3 \(include\)
diff --git a/Tests/RunCMake/MaxRecursionDepth/macro-env-result.txt b/Tests/RunCMake/MaxRecursionDepth/macro-env-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/macro-env-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/MaxRecursionDepth/macro-env-script-result.txt b/Tests/RunCMake/MaxRecursionDepth/macro-env-script-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/macro-env-script-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/MaxRecursionDepth/macro-env-script-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/macro-env-script-stderr.txt
new file mode 100644
index 0000000..142e068
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/macro-env-script-stderr.txt
@@ -0,0 +1,21 @@
+^3
+4
+5
+6
+7
+8
+9
+10
+CMake Error at .*/macro\.cmake:2 \(message\):
+  Maximum recursion depth of 10 exceeded
+Call Stack \(most recent call first\):
+  .*/macro\.cmake:4 \(recursive\)
+  .*/macro\.cmake:4 \(recursive\)
+  .*/macro\.cmake:4 \(recursive\)
+  .*/macro\.cmake:4 \(recursive\)
+  .*/macro\.cmake:4 \(recursive\)
+  .*/macro\.cmake:4 \(recursive\)
+  .*/macro\.cmake:4 \(recursive\)
+  .*/macro\.cmake:4 \(recursive\)
+  .*/macro\.cmake:7 \(recursive\)
+  .*/CMakeLists\.txt:5 \(include\)$
diff --git a/Tests/RunCMake/MaxRecursionDepth/macro-env-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/macro-env-stderr.txt
new file mode 100644
index 0000000..71de553
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/macro-env-stderr.txt
@@ -0,0 +1,21 @@
+^3
+4
+5
+6
+7
+8
+9
+10
+CMake Error at macro\.cmake:2 \(message\):
+  Maximum recursion depth of 10 exceeded
+Call Stack \(most recent call first\):
+  macro\.cmake:4 \(recursive\)
+  macro\.cmake:4 \(recursive\)
+  macro\.cmake:4 \(recursive\)
+  macro\.cmake:4 \(recursive\)
+  macro\.cmake:4 \(recursive\)
+  macro\.cmake:4 \(recursive\)
+  macro\.cmake:4 \(recursive\)
+  macro\.cmake:4 \(recursive\)
+  macro\.cmake:7 \(recursive\)
+  CMakeLists\.txt:5 \(include\)$
diff --git a/Tests/RunCMake/MaxRecursionDepth/macro-invalid-env-result.txt b/Tests/RunCMake/MaxRecursionDepth/macro-invalid-env-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/macro-invalid-env-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/MaxRecursionDepth/macro-invalid-env-script-result.txt b/Tests/RunCMake/MaxRecursionDepth/macro-invalid-env-script-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/macro-invalid-env-script-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/MaxRecursionDepth/macro-invalid-env-script-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/macro-invalid-env-script-stderr.txt
new file mode 100644
index 0000000..c67be57
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/macro-invalid-env-script-stderr.txt
@@ -0,0 +1,5 @@
+[0-9]+
+CMake Error at .*/macro\.cmake:2 \(message\):
+  Maximum recursion depth of [0-9]+ exceeded
+Call Stack \(most recent call first\):
+  .*/macro\.cmake:4 \(recursive\)
diff --git a/Tests/RunCMake/MaxRecursionDepth/macro-invalid-env-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/macro-invalid-env-stderr.txt
new file mode 100644
index 0000000..0b27162
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/macro-invalid-env-stderr.txt
@@ -0,0 +1,5 @@
+[0-9]+
+CMake Error at macro\.cmake:2 \(message\):
+  Maximum recursion depth of [0-9]+ exceeded
+Call Stack \(most recent call first\):
+  macro\.cmake:4 \(recursive\)
diff --git a/Tests/RunCMake/MaxRecursionDepth/variable_watch-env-result.txt b/Tests/RunCMake/MaxRecursionDepth/variable_watch-env-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/variable_watch-env-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/MaxRecursionDepth/variable_watch-env-script-result.txt b/Tests/RunCMake/MaxRecursionDepth/variable_watch-env-script-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/variable_watch-env-script-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/MaxRecursionDepth/variable_watch-env-script-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/variable_watch-env-script-stderr.txt
new file mode 100644
index 0000000..52fedd3
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/variable_watch-env-script-stderr.txt
@@ -0,0 +1,22 @@
+^4
+6
+8
+10
+CMake Error at .*/variable_watch\.cmake:[0-9]+ \(update_x\):
+  Maximum recursion depth of 10 exceeded
+Call Stack \(most recent call first\):
+  .*/variable_watch\.cmake:5 \(set\)
+  .*/variable_watch\.cmake:[0-9]+ \(update_x\)
+  .*/variable_watch\.cmake:5 \(set\)
+  .*/variable_watch\.cmake:[0-9]+ \(update_x\)
+  .*/variable_watch\.cmake:5 \(set\)
+  .*/variable_watch\.cmake:[0-9]+ \(update_x\)
+  .*/variable_watch\.cmake:5 \(set\)
+  .*/variable_watch\.cmake:[0-9]+ \(update_x\)
+  .*/variable_watch\.cmake:9 \(set\)
+  .*/CMakeLists\.txt:5 \(include\)
+
+
+CMake Error: Error in cmake code at
+Unknown:0:
+A command failed during the invocation of callback "update_x"\.$
diff --git a/Tests/RunCMake/MaxRecursionDepth/variable_watch-env-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/variable_watch-env-stderr.txt
new file mode 100644
index 0000000..1427f1d
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/variable_watch-env-stderr.txt
@@ -0,0 +1,22 @@
+^4
+6
+8
+10
+CMake Error at variable_watch\.cmake:[0-9]+ \(update_x\):
+  Maximum recursion depth of 10 exceeded
+Call Stack \(most recent call first\):
+  variable_watch\.cmake:5 \(set\)
+  variable_watch\.cmake:[0-9]+ \(update_x\)
+  variable_watch\.cmake:5 \(set\)
+  variable_watch\.cmake:[0-9]+ \(update_x\)
+  variable_watch\.cmake:5 \(set\)
+  variable_watch\.cmake:[0-9]+ \(update_x\)
+  variable_watch\.cmake:5 \(set\)
+  variable_watch\.cmake:[0-9]+ \(update_x\)
+  variable_watch\.cmake:9 \(set\)
+  CMakeLists\.txt:5 \(include\)
+
+
+CMake Error: Error in cmake code at
+Unknown:0:
+A command failed during the invocation of callback "update_x"\.$
diff --git a/Tests/RunCMake/MaxRecursionDepth/variable_watch-invalid-env-result.txt b/Tests/RunCMake/MaxRecursionDepth/variable_watch-invalid-env-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/variable_watch-invalid-env-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/MaxRecursionDepth/variable_watch-invalid-env-script-result.txt b/Tests/RunCMake/MaxRecursionDepth/variable_watch-invalid-env-script-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/variable_watch-invalid-env-script-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/MaxRecursionDepth/variable_watch-invalid-env-script-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/variable_watch-invalid-env-script-stderr.txt
new file mode 100644
index 0000000..07deee2
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/variable_watch-invalid-env-script-stderr.txt
@@ -0,0 +1,6 @@
+[0-9]+
+CMake Error at .*/variable_watch\.cmake:[0-9]+ \(update_x\):
+  Maximum recursion depth of [0-9]+ exceeded
+Call Stack \(most recent call first\):
+  .*/variable_watch\.cmake:5 \(set\)
+  .*/variable_watch\.cmake:[0-9]+ \(update_x\)
diff --git a/Tests/RunCMake/MaxRecursionDepth/variable_watch-invalid-env-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/variable_watch-invalid-env-stderr.txt
new file mode 100644
index 0000000..b2395b3
--- /dev/null
+++ b/Tests/RunCMake/MaxRecursionDepth/variable_watch-invalid-env-stderr.txt
@@ -0,0 +1,6 @@
+[0-9]+
+CMake Error at variable_watch\.cmake:[0-9]+ \(update_x\):
+  Maximum recursion depth of [0-9]+ exceeded
+Call Stack \(most recent call first\):
+  variable_watch\.cmake:5 \(set\)
+  variable_watch\.cmake:[0-9]+ \(update_x\)
diff --git a/Tests/RunCMake/MultiLint/C-launch_skip_linting_ON.cmake b/Tests/RunCMake/MultiLint/C-launch_skip_linting_ON.cmake
new file mode 100644
index 0000000..d0d9866
--- /dev/null
+++ b/Tests/RunCMake/MultiLint/C-launch_skip_linting_ON.cmake
@@ -0,0 +1,3 @@
+set(CTEST_USE_LAUNCHERS 1)
+include(CTestUseLaunchers)
+include(C_skip_linting_ON.cmake)
diff --git a/Tests/RunCMake/MultiLint/CMakeLists copy.txt b/Tests/RunCMake/MultiLint/CMakeLists copy.txt
new file mode 100644
index 0000000..93ee9df
--- /dev/null
+++ b/Tests/RunCMake/MultiLint/CMakeLists copy.txt
@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.5)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/MultiLint/CXX-launch_skip_linting_ON.cmake b/Tests/RunCMake/MultiLint/CXX-launch_skip_linting_ON.cmake
new file mode 100644
index 0000000..6347e60
--- /dev/null
+++ b/Tests/RunCMake/MultiLint/CXX-launch_skip_linting_ON.cmake
@@ -0,0 +1,3 @@
+set(CTEST_USE_LAUNCHERS 1)
+include(CTestUseLaunchers)
+include(CXX_skip_linting_ON.cmake)
diff --git a/Tests/RunCMake/MultiLint/CXX.cmake b/Tests/RunCMake/MultiLint/CXX.cmake
index dc30146..3e99e73 100644
--- a/Tests/RunCMake/MultiLint/CXX.cmake
+++ b/Tests/RunCMake/MultiLint/CXX.cmake
@@ -1,6 +1,6 @@
 enable_language(CXX)
-set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "${PSEUDO_IWYU}" -some -args)
-set(CMAKE_CXX_CLANG_TIDY "${PSEUDO_TIDY}" -some -args)
-set(CMAKE_CXX_CPPLINT "${PSEUDO_CPPLINT}" --verbose=0 --linelength=80)
-set(CMAKE_CXX_CPPCHECK "${PSEUDO_CPPCHECK}")
+set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "$<1:${PSEUDO_IWYU}>" -some -args)
+set(CMAKE_CXX_CLANG_TIDY "$<1:${PSEUDO_TIDY}>" -some -args)
+set(CMAKE_CXX_CPPLINT "$<1:${PSEUDO_CPPLINT}>" --verbose=0 --linelength=80)
+set(CMAKE_CXX_CPPCHECK "$<1:${PSEUDO_CPPCHECK}>")
 add_executable(main main.cxx)
diff --git a/Tests/RunCMake/MultiLint/CXX_skip_linting_OFF-Build-result.txt b/Tests/RunCMake/MultiLint/CXX_skip_linting_OFF-Build-result.txt
new file mode 100644
index 0000000..3beecb0
--- /dev/null
+++ b/Tests/RunCMake/MultiLint/CXX_skip_linting_OFF-Build-result.txt
@@ -0,0 +1 @@
+(1|2)
diff --git a/Tests/RunCMake/MultiLint/CXX_skip_linting_OFF.cmake b/Tests/RunCMake/MultiLint/CXX_skip_linting_OFF.cmake
new file mode 100644
index 0000000..a0311a6
--- /dev/null
+++ b/Tests/RunCMake/MultiLint/CXX_skip_linting_OFF.cmake
@@ -0,0 +1,7 @@
+enable_language(CXX)
+set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "$<1:${PSEUDO_IWYU}>" -some -args)
+set(CMAKE_CXX_CLANG_TIDY "$<1:${PSEUDO_TIDY}>" -bad)
+set(CMAKE_CXX_CPPLINT "$<1:${PSEUDO_CPPLINT}>" --error)
+set(CMAKE_CXX_CPPCHECK "$<1:${PSEUDO_CPPCHECK}>" -error)
+add_executable(main main.cxx)
+set_source_files_properties(main.cxx PROPERTIES SKIP_LINTING OFF)
diff --git a/Tests/RunCMake/MultiLint/CXX_skip_linting_ON.cmake b/Tests/RunCMake/MultiLint/CXX_skip_linting_ON.cmake
new file mode 100644
index 0000000..39cfe87
--- /dev/null
+++ b/Tests/RunCMake/MultiLint/CXX_skip_linting_ON.cmake
@@ -0,0 +1,7 @@
+enable_language(CXX)
+set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "$<1:${PSEUDO_IWYU}>" -some -args)
+set(CMAKE_CXX_CLANG_TIDY "$<1:${PSEUDO_TIDY}>" -bad)
+set(CMAKE_CXX_CPPLINT "$<1:${PSEUDO_CPPLINT}>" --error)
+set(CMAKE_CXX_CPPCHECK "$<1:${PSEUDO_CPPCHECK}>" -error)
+add_executable(main main.cxx)
+set_source_files_properties(main.cxx PROPERTIES SKIP_LINTING ON)
diff --git a/Tests/RunCMake/MultiLint/C_skip_linting_OFF-Build-result.txt b/Tests/RunCMake/MultiLint/C_skip_linting_OFF-Build-result.txt
new file mode 100644
index 0000000..3beecb0
--- /dev/null
+++ b/Tests/RunCMake/MultiLint/C_skip_linting_OFF-Build-result.txt
@@ -0,0 +1 @@
+(1|2)
diff --git a/Tests/RunCMake/MultiLint/C_skip_linting_OFF.cmake b/Tests/RunCMake/MultiLint/C_skip_linting_OFF.cmake
new file mode 100644
index 0000000..2968a21
--- /dev/null
+++ b/Tests/RunCMake/MultiLint/C_skip_linting_OFF.cmake
@@ -0,0 +1,7 @@
+enable_language(C)
+set(CMAKE_C_INCLUDE_WHAT_YOU_USE "${PSEUDO_IWYU}" -some -args)
+set(CMAKE_C_CLANG_TIDY "${PSEUDO_TIDY}" -bad)
+set(CMAKE_C_CPPLINT "${PSEUDO_CPPLINT}" --error)
+set(CMAKE_C_CPPCHECK "${PSEUDO_CPPCHECK}" -error)
+add_executable(main main.c)
+set_source_files_properties(main.c PROPERTIES SKIP_LINTING OFF)
diff --git a/Tests/RunCMake/MultiLint/C_skip_linting_ON.cmake b/Tests/RunCMake/MultiLint/C_skip_linting_ON.cmake
new file mode 100644
index 0000000..09fc761
--- /dev/null
+++ b/Tests/RunCMake/MultiLint/C_skip_linting_ON.cmake
@@ -0,0 +1,7 @@
+enable_language(C)
+set(CMAKE_C_INCLUDE_WHAT_YOU_USE "${PSEUDO_IWYU}" -some -args)
+set(CMAKE_C_CLANG_TIDY "${PSEUDO_TIDY}" -bad)
+set(CMAKE_C_CPPLINT "${PSEUDO_CPPLINT}" --error)
+set(CMAKE_C_CPPCHECK "${PSEUDO_CPPCHECK}" -error)
+add_executable(main main.c)
+set_source_files_properties(main.c PROPERTIES SKIP_LINTING ON)
diff --git a/Tests/RunCMake/MultiLint/RunCMakeTest.cmake b/Tests/RunCMake/MultiLint/RunCMakeTest.cmake
index afd98fd..9b7a6a9 100644
--- a/Tests/RunCMake/MultiLint/RunCMakeTest.cmake
+++ b/Tests/RunCMake/MultiLint/RunCMakeTest.cmake
@@ -25,3 +25,22 @@
   run_multilint(C-launch)
   run_multilint(CXX-launch)
 endif()
+
+function(run_skip_linting test_name)
+    set(RunCMake_TEST_BINARY_DIR "${RunCMake_BINARY_DIR}/${test_name}-build")
+    set(RunCMake_TEST_NO_CLEAN 1)
+
+    run_cmake(${test_name})
+    set(RunCMake_TEST_OUTPUT_MERGE 1)
+    run_cmake_command(${test_name}-Build ${CMAKE_COMMAND} --build .)
+endfunction()
+
+run_skip_linting(C_skip_linting_ON)
+run_skip_linting(CXX_skip_linting_ON)
+run_skip_linting(C_skip_linting_OFF)
+run_skip_linting(CXX_skip_linting_OFF)
+
+if(NOT RunCMake_GENERATOR STREQUAL "Watcom WMake")
+  run_skip_linting(C-launch_skip_linting_ON)
+  run_skip_linting(CXX-launch_skip_linting_ON)
+endif()
diff --git a/Tests/RunCMake/Ninja/AssumedSources.cmake b/Tests/RunCMake/Ninja/AssumedSources.cmake
index d5364f0..d68fca9 100644
--- a/Tests/RunCMake/Ninja/AssumedSources.cmake
+++ b/Tests/RunCMake/Ninja/AssumedSources.cmake
@@ -1,6 +1,5 @@
-cmake_minimum_required(VERSION 3.8)
 cmake_policy(SET CMP0118 NEW)
-project(AssumedSources)
+enable_language(C)
 
 set_source_files_properties(
   "${CMAKE_CURRENT_BINARY_DIR}/target.c"
diff --git a/Tests/RunCMake/Ninja/CMP0058-OLD-by-stderr.txt b/Tests/RunCMake/Ninja/CMP0058-OLD-by-stderr.txt
index 9a606ee..6d340b0 100644
--- a/Tests/RunCMake/Ninja/CMP0058-OLD-by-stderr.txt
+++ b/Tests/RunCMake/Ninja/CMP0058-OLD-by-stderr.txt
@@ -1,6 +1,15 @@
-^CMake Deprecation Warning at CMP0058-OLD-by.cmake:[0-9]+ \(cmake_policy\):
+^CMake Deprecation Warning at CMP0058-OLD-by\.cmake:[0-9] \(cmake_policy\):
+  Compatibility with CMake < 3\.5 will be removed from a future version of
+  CMake\.
+
+  Update the VERSION argument <min> value or use a \.\.\.<max> suffix to tell
+  CMake that the project does not need compatibility with older versions\.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9] \(include\)
++
+CMake Deprecation Warning at CMP0058-OLD-by\.cmake:[0-9]+ \(cmake_policy\):
   The OLD behavior for policy CMP0058 will be removed from a future version
-  of CMake.
+  of CMake\.
 
   The cmake-policies\(7\) manual explains that the OLD behaviors of all
   policies are deprecated and that a policy should be set to OLD only under
diff --git a/Tests/RunCMake/Ninja/CMP0058-OLD-by.cmake b/Tests/RunCMake/Ninja/CMP0058-OLD-by.cmake
index 92a3a0f..45e5aa3 100644
--- a/Tests/RunCMake/Ninja/CMP0058-OLD-by.cmake
+++ b/Tests/RunCMake/Ninja/CMP0058-OLD-by.cmake
@@ -1,3 +1,4 @@
+cmake_policy(VERSION 3.2)
 cmake_policy(SET CMP0058 OLD)
 set(byproducts BYPRODUCTS byproduct1a byproduct1b)
 include(CMP0058-common.cmake)
diff --git a/Tests/RunCMake/Ninja/CMP0058-OLD-no-stderr.txt b/Tests/RunCMake/Ninja/CMP0058-OLD-no-stderr.txt
index ba6e5da..834c781 100644
--- a/Tests/RunCMake/Ninja/CMP0058-OLD-no-stderr.txt
+++ b/Tests/RunCMake/Ninja/CMP0058-OLD-no-stderr.txt
@@ -1,6 +1,15 @@
-^CMake Deprecation Warning at CMP0058-OLD-no.cmake:[0-9]+ \(cmake_policy\):
+^CMake Deprecation Warning at CMP0058-OLD-no\.cmake:[0-9] \(cmake_policy\):
+  Compatibility with CMake < 3\.5 will be removed from a future version of
+  CMake\.
+
+  Update the VERSION argument <min> value or use a \.\.\.<max> suffix to tell
+  CMake that the project does not need compatibility with older versions\.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9] \(include\)
++
+CMake Deprecation Warning at CMP0058-OLD-no\.cmake:[0-9]+ \(cmake_policy\):
   The OLD behavior for policy CMP0058 will be removed from a future version
-  of CMake.
+  of CMake\.
 
   The cmake-policies\(7\) manual explains that the OLD behaviors of all
   policies are deprecated and that a policy should be set to OLD only under
diff --git a/Tests/RunCMake/Ninja/CMP0058-OLD-no.cmake b/Tests/RunCMake/Ninja/CMP0058-OLD-no.cmake
index 0326e07..388e018 100644
--- a/Tests/RunCMake/Ninja/CMP0058-OLD-no.cmake
+++ b/Tests/RunCMake/Ninja/CMP0058-OLD-no.cmake
@@ -1,2 +1,3 @@
+cmake_policy(VERSION 3.2)
 cmake_policy(SET CMP0058 OLD)
 include(CMP0058-common.cmake)
diff --git a/Tests/RunCMake/Ninja/CMP0058-WARN-by-stderr.txt b/Tests/RunCMake/Ninja/CMP0058-WARN-by-stderr.txt
new file mode 100644
index 0000000..2927f52
--- /dev/null
+++ b/Tests/RunCMake/Ninja/CMP0058-WARN-by-stderr.txt
@@ -0,0 +1,8 @@
+^CMake Deprecation Warning at CMP0058-WARN-by\.cmake:[0-9] \(cmake_policy\):
+  Compatibility with CMake < 3\.5 will be removed from a future version of
+  CMake\.
+
+  Update the VERSION argument <min> value or use a \.\.\.<max> suffix to tell
+  CMake that the project does not need compatibility with older versions\.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9] \(include\)$
diff --git a/Tests/RunCMake/Ninja/CMP0058-WARN-by.cmake b/Tests/RunCMake/Ninja/CMP0058-WARN-by.cmake
index 6128167..6f5484a 100644
--- a/Tests/RunCMake/Ninja/CMP0058-WARN-by.cmake
+++ b/Tests/RunCMake/Ninja/CMP0058-WARN-by.cmake
@@ -1,2 +1,3 @@
+cmake_policy(VERSION 3.2)
 set(byproducts BYPRODUCTS byproduct1a byproduct1b)
 include(CMP0058-common.cmake)
diff --git a/Tests/RunCMake/Ninja/CMP0058-WARN-no-stderr.txt b/Tests/RunCMake/Ninja/CMP0058-WARN-no-stderr.txt
index 439a2d9..1ffb416 100644
--- a/Tests/RunCMake/Ninja/CMP0058-WARN-no-stderr.txt
+++ b/Tests/RunCMake/Ninja/CMP0058-WARN-no-stderr.txt
@@ -1,4 +1,13 @@
-^CMake Warning \(dev\):
+^CMake Deprecation Warning at CMP0058-WARN-no\.cmake:[0-9] \(cmake_policy\):
+  Compatibility with CMake < 3\.5 will be removed from a future version of
+  CMake\.
+
+  Update the VERSION argument <min> value or use a \.\.\.<max> suffix to tell
+  CMake that the project does not need compatibility with older versions\.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9] \(include\)
++
+CMake Warning \(dev\):
   Policy CMP0058 is not set: Ninja requires custom command byproducts to be
   explicit.  Run "cmake --help-policy CMP0058" for policy details.  Use the
   cmake_policy command to set the policy and suppress this warning.
diff --git a/Tests/RunCMake/Ninja/CMP0058-WARN-no.cmake b/Tests/RunCMake/Ninja/CMP0058-WARN-no.cmake
index 7bc66ef..714ae64 100644
--- a/Tests/RunCMake/Ninja/CMP0058-WARN-no.cmake
+++ b/Tests/RunCMake/Ninja/CMP0058-WARN-no.cmake
@@ -1 +1,2 @@
+cmake_policy(VERSION 3.2)
 include(CMP0058-common.cmake)
diff --git a/Tests/RunCMake/Ninja/CMakeLists.txt b/Tests/RunCMake/Ninja/CMakeLists.txt
index 2a0591e..8eb5748 100644
--- a/Tests/RunCMake/Ninja/CMakeLists.txt
+++ b/Tests/RunCMake/Ninja/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.2)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake NO_POLICY_SCOPE)
diff --git a/Tests/RunCMake/Ninja/CommandConcat.cmake b/Tests/RunCMake/Ninja/CommandConcat.cmake
index 790cf9d..7d6faf5 100644
--- a/Tests/RunCMake/Ninja/CommandConcat.cmake
+++ b/Tests/RunCMake/Ninja/CommandConcat.cmake
@@ -1,5 +1,3 @@
-cmake_minimum_required(VERSION 3.7)
-project(concat_cmd NONE)
 set(output1 ${CMAKE_BINARY_DIR}/out1.txt)
 set(output2 ${CMAKE_BINARY_DIR}/out2.txt)
 file(REMOVE ${output1} ${output2})
diff --git a/Tests/RunCMake/Ninja/CustomCommandDepfile-check.cmake b/Tests/RunCMake/Ninja/CustomCommandDepfile-check.cmake
index 51f4f52..edde0c0 100644
--- a/Tests/RunCMake/Ninja/CustomCommandDepfile-check.cmake
+++ b/Tests/RunCMake/Ninja/CustomCommandDepfile-check.cmake
@@ -3,8 +3,10 @@
 
 set(RunCMake_TEST_FAILED)
 if(NOT "${build_file}" MATCHES "depfile = test\\.d")
-  list(APPEND RunCMake_TEST_FAILED "Log file:\n ${log}\ndoes not have expected line: depfile = test.d")
+  string(CONCAT no_test_d "Log file:\n ${log}\n" "does not have expected line: depfile = test.d")
+  list(APPEND RunCMake_TEST_FAILED "${no_test_d}")
 endif()
 if(NOT "${build_file}" MATCHES "depfile = test_Debug\\.d")
-  list(APPEND RunCMake_TEST_FAILED "\nLog file:\n ${log}\ndoes not have expected line: depfile = test_Debug.d")
+  string(CONCAT no_test_Debug_d "\nLog file:\n ${log}\n" "does not have expected line: depfile = test_Debug.d")
+  list(APPEND RunCMake_TEST_FAILED "${no_test_Debug_d}")
 endif()
diff --git a/Tests/RunCMake/Ninja/CustomCommandExplicitDepends.cmake b/Tests/RunCMake/Ninja/CustomCommandExplicitDepends.cmake
new file mode 100644
index 0000000..fefd86a
--- /dev/null
+++ b/Tests/RunCMake/Ninja/CustomCommandExplicitDepends.cmake
@@ -0,0 +1,36 @@
+cmake_minimum_required(VERSION 3.26)
+project(CustomCommandExplicitDepends C)
+
+add_custom_command(
+  OUTPUT  "${CMAKE_CURRENT_BINARY_DIR}/command-option.h"
+  COMMAND "${CMAKE_COMMAND}" -E touch
+          "${CMAKE_CURRENT_BINARY_DIR}/command-option.h"
+  COMMENT "Creating command-option.h"
+  DEPENDS_EXPLICIT_ONLY
+)
+
+set(CMAKE_ADD_CUSTOM_COMMAND_DEPENDS_EXPLICIT_ONLY ON)
+add_custom_command(
+  OUTPUT  "${CMAKE_CURRENT_BINARY_DIR}/command-variable-on.h"
+  COMMAND "${CMAKE_COMMAND}" -E touch
+          "${CMAKE_CURRENT_BINARY_DIR}/command-variable-on.h"
+  COMMENT "Creating command-variable-on.h"
+)
+
+set(CMAKE_ADD_CUSTOM_COMMAND_DEPENDS_EXPLICIT_ONLY OFF)
+add_custom_command(
+  OUTPUT  "${CMAKE_CURRENT_BINARY_DIR}/command-variable-off.h"
+  COMMAND "${CMAKE_COMMAND}" -E touch
+          "${CMAKE_CURRENT_BINARY_DIR}/command-variable-off.h"
+  COMMENT "Creating command-variable-off.h"
+)
+
+add_library(dep SHARED dep.c)
+
+add_library(top SHARED
+  top.c
+  "${CMAKE_CURRENT_BINARY_DIR}/command-option.h"
+  "${CMAKE_CURRENT_BINARY_DIR}/command-variable-on.h"
+  "${CMAKE_CURRENT_BINARY_DIR}/command-variable-off.h"
+)
+target_link_libraries(top PRIVATE dep)
diff --git a/Tests/RunCMake/Ninja/CustomCommandJobPool-check.cmake b/Tests/RunCMake/Ninja/CustomCommandJobPool-check.cmake
index 7f7fa33..793b5d2 100644
--- a/Tests/RunCMake/Ninja/CustomCommandJobPool-check.cmake
+++ b/Tests/RunCMake/Ninja/CustomCommandJobPool-check.cmake
@@ -1,8 +1,8 @@
 set(log "${RunCMake_BINARY_DIR}/CustomCommandJobPool-build/build.ninja")
 file(READ "${log}" build_file)
 if(NOT "${build_file}" MATCHES "pool = custom_command_pool")
-  set(RunCMake_TEST_FAILED "Log file:\n ${log}\ndoes not have expected line: pool = custom_command_pool")
+  string(CONCAT RunCMake_TEST_FAILED "Log file:\n ${log}\n" "does not have expected line: pool = custom_command_pool")
 endif()
 if(NOT "${build_file}" MATCHES "pool = custom_target_pool")
-  set(RunCMake_TEST_FAILED "Log file:\n ${log}\ndoes not have expected line: pool = custom_target_pool")
+  string(CONCAT RunCMake_TEST_FAILED "Log file:\n ${log}\n" "does not have expected line: pool = custom_target_pool")
 endif()
diff --git a/Tests/RunCMake/Ninja/CustomCommandWorkingDirectory.cmake b/Tests/RunCMake/Ninja/CustomCommandWorkingDirectory.cmake
index 8e01c8c..e04ac21 100644
--- a/Tests/RunCMake/Ninja/CustomCommandWorkingDirectory.cmake
+++ b/Tests/RunCMake/Ninja/CustomCommandWorkingDirectory.cmake
@@ -1,6 +1,3 @@
-cmake_minimum_required(VERSION 3.5)
-project(hello NONE)
-
 add_custom_command(
   OUTPUT hello.copy.c
   COMMAND "${CMAKE_COMMAND}" -E copy
diff --git a/Tests/RunCMake/Ninja/Executable.cmake b/Tests/RunCMake/Ninja/Executable.cmake
index 4e17d68..2b6a61b 100644
--- a/Tests/RunCMake/Ninja/Executable.cmake
+++ b/Tests/RunCMake/Ninja/Executable.cmake
@@ -1,5 +1,4 @@
-cmake_minimum_required(VERSION 3.5)
-project(hello C)
+enable_language(C)
 add_executable(hello hello.c)
 include(CheckOutput.cmake)
 include(CheckNoPrefixSubDir.cmake)
diff --git a/Tests/RunCMake/Ninja/LooseObjectDepends.cmake b/Tests/RunCMake/Ninja/LooseObjectDepends.cmake
index 360c7ba..90f8249 100644
--- a/Tests/RunCMake/Ninja/LooseObjectDepends.cmake
+++ b/Tests/RunCMake/Ninja/LooseObjectDepends.cmake
@@ -1,5 +1,4 @@
-cmake_minimum_required(VERSION 3.8)
-project(LooseObjectDepends C)
+enable_language(C)
 
 add_custom_command(
   OUTPUT  "${CMAKE_CURRENT_BINARY_DIR}/command.h"
diff --git a/Tests/RunCMake/Ninja/PreventConfigureFileDupBuildRule.cmake b/Tests/RunCMake/Ninja/PreventConfigureFileDupBuildRule.cmake
index 505f750..9615c56 100644
--- a/Tests/RunCMake/Ninja/PreventConfigureFileDupBuildRule.cmake
+++ b/Tests/RunCMake/Ninja/PreventConfigureFileDupBuildRule.cmake
@@ -1,5 +1,4 @@
-cmake_minimum_required(VERSION 3.12)
-project(Test LANGUAGES C)
+enable_language(C)
 
 configure_file(PreventConfigureFileDupBuildRule.cmake PreventTargetAliasesDupBuildRule.cmake @ONLY)
 add_subdirectory(SubDirConfigureFileDup)
diff --git a/Tests/RunCMake/Ninja/PreventTargetAliasesDupBuildRule.cmake b/Tests/RunCMake/Ninja/PreventTargetAliasesDupBuildRule.cmake
index da6f86a..81eb731 100644
--- a/Tests/RunCMake/Ninja/PreventTargetAliasesDupBuildRule.cmake
+++ b/Tests/RunCMake/Ninja/PreventTargetAliasesDupBuildRule.cmake
@@ -1,5 +1,4 @@
-cmake_minimum_required(VERSION 3.12)
-project(Test LANGUAGES C)
+enable_language(C)
 
 # fake launcher executable
 set(input_launcher_executable ${CMAKE_CURRENT_BINARY_DIR}/fake_launcher_executable)
diff --git a/Tests/RunCMake/Ninja/RunCMakeTest.cmake b/Tests/RunCMake/Ninja/RunCMakeTest.cmake
index a52c64e..6eb0b1d 100644
--- a/Tests/RunCMake/Ninja/RunCMakeTest.cmake
+++ b/Tests/RunCMake/Ninja/RunCMakeTest.cmake
@@ -192,6 +192,38 @@
 endfunction ()
 run_LooseObjectDepends()
 
+function (run_CustomCommandExplictDepends)
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/CustomCommandExplicitDepends-build)
+  run_cmake(CustomCommandExplicitDepends)
+
+  set(DEP_LIB "${RunCMake_TEST_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}dep${CMAKE_SHARED_LIBRARY_SUFFIX}")
+
+  run_ninja("${RunCMake_TEST_BINARY_DIR}" "command-option.h")
+  if (EXISTS "${DEP_LIB}")
+    message(FATAL_ERROR
+      "The `dep` library was created when requesting a custom command to be "
+      "generated; this should no longer be necessary when passing "
+      "DEPENDS_EXPLICIT_ONLY option.")
+  endif ()
+
+  run_ninja("${RunCMake_TEST_BINARY_DIR}" "command-variable-on.h")
+  if (EXISTS "${DEP_LIB}")
+    message(FATAL_ERROR
+      "The `dep` library was created when requesting a custom command to be "
+      "generated; this should no longer be necessary when setting "
+      "CMAKE_ADD_CUSTOM_COMMAND_DEPENDS_EXPLICIT_ONLY variable to ON.")
+  endif ()
+
+  run_ninja("${RunCMake_TEST_BINARY_DIR}" "command-variable-off.h")
+  if (NOT EXISTS "${DEP_LIB}")
+    message(FATAL_ERROR
+      "The `dep` library was not created when requesting a custom command to be "
+      "generated; this should be necessary when setting "
+      "CMAKE_ADD_CUSTOM_COMMAND_DEPENDS_EXPLICIT_ONLY variable to OFF.")
+  endif ()
+endfunction ()
+run_CustomCommandExplictDepends()
+
 function (run_AssumedSources)
   set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/AssumedSources-build)
   run_cmake(AssumedSources)
diff --git a/Tests/RunCMake/Ninja/SharedLib.cmake b/Tests/RunCMake/Ninja/SharedLib.cmake
index 1a78390..c295c16 100644
--- a/Tests/RunCMake/Ninja/SharedLib.cmake
+++ b/Tests/RunCMake/Ninja/SharedLib.cmake
@@ -1,5 +1,4 @@
-cmake_minimum_required(VERSION 3.5)
-project(hello C)
+enable_language(C)
 add_library(greeting SHARED greeting.c)
 include_directories(${CMAKE_CURRENT_SOURCE_DIR})
 add_executable(hello hello_with_greeting.c)
diff --git a/Tests/RunCMake/Ninja/StaticLib.cmake b/Tests/RunCMake/Ninja/StaticLib.cmake
index 0f815ae..dab6742 100644
--- a/Tests/RunCMake/Ninja/StaticLib.cmake
+++ b/Tests/RunCMake/Ninja/StaticLib.cmake
@@ -1,5 +1,4 @@
-cmake_minimum_required(VERSION 3.5)
-project(hello C)
+enable_language(C)
 add_definitions(-DGREETING_STATIC)
 add_library(greeting STATIC greeting.c)
 include_directories(${CMAKE_CURRENT_SOURCE_DIR})
diff --git a/Tests/RunCMake/Ninja/SubDirPrefix.cmake b/Tests/RunCMake/Ninja/SubDirPrefix.cmake
index 30ad1e6..49d075f 100644
--- a/Tests/RunCMake/Ninja/SubDirPrefix.cmake
+++ b/Tests/RunCMake/Ninja/SubDirPrefix.cmake
@@ -1,5 +1,4 @@
-cmake_minimum_required(VERSION 3.5)
-project(hello C)
+enable_language(C)
 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin")
 add_subdirectory(SubDirPrefix)
 include_directories(${CMAKE_CURRENT_SOURCE_DIR})
diff --git a/Tests/RunCMake/Ninja/TwoLibs.cmake b/Tests/RunCMake/Ninja/TwoLibs.cmake
index 666452f..10ac5a6 100644
--- a/Tests/RunCMake/Ninja/TwoLibs.cmake
+++ b/Tests/RunCMake/Ninja/TwoLibs.cmake
@@ -1,5 +1,4 @@
-cmake_minimum_required(VERSION 3.5)
-project(hello C)
+enable_language(C)
 
 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
 set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib-static")
diff --git a/Tests/RunCMake/Ninja/VerboseBuild-nowork-stdout.txt b/Tests/RunCMake/Ninja/VerboseBuild-nowork-stdout.txt
index 60a9228..40b4527 100644
--- a/Tests/RunCMake/Ninja/VerboseBuild-nowork-stdout.txt
+++ b/Tests/RunCMake/Ninja/VerboseBuild-nowork-stdout.txt
@@ -1 +1 @@
-^ninja: no work to do
+ninja: no work to do
diff --git a/Tests/RunCMake/NinjaMultiConfig/CustomCommandDepfile-check.cmake b/Tests/RunCMake/NinjaMultiConfig/CustomCommandDepfile-check.cmake
index a7837ca..3674aba 100644
--- a/Tests/RunCMake/NinjaMultiConfig/CustomCommandDepfile-check.cmake
+++ b/Tests/RunCMake/NinjaMultiConfig/CustomCommandDepfile-check.cmake
@@ -3,8 +3,10 @@
 
 set(RunCMake_TEST_FAILED)
 if(NOT "${build_file}" MATCHES "depfile = test\\.d")
-  list(APPEND RunCMake_TEST_FAILED "Log file:\n ${log}\ndoes not have expected line: depfile = test.d")
+  string(CONCAT no_test_d "Log file:\n ${log}\n" "does not have expected line: depfile = test.d")
+  list(APPEND RunCMake_TEST_FAILED "${no_test_d}")
 endif()
 if(NOT "${build_file}" MATCHES "depfile = test_Debug\\.d")
-  list(APPEND RunCMake_TEST_FAILED "\nLog file:\n ${log}\ndoes not have expected line: depfile = test_Debug.d")
+  string(CONCAT no_test_Debug_d "\nLog file:\n ${log}\n" "does not have expected line: depfile = test_Debug.d")
+  list(APPEND RunCMake_TEST_FAILED "${no_test_Debug_d}")
 endif()
diff --git a/Tests/RunCMake/NinjaMultiConfig/OutputPathPrefix-all-ninja-stdout.txt b/Tests/RunCMake/NinjaMultiConfig/OutputPathPrefix-all-ninja-stdout.txt
new file mode 100644
index 0000000..f5bce43
--- /dev/null
+++ b/Tests/RunCMake/NinjaMultiConfig/OutputPathPrefix-all-ninja-stdout.txt
@@ -0,0 +1 @@
+tgt has been built
diff --git a/Tests/RunCMake/NinjaMultiConfig/OutputPathPrefix.cmake b/Tests/RunCMake/NinjaMultiConfig/OutputPathPrefix.cmake
new file mode 100644
index 0000000..c4e0587
--- /dev/null
+++ b/Tests/RunCMake/NinjaMultiConfig/OutputPathPrefix.cmake
@@ -0,0 +1 @@
+add_custom_target(tgt ALL COMMAND ${CMAKE_COMMAND} -E echo "tgt has been built")
diff --git a/Tests/RunCMake/NinjaMultiConfig/RunCMakeTest.cmake b/Tests/RunCMake/NinjaMultiConfig/RunCMakeTest.cmake
index c040e8f..47f5eee 100644
--- a/Tests/RunCMake/NinjaMultiConfig/RunCMakeTest.cmake
+++ b/Tests/RunCMake/NinjaMultiConfig/RunCMakeTest.cmake
@@ -459,6 +459,15 @@
 run_cmake(CompileCommands)
 unset(RunCMake_TEST_OPTIONS)
 
+set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/OutputPathPrefix-build)
+run_cmake_with_options(OutputPathPrefix "-DCMAKE_NINJA_OUTPUT_PATH_PREFIX=OutputPathPrefix-build")
+set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR})
+set(RunCMake_TEST_NO_CLEAN 1)
+run_cmake_command(OutputPathPrefix-all-ninja "${RunCMake_MAKE_PROGRAM}" -f OutputPathPrefix-build/build.ninja OutputPathPrefix-build/all)
+run_cmake_command(OutputPathPrefix-clean-ninja "${RunCMake_MAKE_PROGRAM}" -f OutputPathPrefix-build/build.ninja OutputPathPrefix-build/clean)
+unset(RunCMake_TEST_NO_CLEAN)
+unset(RunCMake_TEST_BINARY_DIR)
+
 # CudaSimple uses separable compilation, which is currently only supported on NVCC.
 if(CMake_TEST_CUDA)
   set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/CudaSimple-build)
diff --git a/Tests/RunCMake/ObjectLibrary/MissingSource-stderr.txt b/Tests/RunCMake/ObjectLibrary/MissingSource-stderr.txt
index 5c7882d..05eb42d 100644
--- a/Tests/RunCMake/ObjectLibrary/MissingSource-stderr.txt
+++ b/Tests/RunCMake/ObjectLibrary/MissingSource-stderr.txt
@@ -3,7 +3,7 @@
 
     missing.c
 
-  Tried extensions \.c \.C .*
-.*
-Call Stack \(most recent call first\):
+  Tried extensions ([^
+]+
+)+Call Stack \(most recent call first\):
   CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/ParseImplicitIncludeInfo/CMakeLists.txt b/Tests/RunCMake/ParseImplicitIncludeInfo/CMakeLists.txt
index 2897109..3e470a2 100644
--- a/Tests/RunCMake/ParseImplicitIncludeInfo/CMakeLists.txt
+++ b/Tests/RunCMake/ParseImplicitIncludeInfo/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.14)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/ParseImplicitIncludeInfo/ParseImplicitIncludeInfo.cmake b/Tests/RunCMake/ParseImplicitIncludeInfo/ParseImplicitIncludeInfo.cmake
index 75c26a7..6027f03 100644
--- a/Tests/RunCMake/ParseImplicitIncludeInfo/ParseImplicitIncludeInfo.cmake
+++ b/Tests/RunCMake/ParseImplicitIncludeInfo/ParseImplicitIncludeInfo.cmake
@@ -1,6 +1,3 @@
-cmake_minimum_required(VERSION 3.14)
-project(Minimal NONE)
-
 #
 # list of targets to test.  to add a target: put its files in the data
 # subdirectory and add it to this list...  we run each target's
diff --git a/Tests/RunCMake/ParseImplicitLinkInfo/CMakeLists.txt b/Tests/RunCMake/ParseImplicitLinkInfo/CMakeLists.txt
index 2897109..3e470a2 100644
--- a/Tests/RunCMake/ParseImplicitLinkInfo/CMakeLists.txt
+++ b/Tests/RunCMake/ParseImplicitLinkInfo/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.14)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/ParseImplicitLinkInfo/ExcludeDirs.cmake b/Tests/RunCMake/ParseImplicitLinkInfo/ExcludeDirs.cmake
new file mode 100644
index 0000000..6cece68
--- /dev/null
+++ b/Tests/RunCMake/ParseImplicitLinkInfo/ExcludeDirs.cmake
@@ -0,0 +1,10 @@
+include("${info}")
+list(GET INFO_CMAKE_C_IMPLICIT_LINK_DIRECTORIES -1 last_dir)
+set(ENV{CMAKE_C_IMPLICIT_LINK_DIRECTORIES_EXCLUDE} "${last_dir}")
+enable_language(C)
+message(STATUS "INFO_CMAKE_C_IMPLICIT_LINK_DIRECTORIES=[${INFO_CMAKE_C_IMPLICIT_LINK_DIRECTORIES}]")
+message(STATUS "ENV{CMAKE_C_IMPLICIT_LINK_DIRECTORIES_EXCLUDE}=[$ENV{CMAKE_C_IMPLICIT_LINK_DIRECTORIES_EXCLUDE}]")
+message(STATUS "CMAKE_C_IMPLICIT_LINK_DIRECTORIES=[${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}]")
+if("${last_dir}" IN_LIST CMAKE_C_IMPLICIT_LINK_DIRECTORIES)
+  message(FATAL_ERROR "${last_dir} was not excluded!")
+endif()
diff --git a/Tests/RunCMake/ParseImplicitLinkInfo/Inspect.cmake b/Tests/RunCMake/ParseImplicitLinkInfo/Inspect.cmake
new file mode 100644
index 0000000..42e1c67
--- /dev/null
+++ b/Tests/RunCMake/ParseImplicitLinkInfo/Inspect.cmake
@@ -0,0 +1,12 @@
+enable_language(C)
+
+set(info "")
+foreach(var
+    CMAKE_C_IMPLICIT_LINK_DIRECTORIES
+    )
+  if(DEFINED ${var})
+    string(APPEND info "set(INFO_${var} \"${${var}}\")\n")
+  endif()
+endforeach()
+
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/info.cmake" "${info}")
diff --git a/Tests/RunCMake/ParseImplicitLinkInfo/ParseImplicitLinkInfo.cmake b/Tests/RunCMake/ParseImplicitLinkInfo/ParseImplicitLinkInfo.cmake
index df4ef1f..fa7bf07 100644
--- a/Tests/RunCMake/ParseImplicitLinkInfo/ParseImplicitLinkInfo.cmake
+++ b/Tests/RunCMake/ParseImplicitLinkInfo/ParseImplicitLinkInfo.cmake
@@ -1,6 +1,3 @@
-cmake_minimum_required(VERSION 3.14)
-project(Minimal NONE)
-
 #
 # list of targets to test.  to add a target: put its files in the data
 # subdirectory and add it to this list...  we run each target's
diff --git a/Tests/RunCMake/ParseImplicitLinkInfo/RunCMakeTest.cmake b/Tests/RunCMake/ParseImplicitLinkInfo/RunCMakeTest.cmake
index 713e2e7..c7655d2 100644
--- a/Tests/RunCMake/ParseImplicitLinkInfo/RunCMakeTest.cmake
+++ b/Tests/RunCMake/ParseImplicitLinkInfo/RunCMakeTest.cmake
@@ -1,3 +1,11 @@
 include(RunCMake)
 
 run_cmake(ParseImplicitLinkInfo)
+
+run_cmake(Inspect)
+set(info "${RunCMake_BINARY_DIR}/Inspect-build/info.cmake")
+include("${info}")
+
+if(INFO_CMAKE_C_IMPLICIT_LINK_DIRECTORIES MATCHES ";")
+  run_cmake_with_options(ExcludeDirs "-Dinfo=${RunCMake_BINARY_DIR}/Inspect-build/info.cmake")
+endif()
diff --git a/Tests/RunCMake/PolicyScope/RunCMakeTest.cmake b/Tests/RunCMake/PolicyScope/RunCMakeTest.cmake
index abd27f4..da608b3 100644
--- a/Tests/RunCMake/PolicyScope/RunCMakeTest.cmake
+++ b/Tests/RunCMake/PolicyScope/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 run_cmake(NotClosed)
 run_cmake(NotOpened)
diff --git a/Tests/RunCMake/PositionIndependentCode/CMakeLists.txt b/Tests/RunCMake/PositionIndependentCode/CMakeLists.txt
index c3922d6..42b0577 100644
--- a/Tests/RunCMake/PositionIndependentCode/CMakeLists.txt
+++ b/Tests/RunCMake/PositionIndependentCode/CMakeLists.txt
@@ -1,5 +1,4 @@
-
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} CXX)
 
 # MSVC creates extra targets which pollute the stderr unless we set this.
diff --git a/Tests/RunCMake/README.rst b/Tests/RunCMake/README.rst
index ebe40cf..d8cae8b 100644
--- a/Tests/RunCMake/README.rst
+++ b/Tests/RunCMake/README.rst
@@ -82,3 +82,10 @@
 
 This will only run subtests in ``RunCMake.Example`` that start with
 ``example``.
+
+To speed up the process of creating a new ``RunCMake`` test, you can run a
+script that will automatically perform steps 1 through 4 for you::
+
+  cmake -DRunCMake_TEST_SUITE=<test suite name> -P Tests/RunCMake/AddRunCMakeTestSuite.cmake
+
+Be sure to run this from the top-level CMake source directory.
diff --git a/Tests/RunCMake/RunCMake.cmake b/Tests/RunCMake/RunCMake.cmake
index faff957..bc4a330 100644
--- a/Tests/RunCMake/RunCMake.cmake
+++ b/Tests/RunCMake/RunCMake.cmake
@@ -11,8 +11,12 @@
 endforeach()
 
 function(run_cmake test)
-  if(DEFINED ENV{RunCMake_TEST_FILTER} AND NOT test MATCHES "$ENV{RunCMake_TEST_FILTER}")
-    return()
+  if(DEFINED ENV{RunCMake_TEST_FILTER})
+    set(test_and_variant "${test}${RunCMake_TEST_VARIANT_DESCRIPTION}")
+    if(NOT test_and_variant MATCHES "$ENV{RunCMake_TEST_FILTER}")
+      return()
+    endif()
+    unset(test_and_variant)
   endif()
 
   set(top_src "${RunCMake_SOURCE_DIR}")
@@ -43,6 +47,8 @@
     elseif(EXISTS ${top_src}/${test}-${o}.txt)
       file(READ ${top_src}/${test}-${o}.txt expect_${o})
       string(REGEX REPLACE "\n+$" "" expect_${o} "${expect_${o}}")
+    elseif(DEFINED RunCMake_TEST_EXPECT_${o})
+      string(REGEX REPLACE "\n+$" "" expect_${o} "${RunCMake_TEST_EXPECT_${o}}")
     else()
       unset(expect_${o})
     endif()
@@ -94,7 +100,7 @@
     if(APPLE)
       list(APPEND RunCMake_TEST_OPTIONS -DCMAKE_POLICY_DEFAULT_CMP0025=NEW)
     endif()
-    if(NOT RunCMake_TEST_NO_CMP0129 AND CMAKE_C_COMPILER_ID STREQUAL "LCC")
+    if(RunCMake_TEST_LCC AND NOT RunCMake_TEST_NO_CMP0129)
       list(APPEND RunCMake_TEST_OPTIONS -DCMAKE_POLICY_DEFAULT_CMP0129=NEW)
     endif()
     if(RunCMake_MAKE_PROGRAM)
@@ -161,7 +167,9 @@
     "|BullseyeCoverage"
     "|[a-z]+\\([0-9]+\\) malloc:"
     "|clang[^:]*: warning: the object size sanitizer has no effect at -O0, but is explicitly enabled:"
+    "|flang-new: warning: argument unused during compilation: .-flang-experimental-exec."
     "|icp?x: remark: Note that use of .-g. without any optimization-level option will turn off most compiler optimizations"
+    "|ifx: remark #10440: Note that use of a debug option without any optimization-level option will turnoff most compiler optimizations"
     "|lld-link: warning: procedure symbol record for .* refers to PDB item index [0-9A-Fa-fx]+ which is not a valid function ID record"
     "|Error kstat returned"
     "|Hit xcodebuild bug"
@@ -171,7 +179,7 @@
     "|Your license to use PGI[^\n]*expired"
     "|Please obtain a new version at"
     "|contact PGI Sales at"
-    "|icp?c: remark #10441: The Intel\\(R\\) C\\+\\+ Compiler Classic \\(ICC\\) is deprecated"
+    "|ic(p?c|l): remark #10441: The Intel\\(R\\) C\\+\\+ Compiler Classic \\(ICC\\) is deprecated"
 
     "|[^\n]*install_name_tool: warning: changes being made to the file will invalidate the code signature in:"
     "|[^\n]*xcodebuild[^\n]*DVTCoreDeviceEnabledState: DVTCoreDeviceEnabledState_Disabled set via user default"
@@ -187,6 +195,18 @@
     "|[^\n]*Bullseye Testing Technology"
     ")[^\n]*\n)+"
     )
+  if(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION)
+    string(REGEX REPLACE [[
+^CMake Deprecation Warning at [^
+]*CMakeLists.txt:1 \(cmake_minimum_required\):
+  Compatibility with CMake < 3\.5 will be removed from a future version of
+  CMake.
+
+  Update the VERSION argument <min> value or use a \.\.\.<max> suffix to tell
+  CMake that the project does not need compatibility with older versions\.
++
+]] "" actual_stderr "${actual_stderr}")
+  endif()
   foreach(o IN ITEMS stdout stderr config)
     string(REGEX REPLACE "\r\n" "\n" actual_${o} "${actual_${o}}")
     string(REGEX REPLACE "${ignore_line_regex}" "\\1" actual_${o} "${actual_${o}}")
diff --git a/Tests/RunCMake/Swift/CMakeLists.txt b/Tests/RunCMake/Swift/CMakeLists.txt
index 74b3ff8..77030d6 100644
--- a/Tests/RunCMake/Swift/CMakeLists.txt
+++ b/Tests/RunCMake/Swift/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.15)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/Swift/SwiftMultiArch-stderr.txt b/Tests/RunCMake/Swift/SwiftMultiArch-stderr.txt
index 874bdc7..16d2e21 100644
--- a/Tests/RunCMake/Swift/SwiftMultiArch-stderr.txt
+++ b/Tests/RunCMake/Swift/SwiftMultiArch-stderr.txt
@@ -1,4 +1,4 @@
-^CMake Error at SwiftMultiArch.cmake:3 \(project\):
+^CMake Error at SwiftMultiArch.cmake:2 \(enable_language\):
   multiple values for CMAKE_OSX_ARCHITECTURES not supported with Swift
 Call Stack \(most recent call first\):
   CMakeLists.txt:3
diff --git a/Tests/RunCMake/Swift/SwiftMultiArch.cmake b/Tests/RunCMake/Swift/SwiftMultiArch.cmake
index 5fdb688..b59bb62 100644
--- a/Tests/RunCMake/Swift/SwiftMultiArch.cmake
+++ b/Tests/RunCMake/Swift/SwiftMultiArch.cmake
@@ -1,4 +1,2 @@
-cmake_minimum_required(VERSION 3.15.1)
 set(CMAKE_OSX_ARCHITECTURES "armv7;arm64;i386;x86_64")
-project(SwiftMultiArch
-  LANGUAGES Swift)
+enable_language(Swift)
diff --git a/Tests/RunCMake/SymlinkTrees/CMakeLists.txt b/Tests/RunCMake/SymlinkTrees/CMakeLists.txt
index e16faea..919a168 100644
--- a/Tests/RunCMake/SymlinkTrees/CMakeLists.txt
+++ b/Tests/RunCMake/SymlinkTrees/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 
 message(STATUS "source: '${CMAKE_SOURCE_DIR}'")
diff --git a/Tests/RunCMake/Syntax/RunCMakeTest.cmake b/Tests/RunCMake/Syntax/RunCMakeTest.cmake
index f0c287c..f56ac64 100644
--- a/Tests/RunCMake/Syntax/RunCMakeTest.cmake
+++ b/Tests/RunCMake/Syntax/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 run_cmake(BOM-UTF-8)
 run_cmake(BOM-UTF-16-LE)
diff --git a/Tests/RunCMake/Syntax/String1-stderr.txt b/Tests/RunCMake/Syntax/String1-stderr.txt
index 07e98da..382ea00 100644
--- a/Tests/RunCMake/Syntax/String1-stderr.txt
+++ b/Tests/RunCMake/Syntax/String1-stderr.txt
@@ -1,3 +1,3 @@
-^
+^'
   1 \${var} 4
- $
+ '$
diff --git a/Tests/RunCMake/Syntax/String1.cmake b/Tests/RunCMake/Syntax/String1.cmake
index a94c9ff..20ed3c1 100644
--- a/Tests/RunCMake/Syntax/String1.cmake
+++ b/Tests/RunCMake/Syntax/String1.cmake
@@ -1,3 +1,3 @@
-message("
+message("'
   1 \${var} 4
- ")
+ '")
diff --git a/Tests/RunCMake/Syntax/UnterminatedBrace0-stderr.txt b/Tests/RunCMake/Syntax/UnterminatedBrace0-stderr.txt
index b309c3b..c7238df 100644
--- a/Tests/RunCMake/Syntax/UnterminatedBrace0-stderr.txt
+++ b/Tests/RunCMake/Syntax/UnterminatedBrace0-stderr.txt
@@ -1,7 +1,7 @@
-CMake Error at UnterminatedBrace0.cmake:2 \(set\):
+CMake Error at UnterminatedBrace0.cmake:1 \(set\):
   Syntax error in cmake code at
 
-    .*/Tests/RunCMake/Syntax/UnterminatedBrace0.cmake:2
+    .*/Tests/RunCMake/Syntax/UnterminatedBrace0.cmake:1
 
   when parsing string
 
diff --git a/Tests/RunCMake/Syntax/UnterminatedBrace0.cmake b/Tests/RunCMake/Syntax/UnterminatedBrace0.cmake
index 0da1290..09af0ce 100644
--- a/Tests/RunCMake/Syntax/UnterminatedBrace0.cmake
+++ b/Tests/RunCMake/Syntax/UnterminatedBrace0.cmake
@@ -1,2 +1 @@
-cmake_minimum_required(VERSION 3.0)
 set(var "${")
diff --git a/Tests/RunCMake/Syntax/UnterminatedBrace1-stderr.txt b/Tests/RunCMake/Syntax/UnterminatedBrace1-stderr.txt
index ffe0e2a..3d88f36 100644
--- a/Tests/RunCMake/Syntax/UnterminatedBrace1-stderr.txt
+++ b/Tests/RunCMake/Syntax/UnterminatedBrace1-stderr.txt
@@ -1,7 +1,7 @@
-CMake Warning \(dev\) at UnterminatedBrace1.cmake:3 \(set\):
+CMake Warning \(dev\) at UnterminatedBrace1.cmake:2 \(set\):
   Syntax error in cmake code at
 
-    .*/Tests/RunCMake/Syntax/UnterminatedBrace1.cmake:3
+    .*/Tests/RunCMake/Syntax/UnterminatedBrace1.cmake:2
 
   when parsing string
 
diff --git a/Tests/RunCMake/Syntax/UnterminatedBrace1.cmake b/Tests/RunCMake/Syntax/UnterminatedBrace1.cmake
index 93fba34..8b40b19 100644
--- a/Tests/RunCMake/Syntax/UnterminatedBrace1.cmake
+++ b/Tests/RunCMake/Syntax/UnterminatedBrace1.cmake
@@ -1,3 +1,2 @@
-cmake_minimum_required(VERSION 3.0)
 cmake_policy(SET CMP0010 OLD)
 set(var "${")
diff --git a/Tests/RunCMake/Syntax/UnterminatedBrace2-stderr.txt b/Tests/RunCMake/Syntax/UnterminatedBrace2-stderr.txt
index b332d34..0d76251 100644
--- a/Tests/RunCMake/Syntax/UnterminatedBrace2-stderr.txt
+++ b/Tests/RunCMake/Syntax/UnterminatedBrace2-stderr.txt
@@ -1,7 +1,7 @@
-CMake Error at UnterminatedBrace2.cmake:4 \(set\):
+CMake Error at UnterminatedBrace2.cmake:3 \(set\):
   Syntax error in cmake code at
 
-    .*/Tests/RunCMake/Syntax/UnterminatedBrace2.cmake:4
+    .*/Tests/RunCMake/Syntax/UnterminatedBrace2.cmake:3
 
   when parsing string
 
diff --git a/Tests/RunCMake/Syntax/UnterminatedBrace2.cmake b/Tests/RunCMake/Syntax/UnterminatedBrace2.cmake
index a650e5b..30d4d4d 100644
--- a/Tests/RunCMake/Syntax/UnterminatedBrace2.cmake
+++ b/Tests/RunCMake/Syntax/UnterminatedBrace2.cmake
@@ -1,4 +1,3 @@
-cmake_minimum_required(VERSION 3.0)
 cmake_policy(SET CMP0010 OLD)
 cmake_policy(SET CMP0053 NEW)
 set(var "${")
diff --git a/Tests/RunCMake/ArtifactOutputDirs/CMakeLists.txt b/Tests/RunCMake/TargetArtifacts/CMakeLists.txt
similarity index 100%
rename from Tests/RunCMake/ArtifactOutputDirs/CMakeLists.txt
rename to Tests/RunCMake/TargetArtifacts/CMakeLists.txt
diff --git a/Tests/RunCMake/TargetArtifacts/DLL-SOVERSION-build-stdout.txt b/Tests/RunCMake/TargetArtifacts/DLL-SOVERSION-build-stdout.txt
new file mode 100644
index 0000000..b375da6
--- /dev/null
+++ b/Tests/RunCMake/TargetArtifacts/DLL-SOVERSION-build-stdout.txt
@@ -0,0 +1,2 @@
+.*exA_name="(libexA\.so\.2|libexA\.2\.dylib|(lib|cyg|msys-|)exA-2\.dll)"
+.*exB_name="(libexB\.so\.2|libexB\.2\.dylib|(lib|cyg|msys-|)exB-2\.dll)"
diff --git a/Tests/RunCMake/TargetArtifacts/DLL-SOVERSION.cmake b/Tests/RunCMake/TargetArtifacts/DLL-SOVERSION.cmake
new file mode 100644
index 0000000..82eca0b
--- /dev/null
+++ b/Tests/RunCMake/TargetArtifacts/DLL-SOVERSION.cmake
@@ -0,0 +1,18 @@
+enable_language(C)
+
+add_library(exA SHARED dll.c)
+set_target_properties(exA PROPERTIES
+  SOVERSION 2
+  DLL_NAME_WITH_SOVERSION 1
+  )
+
+set(CMAKE_DLL_NAME_WITH_SOVERSION 1)
+add_library(exB SHARED dll.c)
+set_property(TARGET exB PROPERTY SOVERSION 2)
+
+add_custom_target(checkNames ALL
+  COMMAND ${CMAKE_COMMAND} -E echo exA_name="$<TARGET_FILE_NAME:exA>"
+  COMMAND ${CMAKE_COMMAND} -E echo exB_name="$<TARGET_FILE_NAME:exB>"
+  VERBATIM
+  )
+add_dependencies(checkNames exA exB)
diff --git a/Tests/RunCMake/ArtifactOutputDirs/ArtifactOutputDirs.cmake b/Tests/RunCMake/TargetArtifacts/OutputDirs.cmake
similarity index 100%
rename from Tests/RunCMake/ArtifactOutputDirs/ArtifactOutputDirs.cmake
rename to Tests/RunCMake/TargetArtifacts/OutputDirs.cmake
diff --git a/Tests/RunCMake/TargetArtifacts/RunCMakeTest.cmake b/Tests/RunCMake/TargetArtifacts/RunCMakeTest.cmake
new file mode 100644
index 0000000..de69936
--- /dev/null
+++ b/Tests/RunCMake/TargetArtifacts/RunCMakeTest.cmake
@@ -0,0 +1,19 @@
+include(RunCMake)
+
+function(run_cmake_and_verify_after_build case)
+  set(RunCMake_TEST_BINARY_DIR "${RunCMake_BINARY_DIR}/${case}-build")
+  if(RunCMake_GENERATOR_IS_MULTI_CONFIG)
+    set(RunCMake_TEST_OPTIONS -DCMAKE_CONFIGURATION_TYPES=Debug)
+  else()
+    set(RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Debug)
+  endif()
+  run_cmake(${case})
+  set(RunCMake_TEST_NO_CLEAN 1)
+  set(RunCMake_TEST_OUTPUT_MERGE 1)
+  run_cmake_command("${case}-build" ${CMAKE_COMMAND} --build .)
+endfunction()
+
+if(NOT CMAKE_SYSTEM_NAME STREQUAL "AIX")
+  run_cmake_and_verify_after_build(DLL-SOVERSION)
+endif()
+run_cmake_and_verify_after_build(OutputDirs)
diff --git a/Tests/RunCMake/ArtifactOutputDirs/check.cmake b/Tests/RunCMake/TargetArtifacts/check.cmake
similarity index 100%
rename from Tests/RunCMake/ArtifactOutputDirs/check.cmake
rename to Tests/RunCMake/TargetArtifacts/check.cmake
diff --git a/Tests/RunCMake/TargetArtifacts/dll.c b/Tests/RunCMake/TargetArtifacts/dll.c
new file mode 100644
index 0000000..31e1dbf
--- /dev/null
+++ b/Tests/RunCMake/TargetArtifacts/dll.c
@@ -0,0 +1,6 @@
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+  void dll(void)
+{
+}
diff --git a/Tests/RunCMake/ArtifactOutputDirs/lib.c b/Tests/RunCMake/TargetArtifacts/lib.c
similarity index 100%
rename from Tests/RunCMake/ArtifactOutputDirs/lib.c
rename to Tests/RunCMake/TargetArtifacts/lib.c
diff --git a/Tests/RunCMake/ArtifactOutputDirs/main.c b/Tests/RunCMake/TargetArtifacts/main.c
similarity index 100%
rename from Tests/RunCMake/ArtifactOutputDirs/main.c
rename to Tests/RunCMake/TargetArtifacts/main.c
diff --git a/Tests/RunCMake/TargetProperties/CMakeLists.txt b/Tests/RunCMake/TargetProperties/CMakeLists.txt
index 44b5d30..26f536b 100644
--- a/Tests/RunCMake/TargetProperties/CMakeLists.txt
+++ b/Tests/RunCMake/TargetProperties/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST})
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/ToolchainFile/RunCMakeTest.cmake b/Tests/RunCMake/ToolchainFile/RunCMakeTest.cmake
index 7744ee8..b588ce0 100644
--- a/Tests/RunCMake/ToolchainFile/RunCMakeTest.cmake
+++ b/Tests/RunCMake/ToolchainFile/RunCMakeTest.cmake
@@ -14,6 +14,7 @@
 run_cmake_toolchain(CMP0126-NEW)
 run_cmake_toolchain(CMP0126-OLD)
 run_cmake_toolchain(CMP0126-WARN)
+run_cmake_toolchain(SetCrossCompiling)
 
 function(run_IncludeDirectories)
   run_cmake_toolchain(IncludeDirectories)
diff --git a/Tests/RunCMake/ToolchainFile/SetCrossCompiling-stderr.txt b/Tests/RunCMake/ToolchainFile/SetCrossCompiling-stderr.txt
new file mode 100644
index 0000000..6642cf6
--- /dev/null
+++ b/Tests/RunCMake/ToolchainFile/SetCrossCompiling-stderr.txt
@@ -0,0 +1,8 @@
+^CMake Warning \(dev\) at [^
+]*/Modules/CMakeDetermineSystem.cmake:[0-9]+ \(message\):
+  CMAKE_CROSSCOMPILING has been set by the project, toolchain file, or user\.
+  CMake is resetting it to false because CMAKE_SYSTEM_NAME was not set\.  To
+  indicate cross compilation, only CMAKE_SYSTEM_NAME needs to be set\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(project\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.$
diff --git a/Tests/RunCMake/ToolchainFile/SetCrossCompiling-toolchain.cmake b/Tests/RunCMake/ToolchainFile/SetCrossCompiling-toolchain.cmake
new file mode 100644
index 0000000..170e26c
--- /dev/null
+++ b/Tests/RunCMake/ToolchainFile/SetCrossCompiling-toolchain.cmake
@@ -0,0 +1 @@
+set(CMAKE_CROSSCOMPILING TRUE)
diff --git a/Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt b/Tests/RunCMake/ToolchainFile/SetCrossCompiling.cmake
similarity index 100%
copy from Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt
copy to Tests/RunCMake/ToolchainFile/SetCrossCompiling.cmake
diff --git a/Tests/RunCMake/TransformDepfile/deps-unix.d.txt b/Tests/RunCMake/TransformDepfile/deps-unix.d.txt
index fbdecc0..2588181 100644
--- a/Tests/RunCMake/TransformDepfile/deps-unix.d.txt
+++ b/Tests/RunCMake/TransformDepfile/deps-unix.d.txt
@@ -1,8 +1,8 @@
 subdir/out1 \
   /home/build/out2: \
-  subdir/in1 \
-  /home/build/in2
+  /home/build/in2 \
+  subdir/in1
 subdir/out3 \
   /home/build/out4: \
-  subdir/in3 \
-  /home/build/in4
+  /home/build/in4 \
+  subdir/in3
diff --git a/Tests/RunCMake/TransformDepfile/deps-windows.d.txt b/Tests/RunCMake/TransformDepfile/deps-windows.d.txt
index e09ae37..805a4c8 100644
--- a/Tests/RunCMake/TransformDepfile/deps-windows.d.txt
+++ b/Tests/RunCMake/TransformDepfile/deps-windows.d.txt
@@ -1,8 +1,8 @@
 subdir/out1 \
   C:/build/out2: \
-  subdir/in1 \
-  C:/build/in2
+  C:/build/in2 \
+  subdir/in1
 subdir/out3 \
   C:/build/out4: \
-  subdir/in3 \
-  C:/build/in4
+  C:/build/in4 \
+  subdir/in3
diff --git a/Tests/RunCMake/VS10Project/CMakeInputs-check.cmake b/Tests/RunCMake/VS10Project/CMakeInputs-check.cmake
new file mode 100644
index 0000000..a125574
--- /dev/null
+++ b/Tests/RunCMake/VS10Project/CMakeInputs-check.cmake
@@ -0,0 +1,25 @@
+set(vcProjectFile "${RunCMake_TEST_BINARY_DIR}/ZERO_CHECK.vcxproj")
+if(NOT EXISTS "${vcProjectFile}")
+  set(RunCMake_TEST_FAILED "Project file ${vcProjectFile} does not exist.")
+  return()
+endif()
+
+set(found_CMakeInputs 0)
+file(STRINGS "${vcProjectFile}" lines)
+foreach(line IN LISTS lines)
+  if(line MATCHES "<([A-Za-z0-9_]+) +Include=.*CMakeInputs.cmake")
+    set(rule "${CMAKE_MATCH_1}")
+    if(NOT rule STREQUAL "None")
+      set(RunCMake_TEST_FAILED "CMakeInputs.cmake referenced as ${rule} instead of None")
+      return()
+    endif()
+    if(found_CMakeInputs)
+      set(RunCMake_TEST_FAILED "CMakeInputs.cmake referenced multiple times")
+      return()
+    endif()
+    set(found_CMakeInputs 1)
+  endif()
+endforeach()
+if(NOT found_CMakeInputs)
+  set(RunCMake_TEST_FAILED "CMakeInputs.cmake not referenced")
+endif()
diff --git a/Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt b/Tests/RunCMake/VS10Project/CMakeInputs.cmake
similarity index 100%
copy from Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt
copy to Tests/RunCMake/VS10Project/CMakeInputs.cmake
diff --git a/Tests/RunCMake/VS10Project/CustomCommandParallel-check.cmake b/Tests/RunCMake/VS10Project/CustomCommandParallel-check.cmake
new file mode 100644
index 0000000..4c8ad00
--- /dev/null
+++ b/Tests/RunCMake/VS10Project/CustomCommandParallel-check.cmake
@@ -0,0 +1,52 @@
+set(vcProjectFile "${RunCMake_TEST_BINARY_DIR}/foo.vcxproj")
+if(NOT EXISTS "${vcProjectFile}")
+  set(RunCMake_TEST_FAILED "Project file ${vcProjectFile} does not exist.")
+  return()
+endif()
+
+set(found_CustomBuild_cmp0147_new 0)
+set(found_CustomBuild_cmp0147_old 0)
+set(found_CustomBuild_CMakeLists 0)
+set(found_BuildInParallel_cmp0147_new 0)
+set(found_BuildInParallel_cmp0147_old 0)
+set(found_BuildInParallel_CMakeLists 0)
+set(in_CustomBuild "")
+file(STRINGS "${vcProjectFile}" lines)
+foreach(line IN LISTS lines)
+  if(line MATCHES [[<CustomBuild Include=".*\\cmp0147-old\.txt\.rule">]])
+    set(found_CustomBuild_cmp0147_old 1)
+    set(in_CustomBuild "cmp0147_old")
+  endif()
+  if(line MATCHES [[<CustomBuild Include=".*\\cmp0147-new\.txt\.rule">]])
+    set(found_CustomBuild_cmp0147_new 1)
+    set(in_CustomBuild "cmp0147_new")
+  endif()
+  if(line MATCHES [[<CustomBuild Include=".*\\CMakeLists\.txt">]])
+    set(found_CustomBuild_CMakeLists 1)
+    set(in_CustomBuild "CMakeLists")
+  endif()
+  if(line MATCHES [[</CustomBuild>]])
+    set(in_CustomBuild "")
+  endif()
+  if(line MATCHES [[<BuildInParallel .*>true</BuildInParallel>]] AND in_CustomBuild)
+    set(found_BuildInParallel_${in_CustomBuild} 1)
+  endif()
+endforeach()
+if(NOT found_CustomBuild_cmp0147_new)
+  string(APPEND RunCMake_TEST_FAILED "CustomBuild for cmp0147-new.txt.rule not found in\n  ${vcProjectFile}\n")
+endif()
+if(NOT found_CustomBuild_cmp0147_old)
+  string(APPEND RunCMake_TEST_FAILED "CustomBuild for cmp0147-old.txt.rule not found in\n  ${vcProjectFile}\n")
+endif()
+if(NOT found_CustomBuild_CMakeLists)
+  string(APPEND RunCMake_TEST_FAILED "CustomBuild for CMakeLists.txt not found in\n  ${vcProjectFile}\n")
+endif()
+if(NOT found_BuildInParallel_cmp0147_new)
+  string(APPEND RunCMake_TEST_FAILED "BuildInParallel for cmp0147-new.txt.rule not found in\n  ${vcProjectFile}\n")
+endif()
+if(found_BuildInParallel_cmp0147_old)
+  string(APPEND RunCMake_TEST_FAILED "BuildInParallel for cmp0147-old.txt.rule incorrectly found in\n  ${vcProjectFile}\n")
+endif()
+if(found_BuildInParallel_CMakeLists)
+  string(APPEND RunCMake_TEST_FAILED "BuildInParallel for CMakeLists.txt incorrectly found in\n  ${vcProjectFile}\n")
+endif()
diff --git a/Tests/RunCMake/VS10Project/CustomCommandParallel.cmake b/Tests/RunCMake/VS10Project/CustomCommandParallel.cmake
new file mode 100644
index 0000000..784fc68
--- /dev/null
+++ b/Tests/RunCMake/VS10Project/CustomCommandParallel.cmake
@@ -0,0 +1,5 @@
+cmake_policy(VERSION 3.26) # CMP0147 left unset
+add_custom_command(OUTPUT "cmp0147-old.txt" COMMAND echo)
+cmake_policy(SET CMP0147 NEW)
+add_custom_command(OUTPUT "cmp0147-new.txt" COMMAND echo)
+add_custom_target(foo DEPENDS "cmp0147-old.txt" "cmp0147-new.txt")
diff --git a/Tests/RunCMake/VS10Project/RunCMakeTest.cmake b/Tests/RunCMake/VS10Project/RunCMakeTest.cmake
index ed74896..cb1a5d5 100644
--- a/Tests/RunCMake/VS10Project/RunCMakeTest.cmake
+++ b/Tests/RunCMake/VS10Project/RunCMakeTest.cmake
@@ -7,7 +7,11 @@
   run_cmake(LanguageStandard)
 endif()
 
+run_cmake(CMakeInputs)
 run_cmake(CustomCommandGenex)
+if(NOT RunCMake_GENERATOR MATCHES "^Visual Studio 1[1-5] ")
+  run_cmake(CustomCommandParallel)
+endif()
 run_cmake(VsCsharpSourceGroup)
 run_cmake(VsCSharpCompilerOpts)
 run_cmake(ExplicitCMakeLists)
diff --git a/Tests/RunCMake/VSSolution/CMakeLists.txt b/Tests/RunCMake/VSSolution/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/VSSolution/CMakeLists.txt
+++ b/Tests/RunCMake/VSSolution/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/VisibilityPreset/RunCMakeTest.cmake b/Tests/RunCMake/VisibilityPreset/RunCMakeTest.cmake
index 7a000ee..133dbe1 100644
--- a/Tests/RunCMake/VisibilityPreset/RunCMakeTest.cmake
+++ b/Tests/RunCMake/VisibilityPreset/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 run_cmake(PropertyTypo)
 run_cmake(CMP0063-OLD)
diff --git a/Tests/RunCMake/VsDotnetSdk/VsDotnetSdk.cmake b/Tests/RunCMake/VsDotnetSdk/VsDotnetSdk.cmake
index bd914f8..623ef2a 100644
--- a/Tests/RunCMake/VsDotnetSdk/VsDotnetSdk.cmake
+++ b/Tests/RunCMake/VsDotnetSdk/VsDotnetSdk.cmake
@@ -1,7 +1,5 @@
-cmake_minimum_required(VERSION 3.22)
-
 # a simple CSharp only test case
-project (DotNetSdk CSharp)
+enable_language(CSharp)
 
 set(CMAKE_DOTNET_TARGET_FRAMEWORK net472)
 set(CMAKE_DOTNET_SDK "Microsoft.NET.Sdk")
diff --git a/Tests/RunCMake/WriteCompilerDetectionHeader/CMakeLists.txt b/Tests/RunCMake/WriteCompilerDetectionHeader/CMakeLists.txt
index 872338d..1d0bd70 100644
--- a/Tests/RunCMake/WriteCompilerDetectionHeader/CMakeLists.txt
+++ b/Tests/RunCMake/WriteCompilerDetectionHeader/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} CXX)
 include(${RunCMake_TEST}.cmake NO_POLICY_SCOPE)
diff --git a/Tests/RunCMake/XcodeProject-Device/CMakeLists.txt b/Tests/RunCMake/XcodeProject-Device/CMakeLists.txt
new file mode 100644
index 0000000..93ee9df
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject-Device/CMakeLists.txt
@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.5)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/XcodeProject/DeploymentTarget.c b/Tests/RunCMake/XcodeProject-Device/DeploymentTarget.c
similarity index 100%
rename from Tests/RunCMake/XcodeProject/DeploymentTarget.c
rename to Tests/RunCMake/XcodeProject-Device/DeploymentTarget.c
diff --git a/Tests/RunCMake/XcodeProject-Device/DeploymentTarget.cmake b/Tests/RunCMake/XcodeProject-Device/DeploymentTarget.cmake
new file mode 100644
index 0000000..234ceef
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject-Device/DeploymentTarget.cmake
@@ -0,0 +1,27 @@
+enable_language(C)
+
+# using Xcode 7.1 SDK versions for deployment targets
+
+if(CMAKE_SYSTEM_NAME STREQUAL "iOS")
+  set(CMAKE_OSX_DEPLOYMENT_TARGET "9.1")
+  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO")
+  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
+  set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "NO")
+elseif(CMAKE_SYSTEM_NAME STREQUAL "watchOS")
+  set(CMAKE_OSX_DEPLOYMENT_TARGET "2.0")
+  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO")
+  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
+  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "")
+  set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "YES")
+elseif(CMAKE_SYSTEM_NAME STREQUAL "tvOS")
+  set(CMAKE_OSX_DEPLOYMENT_TARGET "9.0")
+  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO")
+  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
+  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "")
+  set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "YES")
+else()
+  set(CMAKE_OSX_DEPLOYMENT_TARGET "10.11")
+endif()
+
+add_library(myFramework STATIC DeploymentTarget.c)
+set_target_properties(myFramework PROPERTIES FRAMEWORK TRUE)
diff --git a/Tests/RunCMake/XcodeProject/EffectivePlatformNameOFF.cmake b/Tests/RunCMake/XcodeProject-Device/EffectivePlatformNameOFF.cmake
similarity index 100%
rename from Tests/RunCMake/XcodeProject/EffectivePlatformNameOFF.cmake
rename to Tests/RunCMake/XcodeProject-Device/EffectivePlatformNameOFF.cmake
diff --git a/Tests/RunCMake/XcodeProject-Device/RunCMakeTest.cmake b/Tests/RunCMake/XcodeProject-Device/RunCMakeTest.cmake
new file mode 100644
index 0000000..e2ed045
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject-Device/RunCMakeTest.cmake
@@ -0,0 +1,303 @@
+include(RunCMake)
+
+# Isolate device tests from host architecture selection.
+unset(ENV{CMAKE_OSX_ARCHITECTURES})
+
+if(NOT XCODE_VERSION VERSION_LESS 5)
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeInstallIOS-build)
+  set(RunCMake_TEST_NO_CLEAN 1)
+  set(RunCMake_TEST_OPTIONS
+    "-DCMAKE_SYSTEM_NAME=iOS"
+    "-DCMAKE_INSTALL_PREFIX:PATH=${RunCMake_BINARY_DIR}/ios_install")
+
+  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
+  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
+
+  run_cmake(XcodeInstallIOS)
+  run_cmake_command(XcodeInstallIOS-install ${CMAKE_COMMAND} --build . --target install)
+
+  unset(RunCMake_TEST_BINARY_DIR)
+  unset(RunCMake_TEST_NO_CLEAN)
+  unset(RunCMake_TEST_OPTIONS)
+
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeBundlesOSX-build)
+  set(RunCMake_TEST_NO_CLEAN 1)
+  set(RunCMake_TEST_OPTIONS
+    "-DCMAKE_SYSTEM_NAME=Darwin"
+    "-DCMAKE_INSTALL_PREFIX:PATH=${RunCMake_TEST_BINARY_DIR}/_install")
+
+  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
+  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
+
+  run_cmake(XcodeBundles)
+  run_cmake_command(XcodeBundles-build-macOS ${CMAKE_COMMAND} --build .)
+  run_cmake_command(XcodeBundles-install-macOS ${CMAKE_COMMAND} --build . --target install)
+
+  unset(RunCMake_TEST_BINARY_DIR)
+  unset(RunCMake_TEST_NO_CLEAN)
+  unset(RunCMake_TEST_OPTIONS)
+
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeBundlesIOS-build)
+  set(RunCMake_TEST_NO_CLEAN 1)
+  set(RunCMake_TEST_OPTIONS
+    "-DCMAKE_SYSTEM_NAME=iOS"
+    "-DCMAKE_INSTALL_PREFIX:PATH=${RunCMake_TEST_BINARY_DIR}/_install")
+
+  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
+  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
+
+  run_cmake(XcodeBundles)
+  run_cmake_command(XcodeBundles-build-iOS ${CMAKE_COMMAND} --build .)
+  run_cmake_command(XcodeBundles-install-iOS ${CMAKE_COMMAND} --build . --target install)
+
+  unset(RunCMake_TEST_BINARY_DIR)
+  unset(RunCMake_TEST_NO_CLEAN)
+  unset(RunCMake_TEST_OPTIONS)
+endif()
+
+if(NOT XCODE_VERSION VERSION_LESS 7)
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeBundlesWatchOS-build)
+  set(RunCMake_TEST_NO_CLEAN 1)
+  set(RunCMake_TEST_OPTIONS
+    "-DCMAKE_SYSTEM_NAME=watchOS"
+    "-DCMAKE_INSTALL_PREFIX:PATH=${RunCMake_TEST_BINARY_DIR}/_install")
+
+  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
+  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
+
+  run_cmake(XcodeBundles)
+  run_cmake_command(XcodeBundles-build-watchOS ${CMAKE_COMMAND} --build .)
+  run_cmake_command(XcodeBundles-install-watchOS ${CMAKE_COMMAND} --build . --target install)
+
+  unset(RunCMake_TEST_BINARY_DIR)
+  unset(RunCMake_TEST_NO_CLEAN)
+  unset(RunCMake_TEST_OPTIONS)
+endif()
+
+if(NOT XCODE_VERSION VERSION_LESS 7.1)
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeBundlesTvOS-build)
+  set(RunCMake_TEST_NO_CLEAN 1)
+  set(RunCMake_TEST_OPTIONS
+    "-DCMAKE_SYSTEM_NAME=tvOS"
+    "-DCMAKE_INSTALL_PREFIX:PATH=${RunCMake_TEST_BINARY_DIR}/_install")
+
+  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
+  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
+
+  run_cmake(XcodeBundles)
+  run_cmake_command(XcodeBundles-build-tvOS ${CMAKE_COMMAND} --build .)
+  run_cmake_command(XcodeBundles-install-tvOS ${CMAKE_COMMAND} --build . --target install)
+
+  unset(RunCMake_TEST_BINARY_DIR)
+  unset(RunCMake_TEST_NO_CLEAN)
+  unset(RunCMake_TEST_OPTIONS)
+endif()
+
+if(NOT XCODE_VERSION VERSION_LESS 7)
+  set(RunCMake_TEST_OPTIONS "-DCMAKE_TOOLCHAIN_FILE=${RunCMake_SOURCE_DIR}/osx.cmake")
+  run_cmake(XcodeTbdStub)
+  unset(RunCMake_TEST_OPTIONS)
+endif()
+
+if(XCODE_VERSION VERSION_GREATER_EQUAL 6)
+  # XcodeIOSInstallCombined
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeIOSInstallCombined-build)
+  set(RunCMake_TEST_NO_CLEAN 1)
+  set(RunCMake_TEST_OPTIONS
+    "-DCMAKE_SYSTEM_NAME=iOS"
+    "-DCMAKE_IOS_INSTALL_COMBINED=YES"
+    "-DCMAKE_INSTALL_PREFIX:PATH=${RunCMake_TEST_BINARY_DIR}/_install")
+
+  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
+  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
+
+  run_cmake(XcodeIOSInstallCombined)
+  run_cmake_command(XcodeIOSInstallCombined-build ${CMAKE_COMMAND} --build .)
+  if(XCODE_VERSION VERSION_LESS 12)
+    run_cmake_command(XcodeIOSInstallCombined-install ${CMAKE_COMMAND} --build . --target install)
+  endif()
+  # --build defaults to Debug, --install defaults to Release, so we have to
+  # specify the configuration explicitly
+  run_cmake_command(XcodeIOSInstallCombined-cmakeinstall
+    ${CMAKE_COMMAND} --install . --config Debug
+  )
+
+  unset(RunCMake_TEST_BINARY_DIR)
+  unset(RunCMake_TEST_NO_CLEAN)
+  unset(RunCMake_TEST_OPTIONS)
+
+  # XcodeIOSInstallCombinedPrune
+  # FIXME(#24011): Xcode 14 removed support for older architectures the test needs.
+  if(XCODE_VERSION VERSION_LESS 14)
+    set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeIOSInstallCombinedPrune-build)
+    set(RunCMake_TEST_NO_CLEAN 1)
+    set(RunCMake_TEST_OPTIONS
+      "-DCMAKE_SYSTEM_NAME=iOS"
+      "-DCMAKE_IOS_INSTALL_COMBINED=YES"
+      "-DCMAKE_INSTALL_PREFIX:PATH=${RunCMake_TEST_BINARY_DIR}/_install")
+
+    file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
+    file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
+
+    run_cmake(XcodeIOSInstallCombinedPrune)
+    run_cmake_command(XcodeIOSInstallCombinedPrune-build ${CMAKE_COMMAND} --build .)
+    if(XCODE_VERSION VERSION_LESS 12)
+      run_cmake_command(XcodeIOSInstallCombinedPrune-install ${CMAKE_COMMAND} --build . --target install)
+    endif()
+    # --build defaults to Debug, --install defaults to Release, so we have to
+    # specify the configuration explicitly
+    run_cmake_command(XcodeIOSInstallCombinedPrune-cmakeinstall
+      ${CMAKE_COMMAND} --install . --config Debug
+    )
+
+    unset(RunCMake_TEST_BINARY_DIR)
+    unset(RunCMake_TEST_NO_CLEAN)
+    unset(RunCMake_TEST_OPTIONS)
+  endif()
+
+  # XcodeIOSInstallCombinedSingleArch
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeIOSInstallCombinedSingleArch-build)
+  set(RunCMake_TEST_NO_CLEAN 1)
+  set(RunCMake_TEST_OPTIONS
+    "-DCMAKE_SYSTEM_NAME=iOS"
+    "-DCMAKE_IOS_INSTALL_COMBINED=YES"
+    "-DCMAKE_INSTALL_PREFIX:PATH=${RunCMake_TEST_BINARY_DIR}/_install")
+
+  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
+  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
+
+  run_cmake(XcodeIOSInstallCombinedSingleArch)
+  run_cmake_command(XcodeIOSInstallCombinedSingleArch-build ${CMAKE_COMMAND} --build .)
+  if(XCODE_VERSION VERSION_LESS 12)
+    run_cmake_command(XcodeIOSInstallCombinedSingleArch-install ${CMAKE_COMMAND} --build . --target install)
+  endif()
+  # --build defaults to Debug, --install defaults to Release, so we have to
+  # specify the configuration explicitly
+  run_cmake_command(XcodeIOSInstallCombinedSingleArch-cmakeinstall
+    ${CMAKE_COMMAND} --install . --config Debug
+  )
+
+  unset(RunCMake_TEST_BINARY_DIR)
+  unset(RunCMake_TEST_NO_CLEAN)
+  unset(RunCMake_TEST_OPTIONS)
+endif()
+
+if(NOT XCODE_VERSION VERSION_LESS 5)
+  # XcodeMultiplatform
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeMultiplatform-build)
+  set(RunCMake_TEST_NO_CLEAN 1)
+  set(RunCMake_TEST_OPTIONS "${IOS_DEPLOYMENT_TARGET}")
+
+  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
+  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
+
+  run_cmake(XcodeMultiplatform)
+
+  # build ios before macos
+  run_cmake_command(XcodeMultiplatform-iphonesimulator-build ${CMAKE_COMMAND} --build . -- -sdk iphonesimulator)
+  run_cmake_command(XcodeMultiplatform-iphonesimulator-install ${CMAKE_COMMAND} --build . --target install -- -sdk iphonesimulator DESTDIR=${RunCMake_TEST_BINARY_DIR}/_install_iphonesimulator)
+
+  run_cmake_command(XcodeMultiplatform-macosx-build ${CMAKE_COMMAND} --build . -- -sdk macosx)
+  run_cmake_command(XcodeMultiplatform-macosx-install ${CMAKE_COMMAND} --build . --target install -- -sdk macosx DESTDIR=${RunCMake_TEST_BINARY_DIR}/_install_macosx)
+
+  unset(RunCMake_TEST_BINARY_DIR)
+  unset(RunCMake_TEST_NO_CLEAN)
+  unset(RunCMake_TEST_OPTIONS)
+
+  # EffectivePlatformNameOFF
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/EffectivePlatformNameOFF-build)
+  set(RunCMake_TEST_NO_CLEAN 1)
+  set(RunCMake_TEST_OPTIONS "-DCMAKE_SYSTEM_NAME=iOS" "-DCMAKE_OSX_SYSROOT=iphonesimulator")
+
+  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
+  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
+
+  run_cmake(EffectivePlatformNameOFF)
+
+  run_cmake_command(EffectivePlatformNameOFF-iphonesimulator-build ${CMAKE_COMMAND} --build .)
+  run_cmake_command(EffectivePlatformNameOFF-iphonesimulator-install ${CMAKE_COMMAND} --build . --target install -- DESTDIR=${RunCMake_TEST_BINARY_DIR}/_install_iphonesimulator)
+
+  unset(RunCMake_TEST_BINARY_DIR)
+  unset(RunCMake_TEST_NO_CLEAN)
+  unset(RunCMake_TEST_OPTIONS)
+endif()
+
+if(XCODE_VERSION VERSION_GREATER_EQUAL 8)
+  function(deployment_target_test SystemName SDK)
+    set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/DeploymentTarget-${SDK}-build)
+    set(RunCMake_TEST_NO_CLEAN 1)
+    set(RunCMake_TEST_OPTIONS "-DCMAKE_SYSTEM_NAME=${SystemName}" "-DCMAKE_OSX_SYSROOT=${SDK}")
+
+    file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
+    file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
+
+    run_cmake(DeploymentTarget)
+    run_cmake_command(DeploymentTarget-${SDK} ${CMAKE_COMMAND} --build .)
+  endfunction()
+
+  deployment_target_test(Darwin macosx)
+  deployment_target_test(iOS iphoneos)
+  deployment_target_test(iOS iphonesimulator)
+  deployment_target_test(tvOS appletvos)
+  deployment_target_test(tvOS appletvsimulator)
+  deployment_target_test(watchOS watchos)
+  deployment_target_test(watchOS watchsimulator)
+endif()
+
+if(XCODE_VERSION VERSION_GREATER_EQUAL 8)
+  function(xctest_lookup_test SystemName SDK)
+    set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XCTestLookup-${SDK}-build)
+    set(RunCMake_TEST_OPTIONS "-DCMAKE_SYSTEM_NAME=${SystemName}" "-DCMAKE_OSX_SYSROOT=${SDK}")
+
+    run_cmake(XCTestLookup)
+  endfunction()
+
+  xctest_lookup_test(Darwin macosx)
+  xctest_lookup_test(iOS iphoneos)
+  xctest_lookup_test(iOS iphonesimulator)
+  xctest_lookup_test(tvOS appletvos)
+  xctest_lookup_test(tvOS appletvsimulator)
+endif()
+
+if(XCODE_VERSION VERSION_GREATER_EQUAL 8)
+  function(XcodeRemoveExcessiveISystemSDK SDK)
+    set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeRemoveExcessiveISystemSDK-${SDK}-build)
+    set(RunCMake_TEST_OPTIONS "-DCMAKE_SYSTEM_NAME=iOS" "-DCMAKE_OSX_SYSROOT=${SDK}")
+    run_cmake(XcodeRemoveExcessiveISystem)
+    set(RunCMake_TEST_NO_CLEAN 1)
+    run_cmake_command(XcodeRemoveExcessiveISystemSDK-${SDK}-build ${CMAKE_COMMAND} --build .)
+  endfunction()
+
+  XcodeRemoveExcessiveISystemSDK(iphoneos)
+  XcodeRemoveExcessiveISystemSDK(iphonesimulator)
+endif()
+
+if (XCODE_VERSION VERSION_GREATER_EQUAL 7.3)
+  function(xctest_add_bundle_test SystemName SDK BuildSystemVersion ExpectedOutputDir)
+    set(RunCMake_TEST_BINARY_DIR
+      ${RunCMake_BINARY_DIR}/DeploymentTarget-${SystemName}-${SDK}-${BuildSystemVersion}-build)
+    set(RunCMake_TEST_OPTIONS
+      "-DCMAKE_SYSTEM_NAME=${SystemName}"
+      "-DCMAKE_OSX_SYSROOT=${SDK}"
+      "-DTEST_EXPECTED_OUTPUT_DIR=${ExpectedOutputDir}")
+    unset(RunCMake_GENERATOR_TOOLSET)
+    if(BuildSystemVersion)
+      set(RunCMake_GENERATOR_TOOLSET "buildsystem=${BuildSystemVersion}")
+    endif()
+    run_cmake(XCTestAddBundle)
+  endfunction()
+
+  if(XCODE_VERSION VERSION_GREATER_EQUAL 12)
+    xctest_add_bundle_test(Darwin macosx "1" "$<TARGET_BUNDLE_CONTENT_DIR:TestedApp>/PlugIns")
+    xctest_add_bundle_test(Darwin macosx "12" "$<TARGET_BUNDLE_CONTENT_DIR:TestedApp>/PlugIns")
+    xctest_add_bundle_test(iOS iphonesimulator "1" "$<TARGET_BUNDLE_CONTENT_DIR:TestedApp>/PlugIns")
+    if (XCODE_VERSION VERSION_LESS 12.5)
+      xctest_add_bundle_test(iOS iphonesimulator "12" "$<TARGET_BUNDLE_CONTENT_DIR:TestedApp>")
+    else()
+      xctest_add_bundle_test(iOS iphonesimulator "12" "$<TARGET_BUNDLE_CONTENT_DIR:TestedApp>/PlugIns")
+    endif()
+  else()
+    xctest_add_bundle_test(Darwin macosx "" "$<TARGET_BUNDLE_CONTENT_DIR:TestedApp>/PlugIns")
+    xctest_add_bundle_test(iOS iphonesimulator "" "$<TARGET_BUNDLE_CONTENT_DIR:TestedApp>/PlugIns")
+  endif()
+endif()
diff --git a/Tests/RunCMake/XcodeProject/XCTestAddBundle.cmake b/Tests/RunCMake/XcodeProject-Device/XCTestAddBundle.cmake
similarity index 100%
rename from Tests/RunCMake/XcodeProject/XCTestAddBundle.cmake
rename to Tests/RunCMake/XcodeProject-Device/XCTestAddBundle.cmake
diff --git a/Tests/RunCMake/XcodeProject/XCTestLookup.cmake b/Tests/RunCMake/XcodeProject-Device/XCTestLookup.cmake
similarity index 100%
rename from Tests/RunCMake/XcodeProject/XCTestLookup.cmake
rename to Tests/RunCMake/XcodeProject-Device/XCTestLookup.cmake
diff --git a/Tests/RunCMake/XcodeProject/XcodeBundles-install-check.cmake b/Tests/RunCMake/XcodeProject-Device/XcodeBundles-install-check.cmake
similarity index 100%
rename from Tests/RunCMake/XcodeProject/XcodeBundles-install-check.cmake
rename to Tests/RunCMake/XcodeProject-Device/XcodeBundles-install-check.cmake
diff --git a/Tests/RunCMake/XcodeProject-Device/XcodeBundles.cmake b/Tests/RunCMake/XcodeProject-Device/XcodeBundles.cmake
new file mode 100644
index 0000000..a9fafd2
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject-Device/XcodeBundles.cmake
@@ -0,0 +1,143 @@
+# check if Xcode and CMake have the same understanding of Bundle layout
+
+enable_language(C)
+
+if(CMAKE_SYSTEM_NAME STREQUAL "iOS")
+  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO")
+  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
+  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "")
+  set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "NO")
+endif()
+
+if(CMAKE_SYSTEM_NAME STREQUAL "tvOS" OR CMAKE_SYSTEM_NAME STREQUAL "watchOS")
+  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO")
+  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
+  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "")
+  set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "YES")
+endif()
+
+# App Bundle
+
+add_executable(AppBundle MACOSX_BUNDLE main.m)
+
+add_custom_target(AppBundleTest ALL
+  COMMAND ${CMAKE_COMMAND} -E copy
+    "$<TARGET_FILE:AppBundle>" "$<TARGET_FILE:AppBundle>.old")
+
+add_dependencies(AppBundleTest AppBundle)
+
+# with custom extension
+
+if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+  add_executable(AppBundleExt MACOSX_BUNDLE main.m)
+  set_target_properties(AppBundleExt PROPERTIES BUNDLE_EXTENSION "foo")
+  install(TARGETS AppBundleExt BUNDLE DESTINATION FooExtension)
+
+  add_custom_target(AppBundleExtTest ALL
+    COMMAND ${CMAKE_COMMAND} -E copy
+      "$<TARGET_FILE:AppBundleExt>" "$<TARGET_FILE:AppBundleExt>.old")
+
+  add_dependencies(AppBundleExtTest AppBundleExt)
+endif()
+
+# Shared Framework (not supported for iOS on Xcode < 6)
+
+if(NOT CMAKE_SYSTEM_NAME STREQUAL "iOS" OR NOT XCODE_VERSION VERSION_LESS 6)
+  add_library(SharedFramework SHARED main.c)
+  set_target_properties(SharedFramework PROPERTIES FRAMEWORK TRUE)
+
+  add_custom_target(SharedFrameworkTest ALL
+    COMMAND ${CMAKE_COMMAND} -E copy
+      "$<TARGET_BUNDLE_DIR:SharedFramework>" "$<TARGET_BUNDLE_DIR:SharedFramework>.old"
+    COMMAND ${CMAKE_COMMAND} -E copy
+      "$<TARGET_BUNDLE_CONTENT_DIR:SharedFramework>" "$<TARGET_BUNDLE_CONTENT_DIR:SharedFramework>.old"
+    COMMAND ${CMAKE_COMMAND} -E copy
+      "$<TARGET_FILE:SharedFramework>" "$<TARGET_FILE:SharedFramework>.old")
+
+  add_dependencies(SharedFrameworkTest SharedFramework)
+
+  # with custom extension
+
+  add_library(SharedFrameworkExt SHARED main.c)
+  set_target_properties(SharedFrameworkExt PROPERTIES FRAMEWORK TRUE)
+  set_target_properties(SharedFrameworkExt PROPERTIES BUNDLE_EXTENSION "foo")
+  install(TARGETS SharedFrameworkExt FRAMEWORK DESTINATION FooExtension)
+
+  add_custom_target(SharedFrameworkExtTest ALL
+    COMMAND ${CMAKE_COMMAND} -E copy
+      "$<TARGET_BUNDLE_DIR:SharedFrameworkExt>" "$<TARGET_BUNDLE_DIR:SharedFrameworkExt>.old"
+    COMMAND ${CMAKE_COMMAND} -E copy
+      "$<TARGET_BUNDLE_CONTENT_DIR:SharedFrameworkExt>" "$<TARGET_BUNDLE_CONTENT_DIR:SharedFrameworkExt>.old"
+    COMMAND ${CMAKE_COMMAND} -E copy
+      "$<TARGET_FILE:SharedFrameworkExt>" "$<TARGET_FILE:SharedFrameworkExt>.old")
+
+  add_dependencies(SharedFrameworkExtTest SharedFrameworkExt)
+endif()
+
+# Static Framework (not supported for Xcode < 6)
+
+if(NOT XCODE_VERSION VERSION_LESS 6)
+  add_library(StaticFramework STATIC main.c)
+  set_target_properties(StaticFramework PROPERTIES FRAMEWORK TRUE)
+
+  add_custom_target(StaticFrameworkTest ALL
+    COMMAND ${CMAKE_COMMAND} -E copy
+      "$<TARGET_BUNDLE_DIR:StaticFramework>" "$<TARGET_BUNDLE_DIR:StaticFramework>.old"
+    COMMAND ${CMAKE_COMMAND} -E copy
+      "$<TARGET_BUNDLE_CONTENT_DIR:StaticFramework>" "$<TARGET_BUNDLE_CONTENT_DIR:StaticFramework>.old"
+    COMMAND ${CMAKE_COMMAND} -E copy
+      "$<TARGET_FILE:StaticFramework>" "$<TARGET_FILE:StaticFramework>.old")
+
+  add_dependencies(StaticFrameworkTest StaticFramework)
+
+  # with custom extension
+
+  add_library(StaticFrameworkExt STATIC main.c)
+  set_target_properties(StaticFrameworkExt PROPERTIES FRAMEWORK TRUE)
+  set_target_properties(StaticFrameworkExt PROPERTIES BUNDLE_EXTENSION "foo")
+  install(TARGETS StaticFrameworkExt FRAMEWORK DESTINATION StaticFooExtension)
+
+  add_custom_target(StaticFrameworkExtTest ALL
+    COMMAND ${CMAKE_COMMAND} -E copy
+      "$<TARGET_BUNDLE_DIR:StaticFrameworkExt>" "$<TARGET_BUNDLE_DIR:StaticFrameworkExt>.old"
+    COMMAND ${CMAKE_COMMAND} -E copy
+      "$<TARGET_BUNDLE_CONTENT_DIR:StaticFrameworkExt>" "$<TARGET_BUNDLE_CONTENT_DIR:StaticFrameworkExt>.old"
+    COMMAND ${CMAKE_COMMAND} -E copy
+      "$<TARGET_FILE:StaticFrameworkExt>" "$<TARGET_FILE:StaticFrameworkExt>.old")
+
+  add_dependencies(StaticFrameworkExtTest StaticFrameworkExt)
+endif()
+
+# Bundle
+
+if(NOT CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE)
+  add_library(Bundle MODULE main.c)
+  set_target_properties(Bundle PROPERTIES BUNDLE TRUE)
+
+  add_custom_target(BundleTest ALL
+    COMMAND ${CMAKE_COMMAND} -E copy
+      "$<TARGET_BUNDLE_DIR:Bundle>" "$<TARGET_BUNDLE_DIR:Bundle>.old"
+    COMMAND ${CMAKE_COMMAND} -E copy
+      "$<TARGET_BUNDLE_CONTENT_DIR:Bundle>" "$<TARGET_BUNDLE_CONTENT_DIR:Bundle>.old"
+    COMMAND ${CMAKE_COMMAND} -E copy
+      "$<TARGET_FILE:Bundle>" "$<TARGET_FILE:Bundle>.old")
+
+  add_dependencies(BundleTest Bundle)
+
+  # with custom extension
+
+  add_library(BundleExt MODULE main.c)
+  set_target_properties(BundleExt PROPERTIES BUNDLE TRUE)
+  set_target_properties(BundleExt PROPERTIES BUNDLE_EXTENSION "foo")
+  install(TARGETS BundleExt LIBRARY DESTINATION FooExtension)
+
+  add_custom_target(BundleExtTest ALL
+    COMMAND ${CMAKE_COMMAND} -E copy
+      "$<TARGET_BUNDLE_DIR:BundleExt>" "$<TARGET_BUNDLE_DIR:BundleExt>.old"
+    COMMAND ${CMAKE_COMMAND} -E copy
+      "$<TARGET_BUNDLE_CONTENT_DIR:BundleExt>" "$<TARGET_BUNDLE_CONTENT_DIR:BundleExt>.old"
+    COMMAND ${CMAKE_COMMAND} -E copy
+      "$<TARGET_FILE:BundleExt>" "$<TARGET_FILE:BundleExt>.old")
+
+  add_dependencies(BundleExtTest BundleExt)
+endif()
diff --git a/Tests/RunCMake/XcodeProject/XcodeIOSInstallCombined-cmakeinstall-check.cmake b/Tests/RunCMake/XcodeProject-Device/XcodeIOSInstallCombined-cmakeinstall-check.cmake
similarity index 100%
rename from Tests/RunCMake/XcodeProject/XcodeIOSInstallCombined-cmakeinstall-check.cmake
rename to Tests/RunCMake/XcodeProject-Device/XcodeIOSInstallCombined-cmakeinstall-check.cmake
diff --git a/Tests/RunCMake/XcodeProject/XcodeIOSInstallCombined-install-check.cmake b/Tests/RunCMake/XcodeProject-Device/XcodeIOSInstallCombined-install-check.cmake
similarity index 100%
rename from Tests/RunCMake/XcodeProject/XcodeIOSInstallCombined-install-check.cmake
rename to Tests/RunCMake/XcodeProject-Device/XcodeIOSInstallCombined-install-check.cmake
diff --git a/Tests/RunCMake/XcodeProject-Device/XcodeIOSInstallCombined.cmake b/Tests/RunCMake/XcodeProject-Device/XcodeIOSInstallCombined.cmake
new file mode 100644
index 0000000..f8eccc7
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject-Device/XcodeIOSInstallCombined.cmake
@@ -0,0 +1,40 @@
+enable_language(CXX)
+
+set(maybe_armv7 armv7)
+set(maybe_i386 i386)
+if(XCODE_VERSION VERSION_GREATER_EQUAL 14)
+  set(CMAKE_OSX_DEPLOYMENT_TARGET 16)
+  set(maybe_armv7 "")
+  set(maybe_i386 "")
+elseif(XCODE_VERSION VERSION_GREATER_EQUAL 9)
+  set(CMAKE_OSX_DEPLOYMENT_TARGET 10)
+endif()
+
+if(NOT IOS)
+  message(FATAL_ERROR "IOS variable is not set")
+endif()
+
+set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO")
+set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
+set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "")
+set(CMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT "dwarf")
+set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "NO")
+
+set(CMAKE_OSX_ARCHITECTURES ${maybe_armv7} arm64 ${maybe_i386} x86_64)
+
+add_executable(foo_app MACOSX_BUNDLE main.cpp)
+install(TARGETS foo_app BUNDLE DESTINATION bin)
+
+add_library(foo_static STATIC foo.cpp)
+install(TARGETS foo_static ARCHIVE DESTINATION lib)
+
+add_library(foo_shared SHARED foo.cpp)
+install(TARGETS foo_shared LIBRARY DESTINATION lib)
+
+add_library(foo_bundle MODULE foo.cpp)
+set_target_properties(foo_bundle PROPERTIES BUNDLE TRUE)
+install(TARGETS foo_bundle LIBRARY DESTINATION lib)
+
+add_library(foo_framework SHARED foo.cpp)
+set_target_properties(foo_framework PROPERTIES FRAMEWORK TRUE)
+install(TARGETS foo_framework FRAMEWORK DESTINATION lib)
diff --git a/Tests/RunCMake/XcodeProject/XcodeIOSInstallCombinedPrune-install-check.cmake b/Tests/RunCMake/XcodeProject-Device/XcodeIOSInstallCombinedPrune-install-check.cmake
similarity index 100%
rename from Tests/RunCMake/XcodeProject/XcodeIOSInstallCombinedPrune-install-check.cmake
rename to Tests/RunCMake/XcodeProject-Device/XcodeIOSInstallCombinedPrune-install-check.cmake
diff --git a/Tests/RunCMake/XcodeProject-Device/XcodeIOSInstallCombinedPrune-install-stdout.txt b/Tests/RunCMake/XcodeProject-Device/XcodeIOSInstallCombinedPrune-install-stdout.txt
new file mode 100644
index 0000000..c87008a
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject-Device/XcodeIOSInstallCombinedPrune-install-stdout.txt
@@ -0,0 +1,2 @@
+.*Unexpected architecture `i386` detected.*
+.*Unexpected architecture `arm64` detected.*
diff --git a/Tests/RunCMake/XcodeProject-Device/XcodeIOSInstallCombinedPrune.cmake b/Tests/RunCMake/XcodeProject-Device/XcodeIOSInstallCombinedPrune.cmake
new file mode 100644
index 0000000..e719428
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject-Device/XcodeIOSInstallCombinedPrune.cmake
@@ -0,0 +1,39 @@
+enable_language(CXX)
+
+if(XCODE_VERSION VERSION_GREATER_EQUAL 9)
+  set(CMAKE_OSX_DEPLOYMENT_TARGET 10)
+endif()
+
+set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO")
+set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
+set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "")
+set(CMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT "dwarf")
+
+add_library(foo SHARED foo.cpp)
+install(TARGETS foo DESTINATION lib)
+
+add_library(baz SHARED foo.cpp)
+set_target_properties(
+  foo baz
+  PROPERTIES
+  XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] armv7
+  XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphoneos*] armv7
+  XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] x86_64
+  XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] x86_64
+)
+
+add_library(boo SHARED foo.cpp)
+set_target_properties(
+  boo
+  PROPERTIES
+  XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] arm64
+  XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphoneos*] arm64
+  XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] i386
+  XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] i386
+)
+
+add_custom_command(
+  TARGET foo
+  POST_BUILD
+  COMMAND lipo -create $<TARGET_FILE:baz> $<TARGET_FILE:boo> -output $<TARGET_FILE:foo>
+)
diff --git a/Tests/RunCMake/XcodeProject/XcodeIOSInstallCombinedSingleArch-cmakeinstall-check.cmake b/Tests/RunCMake/XcodeProject-Device/XcodeIOSInstallCombinedSingleArch-cmakeinstall-check.cmake
similarity index 100%
rename from Tests/RunCMake/XcodeProject/XcodeIOSInstallCombinedSingleArch-cmakeinstall-check.cmake
rename to Tests/RunCMake/XcodeProject-Device/XcodeIOSInstallCombinedSingleArch-cmakeinstall-check.cmake
diff --git a/Tests/RunCMake/XcodeProject/XcodeIOSInstallCombinedSingleArch-install-check.cmake b/Tests/RunCMake/XcodeProject-Device/XcodeIOSInstallCombinedSingleArch-install-check.cmake
similarity index 100%
rename from Tests/RunCMake/XcodeProject/XcodeIOSInstallCombinedSingleArch-install-check.cmake
rename to Tests/RunCMake/XcodeProject-Device/XcodeIOSInstallCombinedSingleArch-install-check.cmake
diff --git a/Tests/RunCMake/XcodeProject-Device/XcodeIOSInstallCombinedSingleArch.cmake b/Tests/RunCMake/XcodeProject-Device/XcodeIOSInstallCombinedSingleArch.cmake
new file mode 100644
index 0000000..cb22e51
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject-Device/XcodeIOSInstallCombinedSingleArch.cmake
@@ -0,0 +1,26 @@
+enable_language(CXX)
+
+set(iphoneos_arch armv7)
+if(XCODE_VERSION VERSION_GREATER_EQUAL 14)
+  set(CMAKE_OSX_DEPLOYMENT_TARGET 16)
+  set(iphoneos_arch arm64)
+elseif(XCODE_VERSION VERSION_GREATER_EQUAL 9)
+  set(CMAKE_OSX_DEPLOYMENT_TARGET 10)
+endif()
+
+set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO")
+set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
+set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "")
+set(CMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT "dwarf")
+
+add_library(foo SHARED foo.cpp)
+install(TARGETS foo DESTINATION lib)
+
+set_target_properties(
+  foo
+  PROPERTIES
+  XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] ${iphoneos_arch}
+  XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphoneos*] ${iphoneos_arch}
+  XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] ""
+  XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] ""
+)
diff --git a/Tests/RunCMake/XcodeProject/XcodeInstallIOS-install-stdout.txt b/Tests/RunCMake/XcodeProject-Device/XcodeInstallIOS-install-stdout.txt
similarity index 100%
rename from Tests/RunCMake/XcodeProject/XcodeInstallIOS-install-stdout.txt
rename to Tests/RunCMake/XcodeProject-Device/XcodeInstallIOS-install-stdout.txt
diff --git a/Tests/RunCMake/XcodeProject-Device/XcodeInstallIOS.cmake b/Tests/RunCMake/XcodeProject-Device/XcodeInstallIOS.cmake
new file mode 100644
index 0000000..fccd4c6
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject-Device/XcodeInstallIOS.cmake
@@ -0,0 +1,7 @@
+enable_language(CXX)
+
+set(XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
+set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "NO")
+
+add_library(foo STATIC foo.cpp)
+install(TARGETS foo ARCHIVE DESTINATION lib)
diff --git a/Tests/RunCMake/XcodeProject-Device/XcodeMultiplatform.cmake b/Tests/RunCMake/XcodeProject-Device/XcodeMultiplatform.cmake
new file mode 100644
index 0000000..b334b7d
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject-Device/XcodeMultiplatform.cmake
@@ -0,0 +1,13 @@
+enable_language(CXX)
+
+set_property(GLOBAL PROPERTY XCODE_EMIT_EFFECTIVE_PLATFORM_NAME ON)
+
+set(CMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS "macosx iphonesimulator")
+set(CMAKE_MACOSX_BUNDLE true)
+
+add_library(library STATIC foo.cpp)
+
+add_executable(main main.cpp)
+target_link_libraries(main library)
+
+install(TARGETS library ARCHIVE DESTINATION lib)
diff --git a/Tests/RunCMake/XcodeProject-Device/XcodeRemoveExcessiveISystem.cmake b/Tests/RunCMake/XcodeProject-Device/XcodeRemoveExcessiveISystem.cmake
new file mode 100644
index 0000000..b28a58e
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject-Device/XcodeRemoveExcessiveISystem.cmake
@@ -0,0 +1 @@
+include(${CMAKE_CURRENT_LIST_DIR}/../XcodeProject/XcodeRemoveExcessiveISystem.cmake)
diff --git a/Tests/RunCMake/XcodeProject/XcodeTbdStub-stdout.txt b/Tests/RunCMake/XcodeProject-Device/XcodeTbdStub-stdout.txt
similarity index 100%
rename from Tests/RunCMake/XcodeProject/XcodeTbdStub-stdout.txt
rename to Tests/RunCMake/XcodeProject-Device/XcodeTbdStub-stdout.txt
diff --git a/Tests/RunCMake/XcodeProject-Device/XcodeTbdStub.cmake b/Tests/RunCMake/XcodeProject-Device/XcodeTbdStub.cmake
new file mode 100644
index 0000000..55dd1a7
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject-Device/XcodeTbdStub.cmake
@@ -0,0 +1 @@
+find_package(ZLIB REQUIRED)
diff --git a/Tests/RunCMake/XcodeProject-Device/dummy_main.swift b/Tests/RunCMake/XcodeProject-Device/dummy_main.swift
new file mode 100644
index 0000000..1aaee42
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject-Device/dummy_main.swift
@@ -0,0 +1,18 @@
+#if os(iOS)
+import UIKit
+
+@UIApplicationMain
+class MyApp: UIResponder, UIApplicationDelegate {
+}
+
+#elseif os(macOS)
+import SwiftUI
+
+@main
+struct MyApp: App {
+    var body: some Scene {
+        WindowGroup {
+        }
+    }
+}
+#endif
diff --git a/Tests/RunCMake/XcodeProject-Device/foo.cpp b/Tests/RunCMake/XcodeProject-Device/foo.cpp
new file mode 100644
index 0000000..3695dc9
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject-Device/foo.cpp
@@ -0,0 +1,3 @@
+void foo()
+{
+}
diff --git a/Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt b/Tests/RunCMake/XcodeProject-Device/main.c
similarity index 100%
copy from Tests/RunCMake/CMP0004/CMP0004-WARN-stderr.txt
copy to Tests/RunCMake/XcodeProject-Device/main.c
diff --git a/Tests/RunCMake/XcodeProject-Device/main.cpp b/Tests/RunCMake/XcodeProject-Device/main.cpp
new file mode 100644
index 0000000..f8b643a
--- /dev/null
+++ b/Tests/RunCMake/XcodeProject-Device/main.cpp
@@ -0,0 +1,4 @@
+int main()
+{
+  return 0;
+}
diff --git a/Tests/RunCMake/XcodeProject/main.m b/Tests/RunCMake/XcodeProject-Device/main.m
similarity index 100%
rename from Tests/RunCMake/XcodeProject/main.m
rename to Tests/RunCMake/XcodeProject-Device/main.m
diff --git a/Tests/RunCMake/XcodeProject/osx.cmake b/Tests/RunCMake/XcodeProject-Device/osx.cmake
similarity index 100%
rename from Tests/RunCMake/XcodeProject/osx.cmake
rename to Tests/RunCMake/XcodeProject-Device/osx.cmake
diff --git a/Tests/RunCMake/XcodeProject/BundleLinkBundle.cmake b/Tests/RunCMake/XcodeProject/BundleLinkBundle.cmake
index 1f3c19d..68372a1 100644
--- a/Tests/RunCMake/XcodeProject/BundleLinkBundle.cmake
+++ b/Tests/RunCMake/XcodeProject/BundleLinkBundle.cmake
@@ -1,6 +1,4 @@
-cmake_minimum_required(VERSION 3.23)
-
-project(BundleLinkBundle CXX)
+enable_language(CXX)
 
 add_subdirectory(lib_bundle)
 
diff --git a/Tests/RunCMake/XcodeProject/CMakeLists.txt b/Tests/RunCMake/XcodeProject/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/XcodeProject/CMakeLists.txt
+++ b/Tests/RunCMake/XcodeProject/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/XcodeProject/DeploymentTarget.cmake b/Tests/RunCMake/XcodeProject/DeploymentTarget.cmake
deleted file mode 100644
index 3d8fa17..0000000
--- a/Tests/RunCMake/XcodeProject/DeploymentTarget.cmake
+++ /dev/null
@@ -1,28 +0,0 @@
-cmake_minimum_required(VERSION 3.10)
-project(DeploymentTarget C)
-
-# using Xcode 7.1 SDK versions for deployment targets
-
-if(CMAKE_SYSTEM_NAME STREQUAL "iOS")
-  set(CMAKE_OSX_DEPLOYMENT_TARGET "9.1")
-  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO")
-  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
-  set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "NO")
-elseif(CMAKE_SYSTEM_NAME STREQUAL "watchOS")
-  set(CMAKE_OSX_DEPLOYMENT_TARGET "2.0")
-  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO")
-  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
-  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "")
-  set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "YES")
-elseif(CMAKE_SYSTEM_NAME STREQUAL "tvOS")
-  set(CMAKE_OSX_DEPLOYMENT_TARGET "9.0")
-  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO")
-  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
-  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "")
-  set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "YES")
-else()
-  set(CMAKE_OSX_DEPLOYMENT_TARGET "10.11")
-endif()
-
-add_library(myFramework STATIC DeploymentTarget.c)
-set_target_properties(myFramework PROPERTIES FRAMEWORK TRUE)
diff --git a/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase.cmake b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase.cmake
index a9ea717..b42e933 100644
--- a/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase.cmake
+++ b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase.cmake
@@ -45,7 +45,6 @@
 
 file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/ExternalFrameworks/CMakeLists.txt
 [[
-cmake_minimum_required(VERSION 3.18)
 project(ExternalFrameworks)
 add_library(staticFrameworkExt STATIC func6.c)
 add_library(sharedFrameworkExt SHARED func7.c)
diff --git a/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_Funcs.cmake b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_Funcs.cmake
index e72bf4d..ab64db7 100644
--- a/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_Funcs.cmake
+++ b/Tests/RunCMake/XcodeProject/LinkBinariesBuildPhase_Funcs.cmake
@@ -1,4 +1,3 @@
-cmake_minimum_required(VERSION 3.18...3.19)
 
 macro(returnOnError errorMsg)
   if(NOT "${errorMsg}" STREQUAL "")
diff --git a/Tests/RunCMake/XcodeProject/RunCMakeTest.cmake b/Tests/RunCMake/XcodeProject/RunCMakeTest.cmake
index 3910127..c124950 100644
--- a/Tests/RunCMake/XcodeProject/RunCMakeTest.cmake
+++ b/Tests/RunCMake/XcodeProject/RunCMakeTest.cmake
@@ -101,7 +101,7 @@
 
   run_cmake(XcodeSchemaGeneration)
   if (XCODE_VERSION VERSION_GREATER_EQUAL 13)
-    set(maybe_destination -destination platform=macOS)
+    set(maybe_destination -destination platform=macOS,arch=${CMAKE_HOST_SYSTEM_PROCESSOR})
   else()
     set(maybe_destination "")
   endif()
@@ -169,309 +169,4 @@
 
 BundleLinkBundle()
 
-
-# Isolate device tests from host architecture selection.
-unset(ENV{CMAKE_OSX_ARCHITECTURES})
-
-# Use a single build tree for a few tests without cleaning.
-
-if(NOT XCODE_VERSION VERSION_LESS 5)
-  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeInstallIOS-build)
-  set(RunCMake_TEST_NO_CLEAN 1)
-  set(RunCMake_TEST_OPTIONS
-    "-DCMAKE_SYSTEM_NAME=iOS"
-    "-DCMAKE_INSTALL_PREFIX:PATH=${RunCMake_BINARY_DIR}/ios_install")
-
-  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
-  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
-
-  run_cmake(XcodeInstallIOS)
-  run_cmake_command(XcodeInstallIOS-install ${CMAKE_COMMAND} --build . --target install)
-
-  unset(RunCMake_TEST_BINARY_DIR)
-  unset(RunCMake_TEST_NO_CLEAN)
-  unset(RunCMake_TEST_OPTIONS)
-
-  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeBundlesOSX-build)
-  set(RunCMake_TEST_NO_CLEAN 1)
-  set(RunCMake_TEST_OPTIONS
-    "-DCMAKE_SYSTEM_NAME=Darwin"
-    "-DCMAKE_INSTALL_PREFIX:PATH=${RunCMake_TEST_BINARY_DIR}/_install")
-
-  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
-  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
-
-  run_cmake(XcodeBundles)
-  run_cmake_command(XcodeBundles-build-macOS ${CMAKE_COMMAND} --build .)
-  run_cmake_command(XcodeBundles-install-macOS ${CMAKE_COMMAND} --build . --target install)
-
-  unset(RunCMake_TEST_BINARY_DIR)
-  unset(RunCMake_TEST_NO_CLEAN)
-  unset(RunCMake_TEST_OPTIONS)
-
-  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeBundlesIOS-build)
-  set(RunCMake_TEST_NO_CLEAN 1)
-  set(RunCMake_TEST_OPTIONS
-    "-DCMAKE_SYSTEM_NAME=iOS"
-    "-DCMAKE_INSTALL_PREFIX:PATH=${RunCMake_TEST_BINARY_DIR}/_install")
-
-  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
-  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
-
-  run_cmake(XcodeBundles)
-  run_cmake_command(XcodeBundles-build-iOS ${CMAKE_COMMAND} --build .)
-  run_cmake_command(XcodeBundles-install-iOS ${CMAKE_COMMAND} --build . --target install)
-
-  unset(RunCMake_TEST_BINARY_DIR)
-  unset(RunCMake_TEST_NO_CLEAN)
-  unset(RunCMake_TEST_OPTIONS)
-endif()
-
-if(NOT XCODE_VERSION VERSION_LESS 7)
-  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeBundlesWatchOS-build)
-  set(RunCMake_TEST_NO_CLEAN 1)
-  set(RunCMake_TEST_OPTIONS
-    "-DCMAKE_SYSTEM_NAME=watchOS"
-    "-DCMAKE_INSTALL_PREFIX:PATH=${RunCMake_TEST_BINARY_DIR}/_install")
-
-  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
-  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
-
-  run_cmake(XcodeBundles)
-  run_cmake_command(XcodeBundles-build-watchOS ${CMAKE_COMMAND} --build .)
-  run_cmake_command(XcodeBundles-install-watchOS ${CMAKE_COMMAND} --build . --target install)
-
-  unset(RunCMake_TEST_BINARY_DIR)
-  unset(RunCMake_TEST_NO_CLEAN)
-  unset(RunCMake_TEST_OPTIONS)
-endif()
-
-if(NOT XCODE_VERSION VERSION_LESS 7.1)
-  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeBundlesTvOS-build)
-  set(RunCMake_TEST_NO_CLEAN 1)
-  set(RunCMake_TEST_OPTIONS
-    "-DCMAKE_SYSTEM_NAME=tvOS"
-    "-DCMAKE_INSTALL_PREFIX:PATH=${RunCMake_TEST_BINARY_DIR}/_install")
-
-  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
-  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
-
-  run_cmake(XcodeBundles)
-  run_cmake_command(XcodeBundles-build-tvOS ${CMAKE_COMMAND} --build .)
-  run_cmake_command(XcodeBundles-install-tvOS ${CMAKE_COMMAND} --build . --target install)
-
-  unset(RunCMake_TEST_BINARY_DIR)
-  unset(RunCMake_TEST_NO_CLEAN)
-  unset(RunCMake_TEST_OPTIONS)
-endif()
-
-if(NOT XCODE_VERSION VERSION_LESS 7)
-  set(RunCMake_TEST_OPTIONS "-DCMAKE_TOOLCHAIN_FILE=${RunCMake_SOURCE_DIR}/osx.cmake")
-  run_cmake(XcodeTbdStub)
-  unset(RunCMake_TEST_OPTIONS)
-endif()
-
-if(XCODE_VERSION VERSION_GREATER_EQUAL 6)
-  # XcodeIOSInstallCombined
-  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeIOSInstallCombined-build)
-  set(RunCMake_TEST_NO_CLEAN 1)
-  set(RunCMake_TEST_OPTIONS
-    "-DCMAKE_SYSTEM_NAME=iOS"
-    "-DCMAKE_IOS_INSTALL_COMBINED=YES"
-    "-DCMAKE_INSTALL_PREFIX:PATH=${RunCMake_TEST_BINARY_DIR}/_install")
-
-  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
-  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
-
-  run_cmake(XcodeIOSInstallCombined)
-  run_cmake_command(XcodeIOSInstallCombined-build ${CMAKE_COMMAND} --build .)
-  if(XCODE_VERSION VERSION_LESS 12)
-    run_cmake_command(XcodeIOSInstallCombined-install ${CMAKE_COMMAND} --build . --target install)
-  endif()
-  # --build defaults to Debug, --install defaults to Release, so we have to
-  # specify the configuration explicitly
-  run_cmake_command(XcodeIOSInstallCombined-cmakeinstall
-    ${CMAKE_COMMAND} --install . --config Debug
-  )
-
-  unset(RunCMake_TEST_BINARY_DIR)
-  unset(RunCMake_TEST_NO_CLEAN)
-  unset(RunCMake_TEST_OPTIONS)
-
-  # XcodeIOSInstallCombinedPrune
-  # FIXME(#24011): Xcode 14 removed support for older architectures the test needs.
-  if(XCODE_VERSION VERSION_LESS 14)
-    set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeIOSInstallCombinedPrune-build)
-    set(RunCMake_TEST_NO_CLEAN 1)
-    set(RunCMake_TEST_OPTIONS
-      "-DCMAKE_SYSTEM_NAME=iOS"
-      "-DCMAKE_IOS_INSTALL_COMBINED=YES"
-      "-DCMAKE_INSTALL_PREFIX:PATH=${RunCMake_TEST_BINARY_DIR}/_install")
-
-    file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
-    file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
-
-    run_cmake(XcodeIOSInstallCombinedPrune)
-    run_cmake_command(XcodeIOSInstallCombinedPrune-build ${CMAKE_COMMAND} --build .)
-    if(XCODE_VERSION VERSION_LESS 12)
-      run_cmake_command(XcodeIOSInstallCombinedPrune-install ${CMAKE_COMMAND} --build . --target install)
-    endif()
-    # --build defaults to Debug, --install defaults to Release, so we have to
-    # specify the configuration explicitly
-    run_cmake_command(XcodeIOSInstallCombinedPrune-cmakeinstall
-      ${CMAKE_COMMAND} --install . --config Debug
-    )
-
-    unset(RunCMake_TEST_BINARY_DIR)
-    unset(RunCMake_TEST_NO_CLEAN)
-    unset(RunCMake_TEST_OPTIONS)
-  endif()
-
-  # XcodeIOSInstallCombinedSingleArch
-  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeIOSInstallCombinedSingleArch-build)
-  set(RunCMake_TEST_NO_CLEAN 1)
-  set(RunCMake_TEST_OPTIONS
-    "-DCMAKE_SYSTEM_NAME=iOS"
-    "-DCMAKE_IOS_INSTALL_COMBINED=YES"
-    "-DCMAKE_INSTALL_PREFIX:PATH=${RunCMake_TEST_BINARY_DIR}/_install")
-
-  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
-  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
-
-  run_cmake(XcodeIOSInstallCombinedSingleArch)
-  run_cmake_command(XcodeIOSInstallCombinedSingleArch-build ${CMAKE_COMMAND} --build .)
-  if(XCODE_VERSION VERSION_LESS 12)
-    run_cmake_command(XcodeIOSInstallCombinedSingleArch-install ${CMAKE_COMMAND} --build . --target install)
-  endif()
-  # --build defaults to Debug, --install defaults to Release, so we have to
-  # specify the configuration explicitly
-  run_cmake_command(XcodeIOSInstallCombinedSingleArch-cmakeinstall
-    ${CMAKE_COMMAND} --install . --config Debug
-  )
-
-  unset(RunCMake_TEST_BINARY_DIR)
-  unset(RunCMake_TEST_NO_CLEAN)
-  unset(RunCMake_TEST_OPTIONS)
-endif()
-
-if(NOT XCODE_VERSION VERSION_LESS 5)
-  # XcodeMultiplatform
-  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeMultiplatform-build)
-  set(RunCMake_TEST_NO_CLEAN 1)
-  set(RunCMake_TEST_OPTIONS "${IOS_DEPLOYMENT_TARGET}")
-
-  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
-  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
-
-  run_cmake(XcodeMultiplatform)
-
-  # build ios before macos
-  run_cmake_command(XcodeMultiplatform-iphonesimulator-build ${CMAKE_COMMAND} --build . -- -sdk iphonesimulator)
-  run_cmake_command(XcodeMultiplatform-iphonesimulator-install ${CMAKE_COMMAND} --build . --target install -- -sdk iphonesimulator DESTDIR=${RunCMake_TEST_BINARY_DIR}/_install_iphonesimulator)
-
-  run_cmake_command(XcodeMultiplatform-macosx-build ${CMAKE_COMMAND} --build . -- -sdk macosx)
-  run_cmake_command(XcodeMultiplatform-macosx-install ${CMAKE_COMMAND} --build . --target install -- -sdk macosx DESTDIR=${RunCMake_TEST_BINARY_DIR}/_install_macosx)
-
-  unset(RunCMake_TEST_BINARY_DIR)
-  unset(RunCMake_TEST_NO_CLEAN)
-  unset(RunCMake_TEST_OPTIONS)
-
-  # EffectivePlatformNameOFF
-  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/EffectivePlatformNameOFF-build)
-  set(RunCMake_TEST_NO_CLEAN 1)
-  set(RunCMake_TEST_OPTIONS "-DCMAKE_SYSTEM_NAME=iOS" "-DCMAKE_OSX_SYSROOT=iphonesimulator")
-
-  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
-  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
-
-  run_cmake(EffectivePlatformNameOFF)
-
-  run_cmake_command(EffectivePlatformNameOFF-iphonesimulator-build ${CMAKE_COMMAND} --build .)
-  run_cmake_command(EffectivePlatformNameOFF-iphonesimulator-install ${CMAKE_COMMAND} --build . --target install -- DESTDIR=${RunCMake_TEST_BINARY_DIR}/_install_iphonesimulator)
-
-  unset(RunCMake_TEST_BINARY_DIR)
-  unset(RunCMake_TEST_NO_CLEAN)
-  unset(RunCMake_TEST_OPTIONS)
-endif()
-
-if(XCODE_VERSION VERSION_GREATER_EQUAL 8)
-  function(deployment_target_test SystemName SDK)
-    set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/DeploymentTarget-${SDK}-build)
-    set(RunCMake_TEST_NO_CLEAN 1)
-    set(RunCMake_TEST_OPTIONS "-DCMAKE_SYSTEM_NAME=${SystemName}" "-DCMAKE_OSX_SYSROOT=${SDK}")
-
-    file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
-    file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
-
-    run_cmake(DeploymentTarget)
-    run_cmake_command(DeploymentTarget-${SDK} ${CMAKE_COMMAND} --build .)
-  endfunction()
-
-  deployment_target_test(Darwin macosx)
-  deployment_target_test(iOS iphoneos)
-  deployment_target_test(iOS iphonesimulator)
-  deployment_target_test(tvOS appletvos)
-  deployment_target_test(tvOS appletvsimulator)
-  deployment_target_test(watchOS watchos)
-  deployment_target_test(watchOS watchsimulator)
-endif()
-
-if(XCODE_VERSION VERSION_GREATER_EQUAL 8)
-  function(xctest_lookup_test SystemName SDK)
-    set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XCTestLookup-${SDK}-build)
-    set(RunCMake_TEST_OPTIONS "-DCMAKE_SYSTEM_NAME=${SystemName}" "-DCMAKE_OSX_SYSROOT=${SDK}")
-
-    run_cmake(XCTestLookup)
-  endfunction()
-
-  xctest_lookup_test(Darwin macosx)
-  xctest_lookup_test(iOS iphoneos)
-  xctest_lookup_test(iOS iphonesimulator)
-  xctest_lookup_test(tvOS appletvos)
-  xctest_lookup_test(tvOS appletvsimulator)
-endif()
-
-if(XCODE_VERSION VERSION_GREATER_EQUAL 8)
-  function(XcodeRemoveExcessiveISystemSDK SDK)
-    set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeRemoveExcessiveISystemSDK-${SDK}-build)
-    set(RunCMake_TEST_OPTIONS "-DCMAKE_SYSTEM_NAME=iOS" "-DCMAKE_OSX_SYSROOT=${SDK}")
-    run_cmake(XcodeRemoveExcessiveISystem)
-    set(RunCMake_TEST_NO_CLEAN 1)
-    run_cmake_command(XcodeRemoveExcessiveISystemSDK-${SDK}-build ${CMAKE_COMMAND} --build .)
-  endfunction()
-
-  XcodeRemoveExcessiveISystemSDK(iphoneos)
-  XcodeRemoveExcessiveISystemSDK(iphonesimulator)
-endif()
-
-if (XCODE_VERSION VERSION_GREATER_EQUAL 7.3)
-  function(xctest_add_bundle_test SystemName SDK BuildSystemVersion ExpectedOutputDir)
-    set(RunCMake_TEST_BINARY_DIR
-      ${RunCMake_BINARY_DIR}/DeploymentTarget-${SystemName}-${SDK}-${BuildSystemVersion}-build)
-    set(RunCMake_TEST_OPTIONS
-      "-DCMAKE_SYSTEM_NAME=${SystemName}"
-      "-DCMAKE_OSX_SYSROOT=${SDK}"
-      "-DTEST_EXPECTED_OUTPUT_DIR=${ExpectedOutputDir}")
-    unset(RunCMake_GENERATOR_TOOLSET)
-    if(BuildSystemVersion)
-      set(RunCMake_GENERATOR_TOOLSET "buildsystem=${BuildSystemVersion}")
-    endif()
-    run_cmake(XCTestAddBundle)
-  endfunction()
-
-  if(XCODE_VERSION VERSION_GREATER_EQUAL 12)
-    xctest_add_bundle_test(Darwin macosx "1" "$<TARGET_BUNDLE_CONTENT_DIR:TestedApp>/PlugIns")
-    xctest_add_bundle_test(Darwin macosx "12" "$<TARGET_BUNDLE_CONTENT_DIR:TestedApp>/PlugIns")
-    xctest_add_bundle_test(iOS iphonesimulator "1" "$<TARGET_BUNDLE_CONTENT_DIR:TestedApp>/PlugIns")
-    if (XCODE_VERSION VERSION_LESS 12.5)
-      xctest_add_bundle_test(iOS iphonesimulator "12" "$<TARGET_BUNDLE_CONTENT_DIR:TestedApp>")
-    else()
-      xctest_add_bundle_test(iOS iphonesimulator "12" "$<TARGET_BUNDLE_CONTENT_DIR:TestedApp>/PlugIns")
-    endif()
-  else()
-    xctest_add_bundle_test(Darwin macosx "" "$<TARGET_BUNDLE_CONTENT_DIR:TestedApp>/PlugIns")
-    xctest_add_bundle_test(iOS iphonesimulator "" "$<TARGET_BUNDLE_CONTENT_DIR:TestedApp>/PlugIns")
-  endif()
-endif()
-
-# Please add macOS-only tests above before the device-specific tests.
+# Please add device-specific tests to '../XcodeProject-Device/RunCMakeTest.cmake'.
diff --git a/Tests/RunCMake/XcodeProject/XcodeBundles.cmake b/Tests/RunCMake/XcodeProject/XcodeBundles.cmake
deleted file mode 100644
index bc14874..0000000
--- a/Tests/RunCMake/XcodeProject/XcodeBundles.cmake
+++ /dev/null
@@ -1,144 +0,0 @@
-# check if Xcode and CMake have the same understanding of Bundle layout
-
-cmake_minimum_required(VERSION 3.3)
-enable_language(C)
-
-if(CMAKE_SYSTEM_NAME STREQUAL "iOS")
-  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO")
-  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
-  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "")
-  set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "NO")
-endif()
-
-if(CMAKE_SYSTEM_NAME STREQUAL "tvOS" OR CMAKE_SYSTEM_NAME STREQUAL "watchOS")
-  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO")
-  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
-  set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "")
-  set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "YES")
-endif()
-
-# App Bundle
-
-add_executable(AppBundle MACOSX_BUNDLE main.m)
-
-add_custom_target(AppBundleTest ALL
-  COMMAND ${CMAKE_COMMAND} -E copy
-    "$<TARGET_FILE:AppBundle>" "$<TARGET_FILE:AppBundle>.old")
-
-add_dependencies(AppBundleTest AppBundle)
-
-# with custom extension
-
-if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
-  add_executable(AppBundleExt MACOSX_BUNDLE main.m)
-  set_target_properties(AppBundleExt PROPERTIES BUNDLE_EXTENSION "foo")
-  install(TARGETS AppBundleExt BUNDLE DESTINATION FooExtension)
-
-  add_custom_target(AppBundleExtTest ALL
-    COMMAND ${CMAKE_COMMAND} -E copy
-      "$<TARGET_FILE:AppBundleExt>" "$<TARGET_FILE:AppBundleExt>.old")
-
-  add_dependencies(AppBundleExtTest AppBundleExt)
-endif()
-
-# Shared Framework (not supported for iOS on Xcode < 6)
-
-if(NOT CMAKE_SYSTEM_NAME STREQUAL "iOS" OR NOT XCODE_VERSION VERSION_LESS 6)
-  add_library(SharedFramework SHARED main.c)
-  set_target_properties(SharedFramework PROPERTIES FRAMEWORK TRUE)
-
-  add_custom_target(SharedFrameworkTest ALL
-    COMMAND ${CMAKE_COMMAND} -E copy
-      "$<TARGET_BUNDLE_DIR:SharedFramework>" "$<TARGET_BUNDLE_DIR:SharedFramework>.old"
-    COMMAND ${CMAKE_COMMAND} -E copy
-      "$<TARGET_BUNDLE_CONTENT_DIR:SharedFramework>" "$<TARGET_BUNDLE_CONTENT_DIR:SharedFramework>.old"
-    COMMAND ${CMAKE_COMMAND} -E copy
-      "$<TARGET_FILE:SharedFramework>" "$<TARGET_FILE:SharedFramework>.old")
-
-  add_dependencies(SharedFrameworkTest SharedFramework)
-
-  # with custom extension
-
-  add_library(SharedFrameworkExt SHARED main.c)
-  set_target_properties(SharedFrameworkExt PROPERTIES FRAMEWORK TRUE)
-  set_target_properties(SharedFrameworkExt PROPERTIES BUNDLE_EXTENSION "foo")
-  install(TARGETS SharedFrameworkExt FRAMEWORK DESTINATION FooExtension)
-
-  add_custom_target(SharedFrameworkExtTest ALL
-    COMMAND ${CMAKE_COMMAND} -E copy
-      "$<TARGET_BUNDLE_DIR:SharedFrameworkExt>" "$<TARGET_BUNDLE_DIR:SharedFrameworkExt>.old"
-    COMMAND ${CMAKE_COMMAND} -E copy
-      "$<TARGET_BUNDLE_CONTENT_DIR:SharedFrameworkExt>" "$<TARGET_BUNDLE_CONTENT_DIR:SharedFrameworkExt>.old"
-    COMMAND ${CMAKE_COMMAND} -E copy
-      "$<TARGET_FILE:SharedFrameworkExt>" "$<TARGET_FILE:SharedFrameworkExt>.old")
-
-  add_dependencies(SharedFrameworkExtTest SharedFrameworkExt)
-endif()
-
-# Static Framework (not supported for Xcode < 6)
-
-if(NOT XCODE_VERSION VERSION_LESS 6)
-  add_library(StaticFramework STATIC main.c)
-  set_target_properties(StaticFramework PROPERTIES FRAMEWORK TRUE)
-
-  add_custom_target(StaticFrameworkTest ALL
-    COMMAND ${CMAKE_COMMAND} -E copy
-      "$<TARGET_BUNDLE_DIR:StaticFramework>" "$<TARGET_BUNDLE_DIR:StaticFramework>.old"
-    COMMAND ${CMAKE_COMMAND} -E copy
-      "$<TARGET_BUNDLE_CONTENT_DIR:StaticFramework>" "$<TARGET_BUNDLE_CONTENT_DIR:StaticFramework>.old"
-    COMMAND ${CMAKE_COMMAND} -E copy
-      "$<TARGET_FILE:StaticFramework>" "$<TARGET_FILE:StaticFramework>.old")
-
-  add_dependencies(StaticFrameworkTest StaticFramework)
-
-  # with custom extension
-
-  add_library(StaticFrameworkExt STATIC main.c)
-  set_target_properties(StaticFrameworkExt PROPERTIES FRAMEWORK TRUE)
-  set_target_properties(StaticFrameworkExt PROPERTIES BUNDLE_EXTENSION "foo")
-  install(TARGETS StaticFrameworkExt FRAMEWORK DESTINATION StaticFooExtension)
-
-  add_custom_target(StaticFrameworkExtTest ALL
-    COMMAND ${CMAKE_COMMAND} -E copy
-      "$<TARGET_BUNDLE_DIR:StaticFrameworkExt>" "$<TARGET_BUNDLE_DIR:StaticFrameworkExt>.old"
-    COMMAND ${CMAKE_COMMAND} -E copy
-      "$<TARGET_BUNDLE_CONTENT_DIR:StaticFrameworkExt>" "$<TARGET_BUNDLE_CONTENT_DIR:StaticFrameworkExt>.old"
-    COMMAND ${CMAKE_COMMAND} -E copy
-      "$<TARGET_FILE:StaticFrameworkExt>" "$<TARGET_FILE:StaticFrameworkExt>.old")
-
-  add_dependencies(StaticFrameworkExtTest StaticFrameworkExt)
-endif()
-
-# Bundle
-
-if(NOT CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE)
-  add_library(Bundle MODULE main.c)
-  set_target_properties(Bundle PROPERTIES BUNDLE TRUE)
-
-  add_custom_target(BundleTest ALL
-    COMMAND ${CMAKE_COMMAND} -E copy
-      "$<TARGET_BUNDLE_DIR:Bundle>" "$<TARGET_BUNDLE_DIR:Bundle>.old"
-    COMMAND ${CMAKE_COMMAND} -E copy
-      "$<TARGET_BUNDLE_CONTENT_DIR:Bundle>" "$<TARGET_BUNDLE_CONTENT_DIR:Bundle>.old"
-    COMMAND ${CMAKE_COMMAND} -E copy
-      "$<TARGET_FILE:Bundle>" "$<TARGET_FILE:Bundle>.old")
-
-  add_dependencies(BundleTest Bundle)
-
-  # with custom extension
-
-  add_library(BundleExt MODULE main.c)
-  set_target_properties(BundleExt PROPERTIES BUNDLE TRUE)
-  set_target_properties(BundleExt PROPERTIES BUNDLE_EXTENSION "foo")
-  install(TARGETS BundleExt LIBRARY DESTINATION FooExtension)
-
-  add_custom_target(BundleExtTest ALL
-    COMMAND ${CMAKE_COMMAND} -E copy
-      "$<TARGET_BUNDLE_DIR:BundleExt>" "$<TARGET_BUNDLE_DIR:BundleExt>.old"
-    COMMAND ${CMAKE_COMMAND} -E copy
-      "$<TARGET_BUNDLE_CONTENT_DIR:BundleExt>" "$<TARGET_BUNDLE_CONTENT_DIR:BundleExt>.old"
-    COMMAND ${CMAKE_COMMAND} -E copy
-      "$<TARGET_FILE:BundleExt>" "$<TARGET_FILE:BundleExt>.old")
-
-  add_dependencies(BundleExtTest BundleExt)
-endif()
diff --git a/Tests/RunCMake/XcodeProject/XcodeIOSInstallCombined.cmake b/Tests/RunCMake/XcodeProject/XcodeIOSInstallCombined.cmake
deleted file mode 100644
index 8426148..0000000
--- a/Tests/RunCMake/XcodeProject/XcodeIOSInstallCombined.cmake
+++ /dev/null
@@ -1,42 +0,0 @@
-cmake_minimum_required(VERSION 3.3)
-
-project(IOSInstallCombined CXX)
-
-set(maybe_armv7 armv7)
-set(maybe_i386 i386)
-if(XCODE_VERSION VERSION_GREATER_EQUAL 14)
-  set(CMAKE_OSX_DEPLOYMENT_TARGET 16)
-  set(maybe_armv7 "")
-  set(maybe_i386 "")
-elseif(XCODE_VERSION VERSION_GREATER_EQUAL 9)
-  set(CMAKE_OSX_DEPLOYMENT_TARGET 10)
-endif()
-
-if(NOT IOS)
-  message(FATAL_ERROR "IOS variable is not set")
-endif()
-
-set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO")
-set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
-set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "")
-set(CMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT "dwarf")
-set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "NO")
-
-set(CMAKE_OSX_ARCHITECTURES ${maybe_armv7} arm64 ${maybe_i386} x86_64)
-
-add_executable(foo_app MACOSX_BUNDLE main.cpp)
-install(TARGETS foo_app BUNDLE DESTINATION bin)
-
-add_library(foo_static STATIC foo.cpp)
-install(TARGETS foo_static ARCHIVE DESTINATION lib)
-
-add_library(foo_shared SHARED foo.cpp)
-install(TARGETS foo_shared LIBRARY DESTINATION lib)
-
-add_library(foo_bundle MODULE foo.cpp)
-set_target_properties(foo_bundle PROPERTIES BUNDLE TRUE)
-install(TARGETS foo_bundle LIBRARY DESTINATION lib)
-
-add_library(foo_framework SHARED foo.cpp)
-set_target_properties(foo_framework PROPERTIES FRAMEWORK TRUE)
-install(TARGETS foo_framework FRAMEWORK DESTINATION lib)
diff --git a/Tests/RunCMake/XcodeProject/XcodeIOSInstallCombinedPrune-install-stdout.txt b/Tests/RunCMake/XcodeProject/XcodeIOSInstallCombinedPrune-install-stdout.txt
deleted file mode 100644
index 28edadc..0000000
--- a/Tests/RunCMake/XcodeProject/XcodeIOSInstallCombinedPrune-install-stdout.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-.*Unexpected architecture `i386` detected.*
-.*Unexpected architecture `arm64` detected.*
\ No newline at end of file
diff --git a/Tests/RunCMake/XcodeProject/XcodeIOSInstallCombinedPrune.cmake b/Tests/RunCMake/XcodeProject/XcodeIOSInstallCombinedPrune.cmake
deleted file mode 100644
index 7d14d19..0000000
--- a/Tests/RunCMake/XcodeProject/XcodeIOSInstallCombinedPrune.cmake
+++ /dev/null
@@ -1,41 +0,0 @@
-cmake_minimum_required(VERSION 3.3)
-
-project(XcodeIOSInstallCombinedPrune CXX)
-
-if(XCODE_VERSION VERSION_GREATER_EQUAL 9)
-  set(CMAKE_OSX_DEPLOYMENT_TARGET 10)
-endif()
-
-set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO")
-set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
-set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "")
-set(CMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT "dwarf")
-
-add_library(foo SHARED foo.cpp)
-install(TARGETS foo DESTINATION lib)
-
-add_library(baz SHARED foo.cpp)
-set_target_properties(
-  foo baz
-  PROPERTIES
-  XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] armv7
-  XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphoneos*] armv7
-  XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] x86_64
-  XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] x86_64
-)
-
-add_library(boo SHARED foo.cpp)
-set_target_properties(
-  boo
-  PROPERTIES
-  XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] arm64
-  XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphoneos*] arm64
-  XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] i386
-  XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] i386
-)
-
-add_custom_command(
-  TARGET foo
-  POST_BUILD
-  COMMAND lipo -create $<TARGET_FILE:baz> $<TARGET_FILE:boo> -output $<TARGET_FILE:foo>
-)
diff --git a/Tests/RunCMake/XcodeProject/XcodeIOSInstallCombinedSingleArch.cmake b/Tests/RunCMake/XcodeProject/XcodeIOSInstallCombinedSingleArch.cmake
deleted file mode 100644
index 5177ec2..0000000
--- a/Tests/RunCMake/XcodeProject/XcodeIOSInstallCombinedSingleArch.cmake
+++ /dev/null
@@ -1,28 +0,0 @@
-cmake_minimum_required(VERSION 3.3)
-
-project(XcodeIOSInstallCombinedSingleArch CXX)
-
-set(iphoneos_arch armv7)
-if(XCODE_VERSION VERSION_GREATER_EQUAL 14)
-  set(CMAKE_OSX_DEPLOYMENT_TARGET 16)
-  set(iphoneos_arch arm64)
-elseif(XCODE_VERSION VERSION_GREATER_EQUAL 9)
-  set(CMAKE_OSX_DEPLOYMENT_TARGET 10)
-endif()
-
-set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO")
-set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
-set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "")
-set(CMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT "dwarf")
-
-add_library(foo SHARED foo.cpp)
-install(TARGETS foo DESTINATION lib)
-
-set_target_properties(
-  foo
-  PROPERTIES
-  XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] ${iphoneos_arch}
-  XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphoneos*] ${iphoneos_arch}
-  XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] ""
-  XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] ""
-)
diff --git a/Tests/RunCMake/XcodeProject/XcodeInstallIOS.cmake b/Tests/RunCMake/XcodeProject/XcodeInstallIOS.cmake
deleted file mode 100644
index 75da7b1..0000000
--- a/Tests/RunCMake/XcodeProject/XcodeInstallIOS.cmake
+++ /dev/null
@@ -1,9 +0,0 @@
-cmake_minimum_required(VERSION 3.3)
-
-project(XcodeInstallIOS)
-
-set(XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
-set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "NO")
-
-add_library(foo STATIC foo.cpp)
-install(TARGETS foo ARCHIVE DESTINATION lib)
diff --git a/Tests/RunCMake/XcodeProject/XcodeMultiplatform.cmake b/Tests/RunCMake/XcodeProject/XcodeMultiplatform.cmake
deleted file mode 100644
index a1064f4..0000000
--- a/Tests/RunCMake/XcodeProject/XcodeMultiplatform.cmake
+++ /dev/null
@@ -1,14 +0,0 @@
-cmake_minimum_required(VERSION 3.3)
-enable_language(CXX)
-
-set_property(GLOBAL PROPERTY XCODE_EMIT_EFFECTIVE_PLATFORM_NAME ON)
-
-set(CMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS "macosx iphonesimulator")
-set(CMAKE_MACOSX_BUNDLE true)
-
-add_library(library STATIC foo.cpp)
-
-add_executable(main main.cpp)
-target_link_libraries(main library)
-
-install(TARGETS library ARCHIVE DESTINATION lib)
diff --git a/Tests/RunCMake/XcodeProject/XcodeObjcFlags.cmake b/Tests/RunCMake/XcodeProject/XcodeObjcFlags.cmake
index 4840276..8f54046 100644
--- a/Tests/RunCMake/XcodeProject/XcodeObjcFlags.cmake
+++ b/Tests/RunCMake/XcodeProject/XcodeObjcFlags.cmake
@@ -1,5 +1,5 @@
-cmake_minimum_required(VERSION 3.15)
-project(objctest LANGUAGES C OBJC)
+enable_language(C)
+enable_language(OBJC)
 
 include(CheckOBJCCompilerFlag)
 check_objc_compiler_flag(-fobjc-arc HAVE_OBJC_ARC)
diff --git a/Tests/RunCMake/XcodeProject/XcodeObjcxxFlags.cmake b/Tests/RunCMake/XcodeProject/XcodeObjcxxFlags.cmake
index 0ad942f..193860c 100644
--- a/Tests/RunCMake/XcodeProject/XcodeObjcxxFlags.cmake
+++ b/Tests/RunCMake/XcodeProject/XcodeObjcxxFlags.cmake
@@ -1,5 +1,5 @@
-cmake_minimum_required(VERSION 3.15)
-project(objcxxtest LANGUAGES CXX OBJCXX)
+enable_language(CXX)
+enable_language(OBJCXX)
 
 include(CheckOBJCXXCompilerFlag)
 check_objcxx_compiler_flag(-fobjc-arc HAVE_OBJC_ARC)
diff --git a/Tests/RunCMake/XcodeProject/XcodeObjectLibsInTwoProjects.cmake b/Tests/RunCMake/XcodeProject/XcodeObjectLibsInTwoProjects.cmake
index 3ca24af..5e66e82 100644
--- a/Tests/RunCMake/XcodeProject/XcodeObjectLibsInTwoProjects.cmake
+++ b/Tests/RunCMake/XcodeProject/XcodeObjectLibsInTwoProjects.cmake
@@ -1,5 +1 @@
-cmake_minimum_required (VERSION 3.19)
-
-project (test_xcode_fail NONE)
-
 add_subdirectory(subproject_two_object_libs)
diff --git a/Tests/RunCMake/XcodeProject/XcodeRemoveExcessiveISystem.cmake b/Tests/RunCMake/XcodeProject/XcodeRemoveExcessiveISystem.cmake
index 44052f0..6b09b51 100644
--- a/Tests/RunCMake/XcodeProject/XcodeRemoveExcessiveISystem.cmake
+++ b/Tests/RunCMake/XcodeProject/XcodeRemoveExcessiveISystem.cmake
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 3.14)
+# This file is also included from '../XcodeProject-Device/XcodeRemoveExcessiveISystem.cmake'.
 
 if(NOT "$ENV{CMAKE_OSX_ARCHITECTURES}" MATCHES "[;$]")
   set(USE_SWIFT 1)
@@ -34,12 +34,12 @@
 
 find_package(ZLIB REQUIRED)
 add_library (framework_dependency STATIC)
-target_sources (framework_dependency PRIVATE use_cmath.cpp)
+target_sources (framework_dependency PRIVATE ${CMAKE_CURRENT_LIST_DIR}/use_cmath.cpp)
 target_link_libraries(framework_dependency INTERFACE ZLIB::ZLIB)
 
-add_library (framework_test SHARED use_cmath.cpp)
+add_library (framework_test SHARED ${CMAKE_CURRENT_LIST_DIR}/use_cmath.cpp)
 if(USE_SWIFT)
-  target_sources(framework_test PRIVATE foo.swift)
+  target_sources(framework_test PRIVATE ${CMAKE_CURRENT_LIST_DIR}/foo.swift)
 endif()
 target_link_libraries (framework_test PRIVATE framework_dependency)
 
diff --git a/Tests/RunCMake/XcodeProject/XcodeSchemaGeneration.cmake b/Tests/RunCMake/XcodeProject/XcodeSchemaGeneration.cmake
index 2fe5a9f..ff3419c 100644
--- a/Tests/RunCMake/XcodeProject/XcodeSchemaGeneration.cmake
+++ b/Tests/RunCMake/XcodeProject/XcodeSchemaGeneration.cmake
@@ -1,5 +1,3 @@
-cmake_minimum_required(VERSION 3.7)
-
-project(XcodeSchemaGeneration CXX)
+enable_language(CXX)
 
 add_executable(foo main.cpp)
diff --git a/Tests/RunCMake/XcodeProject/XcodeSchemaProperty.cmake b/Tests/RunCMake/XcodeProject/XcodeSchemaProperty.cmake
index 267e379..05a8976 100644
--- a/Tests/RunCMake/XcodeProject/XcodeSchemaProperty.cmake
+++ b/Tests/RunCMake/XcodeProject/XcodeSchemaProperty.cmake
@@ -1,9 +1,7 @@
-cmake_minimum_required(VERSION 3.7)
+enable_language(CXX)
 
 set(CMAKE_XCODE_GENERATE_SCHEME ON)
 
-project(XcodeSchemaProperty CXX)
-
 function(create_scheme_for_variable variable)
   set(CMAKE_XCODE_SCHEME_${variable} ON)
   add_executable(${variable} main.cpp)
diff --git a/Tests/RunCMake/XcodeProject/XcodeTbdStub.cmake b/Tests/RunCMake/XcodeProject/XcodeTbdStub.cmake
deleted file mode 100644
index e83d7f3..0000000
--- a/Tests/RunCMake/XcodeProject/XcodeTbdStub.cmake
+++ /dev/null
@@ -1,2 +0,0 @@
-cmake_minimum_required(VERSION 3.3)
-find_package(ZLIB REQUIRED)
diff --git a/Tests/RunCMake/XcodeProject/XcodeXCConfig.cmake b/Tests/RunCMake/XcodeProject/XcodeXCConfig.cmake
index 58d2616..bd8995b 100644
--- a/Tests/RunCMake/XcodeProject/XcodeXCConfig.cmake
+++ b/Tests/RunCMake/XcodeProject/XcodeXCConfig.cmake
@@ -1,6 +1,4 @@
-cmake_minimum_required(VERSION 3.23)
-
-project(XcodeXCConfig C)
+enable_language(C)
 
 set(CMAKE_XCODE_XCCONFIG "$<IF:$<CONFIG:Debug>,XcodeXCConfig.global.debug.xcconfig,XcodeXCConfig.global.release.xcconfig>")
 
diff --git a/Tests/RunCMake/add_custom_command/CMakeLists.txt b/Tests/RunCMake/add_custom_command/CMakeLists.txt
index ef2163c..93ee9df 100644
--- a/Tests/RunCMake/add_custom_command/CMakeLists.txt
+++ b/Tests/RunCMake/add_custom_command/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/add_custom_command/WorkingDirectory.cmake b/Tests/RunCMake/add_custom_command/WorkingDirectory.cmake
index 65b7250..d8a5d86 100644
--- a/Tests/RunCMake/add_custom_command/WorkingDirectory.cmake
+++ b/Tests/RunCMake/add_custom_command/WorkingDirectory.cmake
@@ -2,7 +2,7 @@
 add_custom_command(
   OUTPUT out.txt
   COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/PrintDir.cmake
-  WORKING_DIRECTORY ${CMAKE_CFG_INTDIR}
+  WORKING_DIRECTORY $<CONFIG>
   )
 set_property(SOURCE out.txt PROPERTY SYMBOLIC 1)
 add_custom_target(drive ALL DEPENDS out.txt)
diff --git a/Tests/RunCMake/add_custom_target/CMakeLists.txt b/Tests/RunCMake/add_custom_target/CMakeLists.txt
index ef2163c..93ee9df 100644
--- a/Tests/RunCMake/add_custom_target/CMakeLists.txt
+++ b/Tests/RunCMake/add_custom_target/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/add_dependencies/CMakeLists.txt b/Tests/RunCMake/add_dependencies/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/add_dependencies/CMakeLists.txt
+++ b/Tests/RunCMake/add_dependencies/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/add_dependencies/ReadOnlyProperty.cmake b/Tests/RunCMake/add_dependencies/ReadOnlyProperty.cmake
index f0e4069..547c101 100644
--- a/Tests/RunCMake/add_dependencies/ReadOnlyProperty.cmake
+++ b/Tests/RunCMake/add_dependencies/ReadOnlyProperty.cmake
@@ -1,5 +1,4 @@
-cmake_minimum_required(VERSION 3.7)
-project(ReadOnlyProperty C)
+enable_language(C)
 
 add_library(a a.c)
 
diff --git a/Tests/RunCMake/add_dependencies/RetrieveDependencies.cmake b/Tests/RunCMake/add_dependencies/RetrieveDependencies.cmake
index 45b3974..2f883af 100644
--- a/Tests/RunCMake/add_dependencies/RetrieveDependencies.cmake
+++ b/Tests/RunCMake/add_dependencies/RetrieveDependencies.cmake
@@ -1,5 +1,4 @@
-cmake_minimum_required(VERSION 3.7)
-project(RetrieveDependencies C)
+enable_language(C)
 
 add_library(a a.c)
 
diff --git a/Tests/RunCMake/add_executable/CMakeLists.txt b/Tests/RunCMake/add_executable/CMakeLists.txt
index ef2163c..93ee9df 100644
--- a/Tests/RunCMake/add_executable/CMakeLists.txt
+++ b/Tests/RunCMake/add_executable/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/add_library/CMakeLists.txt b/Tests/RunCMake/add_library/CMakeLists.txt
index ef2163c..93ee9df 100644
--- a/Tests/RunCMake/add_library/CMakeLists.txt
+++ b/Tests/RunCMake/add_library/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/add_link_options/LINKER_expansion-list.cmake b/Tests/RunCMake/add_link_options/LINKER_expansion-list.cmake
index c77b43c..f7c551d 100644
--- a/Tests/RunCMake/add_link_options/LINKER_expansion-list.cmake
+++ b/Tests/RunCMake/add_link_options/LINKER_expansion-list.cmake
@@ -11,7 +11,7 @@
 
 add_library(example SHARED LinkOptionsLib.c)
 # use LAUNCH facility to dump linker command
-set_property(TARGET example PROPERTY RULE_LAUNCH_LINK "\"${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/dump${CMAKE_EXECUTABLE_SUFFIX}\"")
+set_property(TARGET example PROPERTY RULE_LAUNCH_LINK "\"$<TARGET_FILE:dump>\"")
 
 add_dependencies (example dump)
 
diff --git a/Tests/RunCMake/add_subdirectory/CMakeLists.txt b/Tests/RunCMake/add_subdirectory/CMakeLists.txt
index 47d249c..b5d7262 100644
--- a/Tests/RunCMake/add_subdirectory/CMakeLists.txt
+++ b/Tests/RunCMake/add_subdirectory/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.2)
+cmake_minimum_required(VERSION 3.5)
 
 # Have to set policy here due to policy scope
 if(DEFINED CMP0082_VALUE)
diff --git a/Tests/RunCMake/add_test/CMP0110-OLD-AlphaNumeric-stderr.txt b/Tests/RunCMake/add_test/CMP0110-OLD-AlphaNumeric-stderr.txt
new file mode 100644
index 0000000..46b32d1
--- /dev/null
+++ b/Tests/RunCMake/add_test/CMP0110-OLD-AlphaNumeric-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at CMP0110-OLD-AlphaNumeric.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0110 will be removed from a future version
+  of CMake.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/add_test/CMP0110-OLD-BracketArgument-stderr.txt b/Tests/RunCMake/add_test/CMP0110-OLD-BracketArgument-stderr.txt
new file mode 100644
index 0000000..906f318
--- /dev/null
+++ b/Tests/RunCMake/add_test/CMP0110-OLD-BracketArgument-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at CMP0110-OLD-BracketArgument.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0110 will be removed from a future version
+  of CMake.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/add_test/CMP0110-OLD-EscapedSpecialChars-stderr.txt b/Tests/RunCMake/add_test/CMP0110-OLD-EscapedSpecialChars-stderr.txt
new file mode 100644
index 0000000..65818fa
--- /dev/null
+++ b/Tests/RunCMake/add_test/CMP0110-OLD-EscapedSpecialChars-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at CMP0110-OLD-EscapedSpecialChars.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0110 will be removed from a future version
+  of CMake.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/add_test/CMP0110-OLD-FormerInvalidSpecialChars-stderr.txt b/Tests/RunCMake/add_test/CMP0110-OLD-FormerInvalidSpecialChars-stderr.txt
new file mode 100644
index 0000000..db88659
--- /dev/null
+++ b/Tests/RunCMake/add_test/CMP0110-OLD-FormerInvalidSpecialChars-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at CMP0110-OLD-FormerInvalidSpecialChars.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0110 will be removed from a future version
+  of CMake.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/add_test/CMP0110-OLD-FormerInvalidSpecialCharsMC-stderr.txt b/Tests/RunCMake/add_test/CMP0110-OLD-FormerInvalidSpecialCharsMC-stderr.txt
new file mode 100644
index 0000000..42cc02a
--- /dev/null
+++ b/Tests/RunCMake/add_test/CMP0110-OLD-FormerInvalidSpecialCharsMC-stderr.txt
@@ -0,0 +1,11 @@
+^CMake Deprecation Warning at CMP0110-OLD-FormerInvalidSpecialChars.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0110 will be removed from a future version
+  of CMake.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD.
+Call Stack \(most recent call first\):
+  CMP0110-OLD-FormerInvalidSpecialCharsMC.cmake:[0-9]+ \(include\)
+  CMakeLists.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/add_test/CMP0110-OLD-LeadingAndTrailingWhitespace-stderr.txt b/Tests/RunCMake/add_test/CMP0110-OLD-LeadingAndTrailingWhitespace-stderr.txt
new file mode 100644
index 0000000..6a39db6
--- /dev/null
+++ b/Tests/RunCMake/add_test/CMP0110-OLD-LeadingAndTrailingWhitespace-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at CMP0110-OLD-LeadingAndTrailingWhitespace.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0110 will be removed from a future version
+  of CMake.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/add_test/CMP0110-OLD-OtherSpecialChars-stderr.txt b/Tests/RunCMake/add_test/CMP0110-OLD-OtherSpecialChars-stderr.txt
new file mode 100644
index 0000000..d5dcada
--- /dev/null
+++ b/Tests/RunCMake/add_test/CMP0110-OLD-OtherSpecialChars-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at CMP0110-OLD-OtherSpecialChars.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0110 will be removed from a future version
+  of CMake.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/add_test/CMP0110-OLD-Quote-stderr.txt b/Tests/RunCMake/add_test/CMP0110-OLD-Quote-stderr.txt
new file mode 100644
index 0000000..69cd304
--- /dev/null
+++ b/Tests/RunCMake/add_test/CMP0110-OLD-Quote-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at CMP0110-OLD-Quote.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0110 will be removed from a future version
+  of CMake.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/add_test/CMP0110-OLD-Semicolon-stderr.txt b/Tests/RunCMake/add_test/CMP0110-OLD-Semicolon-stderr.txt
new file mode 100644
index 0000000..e601bfc
--- /dev/null
+++ b/Tests/RunCMake/add_test/CMP0110-OLD-Semicolon-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at CMP0110-OLD-Semicolon.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0110 will be removed from a future version
+  of CMake.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/add_test/CMP0110-OLD-Space-stderr.txt b/Tests/RunCMake/add_test/CMP0110-OLD-Space-stderr.txt
new file mode 100644
index 0000000..618d2b1
--- /dev/null
+++ b/Tests/RunCMake/add_test/CMP0110-OLD-Space-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at CMP0110-OLD-Space.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0110 will be removed from a future version
+  of CMake.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/add_test/CMP0110-OLD-ValidSpecialChars-stderr.txt b/Tests/RunCMake/add_test/CMP0110-OLD-ValidSpecialChars-stderr.txt
new file mode 100644
index 0000000..ad67c08
--- /dev/null
+++ b/Tests/RunCMake/add_test/CMP0110-OLD-ValidSpecialChars-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at CMP0110-OLD-ValidSpecialChars.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0110 will be removed from a future version
+  of CMake.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/alias_targets/CMakeLists.txt b/Tests/RunCMake/alias_targets/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/alias_targets/CMakeLists.txt
+++ b/Tests/RunCMake/alias_targets/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/alias_targets/invalid-name.cmake b/Tests/RunCMake/alias_targets/invalid-name.cmake
index 01983e5..e2ebbfa 100644
--- a/Tests/RunCMake/alias_targets/invalid-name.cmake
+++ b/Tests/RunCMake/alias_targets/invalid-name.cmake
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_policy(SET CMP0037 OLD)
 enable_language(CXX)
 
 add_library(foo empty.cpp)
diff --git a/Tests/RunCMake/build_command/CMakeLists.txt b/Tests/RunCMake/build_command/CMakeLists.txt
index f1a83e8..1319aec 100644
--- a/Tests/RunCMake/build_command/CMakeLists.txt
+++ b/Tests/RunCMake/build_command/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 if(NOT NoProject)
   project(${RunCMake_TEST} NONE)
 endif()
diff --git a/Tests/RunCMake/cmake_minimum_required/Before2812-stderr.txt b/Tests/RunCMake/cmake_minimum_required/Before2812-stderr.txt
deleted file mode 100644
index 7d40dcb..0000000
--- a/Tests/RunCMake/cmake_minimum_required/Before2812-stderr.txt
+++ /dev/null
@@ -1,26 +0,0 @@
-^CMake Deprecation Warning at Before2812.cmake:1 \(cmake_minimum_required\):
-  Compatibility with CMake < 2.8.12 will be removed from a future version of
-  CMake.
-
-  Update the VERSION argument <min> value or use a ...<max> suffix to tell
-  CMake that the project does not need compatibility with older versions.
-Call Stack \(most recent call first\):
-  CMakeLists.txt:3 \(include\)
-+
-CMake Deprecation Warning at Before2812.cmake:2 \(cmake_policy\):
-  Compatibility with CMake < 2.8.12 will be removed from a future version of
-  CMake.
-
-  Update the VERSION argument <min> value or use a ...<max> suffix to tell
-  CMake that the project does not need compatibility with older versions.
-Call Stack \(most recent call first\):
-  CMakeLists.txt:3 \(include\)
-+
-CMake Deprecation Warning at Before2812.cmake:6 \(cmake_policy\):
-  Compatibility with CMake < 2.8.12 will be removed from a future version of
-  CMake.
-
-  Update the VERSION argument <min> value or use a ...<max> suffix to tell
-  CMake that the project does not need compatibility with older versions.
-Call Stack \(most recent call first\):
-  CMakeLists.txt:3 \(include\)$
diff --git a/Tests/RunCMake/cmake_minimum_required/Before3_5-stderr.txt b/Tests/RunCMake/cmake_minimum_required/Before3_5-stderr.txt
new file mode 100644
index 0000000..7981235
--- /dev/null
+++ b/Tests/RunCMake/cmake_minimum_required/Before3_5-stderr.txt
@@ -0,0 +1,26 @@
+^CMake Deprecation Warning at Before3_5\.cmake:1 \(cmake_minimum_required\):
+  Compatibility with CMake < 3\.5 will be removed from a future version of
+  CMake\.
+
+  Update the VERSION argument <min> value or use a \.\.\.<max> suffix to tell
+  CMake that the project does not need compatibility with older versions\.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
++
+CMake Deprecation Warning at Before3_5\.cmake:2 \(cmake_policy\):
+  Compatibility with CMake < 3\.5 will be removed from a future version of
+  CMake\.
+
+  Update the VERSION argument <min> value or use a \.\.\.<max> suffix to tell
+  CMake that the project does not need compatibility with older versions\.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
++
+CMake Deprecation Warning at Before3_5\.cmake:6 \(cmake_policy\):
+  Compatibility with CMake < 3\.5 will be removed from a future version of
+  CMake\.
+
+  Update the VERSION argument <min> value or use a \.\.\.<max> suffix to tell
+  CMake that the project does not need compatibility with older versions\.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)$
diff --git a/Tests/RunCMake/cmake_minimum_required/Before2812.cmake b/Tests/RunCMake/cmake_minimum_required/Before3_5.cmake
similarity index 100%
rename from Tests/RunCMake/cmake_minimum_required/Before2812.cmake
rename to Tests/RunCMake/cmake_minimum_required/Before3_5.cmake
diff --git a/Tests/RunCMake/cmake_minimum_required/CMakeLists.txt b/Tests/RunCMake/cmake_minimum_required/CMakeLists.txt
index 667561e..8eb5748 100644
--- a/Tests/RunCMake/cmake_minimum_required/CMakeLists.txt
+++ b/Tests/RunCMake/cmake_minimum_required/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake NO_POLICY_SCOPE)
diff --git a/Tests/RunCMake/cmake_minimum_required/CompatBefore24-stderr.txt b/Tests/RunCMake/cmake_minimum_required/CompatBefore24-stderr.txt
index 81d26d2..3eb980a 100644
--- a/Tests/RunCMake/cmake_minimum_required/CompatBefore24-stderr.txt
+++ b/Tests/RunCMake/cmake_minimum_required/CompatBefore24-stderr.txt
@@ -1,9 +1,9 @@
-^CMake Deprecation Warning at CompatBefore24.cmake:1 \(cmake_minimum_required\):
-  Compatibility with CMake < 2.8.12 will be removed from a future version of
-  CMake.
+^CMake Deprecation Warning at CompatBefore24\.cmake:1 \(cmake_minimum_required\):
+  Compatibility with CMake < 3\.5 will be removed from a future version of
+  CMake\.
 
-  Update the VERSION argument <min> value or use a ...<max> suffix to tell
-  CMake that the project does not need compatibility with older versions.
+  Update the VERSION argument <min> value or use a \.\.\.<max> suffix to tell
+  CMake that the project does not need compatibility with older versions\.
 Call Stack \(most recent call first\):
   CMakeLists.txt:3 \(include\)
 +
diff --git a/Tests/RunCMake/cmake_minimum_required/RunCMakeTest.cmake b/Tests/RunCMake/cmake_minimum_required/RunCMakeTest.cmake
index 3a959bb..36c44cb 100644
--- a/Tests/RunCMake/cmake_minimum_required/RunCMakeTest.cmake
+++ b/Tests/RunCMake/cmake_minimum_required/RunCMakeTest.cmake
@@ -4,7 +4,7 @@
 run_cmake(CompatBefore24)
 run_cmake(Future)
 run_cmake(PolicyBefore24)
-run_cmake(Before2812)
+run_cmake(Before3_5)
 run_cmake(Range)
 run_cmake(RangeBad)
 run_cmake(Unknown)
diff --git a/Tests/RunCMake/cmake_parse_arguments/CMakeLists.txt b/Tests/RunCMake/cmake_parse_arguments/CMakeLists.txt
index 6dd8cdf..93ee9df 100644
--- a/Tests/RunCMake/cmake_parse_arguments/CMakeLists.txt
+++ b/Tests/RunCMake/cmake_parse_arguments/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.4)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/cmake_path/call-cmake_path.cmake b/Tests/RunCMake/cmake_path/call-cmake_path.cmake
index 70fd6f5..655115f 100644
--- a/Tests/RunCMake/cmake_path/call-cmake_path.cmake
+++ b/Tests/RunCMake/cmake_path/call-cmake_path.cmake
@@ -1,6 +1,3 @@
-
-cmake_minimum_required(VERSION 3.18...3.19)
-
 # define input variable
 set (path "")
 
diff --git a/Tests/RunCMake/configure_file/CMakeLists.txt b/Tests/RunCMake/configure_file/CMakeLists.txt
index 2897109..93ee9df 100644
--- a/Tests/RunCMake/configure_file/CMakeLists.txt
+++ b/Tests/RunCMake/configure_file/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/continue/CMakeLists.txt b/Tests/RunCMake/continue/CMakeLists.txt
index ef2163c..93ee9df 100644
--- a/Tests/RunCMake/continue/CMakeLists.txt
+++ b/Tests/RunCMake/continue/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/ctest_build/BuildFailure-CMP0061-OLD-stderr.txt b/Tests/RunCMake/ctest_build/BuildFailure-CMP0061-OLD-stderr.txt
index af70ac3..f5d57e9 100644
--- a/Tests/RunCMake/ctest_build/BuildFailure-CMP0061-OLD-stderr.txt
+++ b/Tests/RunCMake/ctest_build/BuildFailure-CMP0061-OLD-stderr.txt
@@ -1,2 +1,9 @@
-^(Error\(s\) when building project
+^CMake Deprecation Warning at [^
+]*/Tests/RunCMake/ctest_build/BuildFailure-CMP0061-OLD/test\.cmake:[0-9]+ \(cmake_policy\):
+  Compatibility with CMake < 3\.5 will be removed from a future version of
+  CMake.
+
+  Update the VERSION argument <min> value or use a \.\.\.<max> suffix to tell
+  CMake that the project does not need compatibility with older versions\.
++(Error\(s\) when building project
 )?ctest_build returned zero$
diff --git a/Tests/RunCMake/ctest_build/CMakeLists.txt.in b/Tests/RunCMake/ctest_build/CMakeLists.txt.in
index 4a067fa..0a59940 100644
--- a/Tests/RunCMake/ctest_build/CMakeLists.txt.in
+++ b/Tests/RunCMake/ctest_build/CMakeLists.txt.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 @CASE_CMAKELISTS_PREFIX_CODE@
 project(CTestBuild@CASE_NAME@ @LANG@)
 include(CTest)
diff --git a/Tests/RunCMake/ctest_build/RunCMakeTest.cmake b/Tests/RunCMake/ctest_build/RunCMakeTest.cmake
index 6f1b4b6..12525f2 100644
--- a/Tests/RunCMake/ctest_build/RunCMakeTest.cmake
+++ b/Tests/RunCMake/ctest_build/RunCMakeTest.cmake
@@ -35,7 +35,9 @@
   run_ctest(BuildFailure)
 
   if (RunCMake_GENERATOR MATCHES "Makefiles")
-    set(CASE_TEST_PREFIX_CODE "")
+    set(CASE_TEST_PREFIX_CODE [[
+cmake_policy(VERSION 3.2)
+]])
     run_ctest(BuildFailure-CMP0061-OLD)
   endif()
 endfunction()
diff --git a/Tests/RunCMake/ctest_build/test.cmake.in b/Tests/RunCMake/ctest_build/test.cmake.in
index 9f7fa13..f92568f 100644
--- a/Tests/RunCMake/ctest_build/test.cmake.in
+++ b/Tests/RunCMake/ctest_build/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 @CASE_TEST_PREFIX_CODE@
 
 set(CTEST_SITE                          "test-site")
diff --git a/Tests/RunCMake/ctest_cmake_error/test.cmake.in b/Tests/RunCMake/ctest_cmake_error/test.cmake.in
index 0648e7c..711a77f 100644
--- a/Tests/RunCMake/ctest_cmake_error/test.cmake.in
+++ b/Tests/RunCMake/ctest_cmake_error/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 set(CTEST_SITE                          "test-site")
 set(CTEST_BUILD_NAME                    "test-build-name")
diff --git a/Tests/RunCMake/ctest_configure/CMakeLists.txt.in b/Tests/RunCMake/ctest_configure/CMakeLists.txt.in
index 2fb21d4..2860b5c 100644
--- a/Tests/RunCMake/ctest_configure/CMakeLists.txt.in
+++ b/Tests/RunCMake/ctest_configure/CMakeLists.txt.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(CTestConfigure@CASE_NAME@ NONE)
 include(CTest)
 add_test(NAME RunCMakeVersion COMMAND "${CMAKE_COMMAND}" --version)
diff --git a/Tests/RunCMake/ctest_configure/test.cmake.in b/Tests/RunCMake/ctest_configure/test.cmake.in
index 72d199a..5935809 100644
--- a/Tests/RunCMake/ctest_configure/test.cmake.in
+++ b/Tests/RunCMake/ctest_configure/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 set(CTEST_SITE                          "test-site")
 set(CTEST_BUILD_NAME                    "test-build-name")
diff --git a/Tests/RunCMake/ctest_coverage/CMakeLists.txt.in b/Tests/RunCMake/ctest_coverage/CMakeLists.txt.in
index 1babd72..da7ec1a 100644
--- a/Tests/RunCMake/ctest_coverage/CMakeLists.txt.in
+++ b/Tests/RunCMake/ctest_coverage/CMakeLists.txt.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(CTestCoverage@CASE_NAME@ NONE)
 include(CTest)
 add_test(NAME RunCMakeVersion COMMAND "${CMAKE_COMMAND}" --version)
diff --git a/Tests/RunCMake/ctest_coverage/test.cmake.in b/Tests/RunCMake/ctest_coverage/test.cmake.in
index 1788e66..f5a1223 100644
--- a/Tests/RunCMake/ctest_coverage/test.cmake.in
+++ b/Tests/RunCMake/ctest_coverage/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 set(CTEST_SITE                          "test-site")
 set(CTEST_BUILD_NAME                    "test-build-name")
diff --git a/Tests/RunCMake/ctest_memcheck/CMakeLists.txt.in b/Tests/RunCMake/ctest_memcheck/CMakeLists.txt.in
index 68a0fcb..1de338b 100644
--- a/Tests/RunCMake/ctest_memcheck/CMakeLists.txt.in
+++ b/Tests/RunCMake/ctest_memcheck/CMakeLists.txt.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(CTestTestMemcheck@CASE_NAME@ NONE)
 include(CTest)
 
diff --git a/Tests/RunCMake/ctest_memcheck/test.cmake.in b/Tests/RunCMake/ctest_memcheck/test.cmake.in
index eedf080..af995fc 100644
--- a/Tests/RunCMake/ctest_memcheck/test.cmake.in
+++ b/Tests/RunCMake/ctest_memcheck/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 
 # Settings:
 set(CTEST_SITE                          "@SITE@")
diff --git a/Tests/RunCMake/ctest_memcheck/testAddressLeakSanitizer.cmake b/Tests/RunCMake/ctest_memcheck/testAddressLeakSanitizer.cmake
index 58c94d7..2b49bbb 100644
--- a/Tests/RunCMake/ctest_memcheck/testAddressLeakSanitizer.cmake
+++ b/Tests/RunCMake/ctest_memcheck/testAddressLeakSanitizer.cmake
@@ -23,7 +23,7 @@
 Direct leak of 4360 byte(s) in 1 object(s) allocated from:
     #0 0x46c669 in operator new[](unsigned long) (/home/kitware/msan/a.out+0x46c669)
     #1 0x4823b4 in main /home/kitware/msan/memcheck.cxx:12
-    #2 0x7fa72bee476c in __libc_start_main /build/buildd/eglibc-2.15/csu/libc-start.c:226
+    #2 0x7fa72bee476c in __libc_start_main /build/eglibc-2.15/csu/libc-start.c:226
 
 SUMMARY: AddressSanitizer: 4436 byte(s) leaked in 2 allocation(s).
 ")
@@ -35,13 +35,13 @@
     #0 0x46c669 in operator new[](unsigned long) (/home/kitware/msan/a.out+0x46c669)
     #1 0x4821b8 in foo() /home/kitware/msan/memcheck.cxx:4
     #2 0x4823f2 in main /home/kitware/msan/memcheck.cxx:14
-    #3 0x7fa72bee476c in __libc_start_main /build/buildd/eglibc-2.15/csu/libc-start.c:226
+    #3 0x7fa72bee476c in __libc_start_main /build/eglibc-2.15/csu/libc-start.c:226
 
 Indirect leak of 76 byte(s) in 1 object(s) allocated from:
     #0 0x46c669 in operator new[](unsigned long) (/home/kitware/msan/a.out+0x46c669)
     #1 0x4821b8 in foo() /home/kitware/msan/memcheck.cxx:4
     #2 0x4823f2 in main /home/kitware/msan/memcheck.cxx:14
-    #3 0x7fa72bee476c in __libc_start_main /build/buildd/eglibc-2.15/csu/libc-start.c:226
+    #3 0x7fa72bee476c in __libc_start_main /build/eglibc-2.15/csu/libc-start.c:226
 
 SUMMARY: AddressSanitizer: 4436 byte(s) leaked in 2 allocation(s).
 ")
diff --git a/Tests/RunCMake/ctest_memcheck/testAddressSanitizer.cmake b/Tests/RunCMake/ctest_memcheck/testAddressSanitizer.cmake
index 8f18cd0..6612375 100644
--- a/Tests/RunCMake/ctest_memcheck/testAddressSanitizer.cmake
+++ b/Tests/RunCMake/ctest_memcheck/testAddressSanitizer.cmake
@@ -42,7 +42,7 @@
   Addressable:           00
   Partially addressable: 01 02 03 04 05 06 07
   Heap left redzone:     fa
-  Heap righ redzone:     fb
+  Heap right redzone:    fb
   Freed Heap region:     fd
   Stack left redzone:    f1
   Stack mid redzone:     f2
diff --git a/Tests/RunCMake/ctest_memcheck/testLeakSanitizer.cmake b/Tests/RunCMake/ctest_memcheck/testLeakSanitizer.cmake
index 4990792..45f3c45 100644
--- a/Tests/RunCMake/ctest_memcheck/testLeakSanitizer.cmake
+++ b/Tests/RunCMake/ctest_memcheck/testLeakSanitizer.cmake
@@ -23,7 +23,7 @@
 Direct leak of 4360 byte(s) in 1 object(s) allocated from:
     #0 0x46c669 in operator new[](unsigned long) (/home/kitware/msan/a.out+0x46c669)
     #1 0x4823b4 in main /home/kitware/msan/memcheck.cxx:12
-    #2 0x7fa72bee476c in __libc_start_main /build/buildd/eglibc-2.15/csu/libc-start.c:226
+    #2 0x7fa72bee476c in __libc_start_main /build/eglibc-2.15/csu/libc-start.c:226
 
 SUMMARY: LeakSanitizer: 4436 byte(s) leaked in 2 allocation(s).
 ")
@@ -35,13 +35,13 @@
     #0 0x46c669 in operator new[](unsigned long) (/home/kitware/msan/a.out+0x46c669)
     #1 0x4821b8 in foo() /home/kitware/msan/memcheck.cxx:4
     #2 0x4823f2 in main /home/kitware/msan/memcheck.cxx:14
-    #3 0x7fa72bee476c in __libc_start_main /build/buildd/eglibc-2.15/csu/libc-start.c:226
+    #3 0x7fa72bee476c in __libc_start_main /build/eglibc-2.15/csu/libc-start.c:226
 
 Indirect leak of 76 byte(s) in 1 object(s) allocated from:
     #0 0x46c669 in operator new[](unsigned long) (/home/kitware/msan/a.out+0x46c669)
     #1 0x4821b8 in foo() /home/kitware/msan/memcheck.cxx:4
     #2 0x4823f2 in main /home/kitware/msan/memcheck.cxx:14
-    #3 0x7fa72bee476c in __libc_start_main /build/buildd/eglibc-2.15/csu/libc-start.c:226
+    #3 0x7fa72bee476c in __libc_start_main /build/eglibc-2.15/csu/libc-start.c:226
 
 SUMMARY: LeakSanitizer: 4436 byte(s) leaked in 2 allocation(s).
 ")
diff --git a/Tests/RunCMake/ctest_memcheck/testMemorySanitizer.cmake b/Tests/RunCMake/ctest_memcheck/testMemorySanitizer.cmake
index 4a6adb1..4b5ef7e 100644
--- a/Tests/RunCMake/ctest_memcheck/testMemorySanitizer.cmake
+++ b/Tests/RunCMake/ctest_memcheck/testMemorySanitizer.cmake
@@ -19,7 +19,7 @@
 "=================================================================
 ==28423== WARNING: MemorySanitizer: use-of-uninitialized-value
     #0 0x7f4364210dd9 in main (/home/kitware/msan/msan-bin/umr+0x7bdd9)
-    #1 0x7f4362d9376c in __libc_start_main /build/buildd/eglibc-2.15/csu/libc-start.c:226
+    #1 0x7f4362d9376c in __libc_start_main /build/eglibc-2.15/csu/libc-start.c:226
     #2 0x7f4364210b0c in _start (/home/kitware/msan/msan-bin/umr+0x7bb0c)
 
 SUMMARY: MemorySanitizer: use-of-uninitialized-value ??:0 main
diff --git a/Tests/RunCMake/ctest_start/CMakeLists.txt.in b/Tests/RunCMake/ctest_start/CMakeLists.txt.in
index 913239c..e497a7d 100644
--- a/Tests/RunCMake/ctest_start/CMakeLists.txt.in
+++ b/Tests/RunCMake/ctest_start/CMakeLists.txt.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(CTestStart@CASE_NAME@ NONE)
 include(CTest)
 add_test(NAME RunCMakeVersion COMMAND "${CMAKE_COMMAND}" --version)
diff --git a/Tests/RunCMake/ctest_start/test.cmake.in b/Tests/RunCMake/ctest_start/test.cmake.in
index 4063ece..82acc19 100644
--- a/Tests/RunCMake/ctest_start/test.cmake.in
+++ b/Tests/RunCMake/ctest_start/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 set(CTEST_SITE                          "test-site")
 set(CTEST_BUILD_NAME                    "test-build-name")
diff --git a/Tests/RunCMake/ctest_submit/test.cmake.in b/Tests/RunCMake/ctest_submit/test.cmake.in
index 35cd16a..0f4885f 100644
--- a/Tests/RunCMake/ctest_submit/test.cmake.in
+++ b/Tests/RunCMake/ctest_submit/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 set(CTEST_SITE                          "test-site")
 set(CTEST_BUILD_NAME                    "test-build-name")
diff --git a/Tests/RunCMake/ctest_test/CMakeLists.txt.in b/Tests/RunCMake/ctest_test/CMakeLists.txt.in
index e61b556..8eb422d 100644
--- a/Tests/RunCMake/ctest_test/CMakeLists.txt.in
+++ b/Tests/RunCMake/ctest_test/CMakeLists.txt.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(CTestTest@CASE_NAME@ NONE)
 include(CTest)
 add_test(NAME RunCMakeVersion COMMAND "${CMAKE_COMMAND}" --version)
diff --git a/Tests/RunCMake/ctest_test/test.cmake.in b/Tests/RunCMake/ctest_test/test.cmake.in
index 36b1dbd..16dde1c 100644
--- a/Tests/RunCMake/ctest_test/test.cmake.in
+++ b/Tests/RunCMake/ctest_test/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 @CASE_TEST_PREFIX_CODE@
 
 set(CTEST_SITE                          "test-site")
diff --git a/Tests/RunCMake/ctest_update/CMakeLists.txt.in b/Tests/RunCMake/ctest_update/CMakeLists.txt.in
index ecf0e54..f816fac 100644
--- a/Tests/RunCMake/ctest_update/CMakeLists.txt.in
+++ b/Tests/RunCMake/ctest_update/CMakeLists.txt.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(CTestTest@CASE_NAME@ NONE)
 include(CTest)
 @CASE_CMAKELISTS_SUFFIX_CODE@
diff --git a/Tests/RunCMake/ctest_update/test.cmake.in b/Tests/RunCMake/ctest_update/test.cmake.in
index 01aab26..faaf9b4 100644
--- a/Tests/RunCMake/ctest_update/test.cmake.in
+++ b/Tests/RunCMake/ctest_update/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 @CASE_TEST_PREFIX_CODE@
 
 set(CTEST_SITE                          "test-site")
diff --git a/Tests/RunCMake/ctest_upload/CMakeLists.txt.in b/Tests/RunCMake/ctest_upload/CMakeLists.txt.in
index 1fab71b..21e5273 100644
--- a/Tests/RunCMake/ctest_upload/CMakeLists.txt.in
+++ b/Tests/RunCMake/ctest_upload/CMakeLists.txt.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(CTestUpload@CASE_NAME@ NONE)
 include(CTest)
 add_test(NAME RunCMakeVersion COMMAND "${CMAKE_COMMAND}" --version)
diff --git a/Tests/RunCMake/ctest_upload/test.cmake.in b/Tests/RunCMake/ctest_upload/test.cmake.in
index f13bdd1..a546d38 100644
--- a/Tests/RunCMake/ctest_upload/test.cmake.in
+++ b/Tests/RunCMake/ctest_upload/test.cmake.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 
 set(CTEST_SITE                          "test-site")
 set(CTEST_BUILD_NAME                    "test-build-name")
diff --git a/Tests/RunCMake/export/CMakeLists.txt b/Tests/RunCMake/export/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/export/CMakeLists.txt
+++ b/Tests/RunCMake/export/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/RunCMakeTest.cmake b/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/RunCMakeTest.cmake
index 43b406b..a68607e 100644
--- a/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/RunCMakeTest.cmake
+++ b/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/RunCMakeTest.cmake
@@ -9,6 +9,10 @@
   run_cmake_command(${case}-build ${CMAKE_COMMAND} --build . --config Debug)
   # Check "all" components.
   set(CMAKE_INSTALL_PREFIX ${RunCMake_TEST_BINARY_DIR}/root-all)
+  set(maybe_stderr "${case}-all-stderr-${CMAKE_C_COMPILER_ID}.txt")
+  if(EXISTS "${RunCMake_SOURCE_DIR}/${maybe_stderr}")
+    set(RunCMake-stderr-file "${maybe_stderr}")
+  endif()
   run_cmake_command(${case}-all ${CMAKE_COMMAND} --install . --prefix ${CMAKE_INSTALL_PREFIX} --config Debug)
 endfunction()
 
diff --git a/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-all-check.cmake b/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-all-check.cmake
index cb0e534..10b7b82 100644
--- a/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-all-check.cmake
+++ b/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-all-check.cmake
@@ -1,19 +1,29 @@
+if(CMAKE_C_COMPILER_ID STREQUAL "Borland")
+  # Borland upper-cases dll names referenced in import libraries.
+  set(conflict_dll [[CONFLICT\.DLL]])
+  set(unresolved_dll [[UNRESOLVED\.DLL]])
+else()
+  set(conflict_dll [[conflict\.dll]])
+  set(unresolved_dll [[unresolved\.dll]])
+endif()
+
 set(_check
   [=[[^;]*/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-build/root-all/bin/\.conflict/\.\./(lib)?libdir\.dll]=]
   [=[[^;]*/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-build/root-all/bin/\.search/(lib)?search\.dll]=]
+  [=[[^;]*/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-build/root-all/bin/(lib)?MixedCase\.dll]=]
   [=[[^;]*/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-build/root-all/bin/(lib)?testlib\.dll]=]
   )
 check_contents(deps/deps1.txt "^${_check}$")
 check_contents(deps/deps2.txt "^${_check}$")
 check_contents(deps/deps3.txt "^${_check}$")
 set(_check
-  [=[(lib)?unresolved\.dll]=]
+  "(lib)?${unresolved_dll}"
   )
 check_contents(deps/udeps1.txt "^${_check}$")
 check_contents(deps/udeps2.txt "^${_check}$")
 check_contents(deps/udeps3.txt "^${_check}$")
 set(_check
-  "^(lib)?conflict\\.dll:[^;]*/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-build/root-all/bin/\\.conflict/(lib)?conflict\\.dll;[^;]*/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-build/root-all/bin/(lib)?conflict\\.dll\n$"
+  "^(lib)?${conflict_dll}:[^;]*/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-build/root-all/bin/\\.conflict/(lib)?conflict\\.dll;[^;]*/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-build/root-all/bin/(lib)?conflict\\.dll\n$"
   )
 check_contents(deps/cdeps1.txt "${_check}")
 check_contents(deps/cdeps2.txt "${_check}")
diff --git a/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-conflict-all-stderr-Borland.txt b/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-conflict-all-stderr-Borland.txt
new file mode 100644
index 0000000..607e4b8
--- /dev/null
+++ b/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-conflict-all-stderr-Borland.txt
@@ -0,0 +1,7 @@
+^CMake Error at cmake_install\.cmake:[0-9]+ \(file\):
+  file Multiple conflicting paths found for PATH\.DLL:
+
+    [^
+]*/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-conflict-build/root-all/lib/test1/path\.dll
+    [^
+]*/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-conflict-build/root-all/lib/test2/path\.dll$
diff --git a/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-unresolved-all-stderr-Borland.txt b/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-unresolved-all-stderr-Borland.txt
new file mode 100644
index 0000000..fea1083
--- /dev/null
+++ b/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-unresolved-all-stderr-Borland.txt
@@ -0,0 +1,4 @@
+^CMake Error at cmake_install\.cmake:[0-9]+ \(file\):
+  file Could not resolve runtime dependencies:
+
+    UNRESOLVED\.DLL$
diff --git a/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows.cmake b/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows.cmake
index 9160ce5..aad9077 100644
--- a/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows.cmake
+++ b/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows.cmake
@@ -8,6 +8,7 @@
   search
   unresolved
   conflict
+  MixedCase
   )
 
 file(REMOVE "${CMAKE_BINARY_DIR}/testlib.c")
@@ -34,7 +35,7 @@
 add_library(testlib_noconflict SHARED "${CMAKE_BINARY_DIR}/testlib_noconflict.c")
 target_link_libraries(testlib_noconflict PRIVATE libdir)
 
-install(TARGETS testlib libdir_postexcluded libdir conflict testlib_noconflict DESTINATION bin)
+install(TARGETS testlib libdir_postexcluded libdir conflict MixedCase testlib_noconflict DESTINATION bin)
 install(TARGETS libdir search_postexcluded search DESTINATION bin/.search) # Prefixing with "." ensures it is the first item after list(SORT)
 install(TARGETS testlib_conflict conflict DESTINATION bin/.conflict)
 
@@ -61,6 +62,7 @@
         "^(lib)?search\\.dll$"
         "^(lib)?unresolved\\.dll$"
         "^(lib)?conflict\\.dll$"
+        "^(lib)?mixedcase\\.dll$"
         "^kernel32\\.dll$"
       PRE_EXCLUDE_REGEXES ".*"
       POST_INCLUDE_REGEXES
@@ -68,6 +70,7 @@
         "^.*/(lib)?libdir\\.dll$"
         "^.*/(lib)?search\\.dll$"
         "^.*/(lib)?conflict\\.dll$"
+        "^.*/(lib)?mixedcase\\.dll$"
       POST_EXCLUDE_REGEXES ".*"
       DIRECTORIES
         "${CMAKE_INSTALL_PREFIX}/bin/.search"
diff --git a/Tests/RunCMake/file/CMakeLists.txt b/Tests/RunCMake/file/CMakeLists.txt
index 2897109..93ee9df 100644
--- a/Tests/RunCMake/file/CMakeLists.txt
+++ b/Tests/RunCMake/file/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/file/COPY_FILE-file-replace.cmake b/Tests/RunCMake/file/COPY_FILE-file-replace.cmake
index 40e4290..cdb06fa 100644
--- a/Tests/RunCMake/file/COPY_FILE-file-replace.cmake
+++ b/Tests/RunCMake/file/COPY_FILE-file-replace.cmake
@@ -5,5 +5,5 @@
 file(COPY_FILE "${oldname}" "${newname}")
 file(READ "${newname}" new)
 if(NOT "${new}" STREQUAL "a")
-  message(FATAL_ERROR "New name:\n  ${newname}\ndoes not contain expected content 'a'.")
+  message(FATAL_ERROR "New name:\n  ${newname}\n" "does not contain expected content 'a'.")
 endif()
diff --git a/Tests/RunCMake/file/COPY_FILE-link-to-file.cmake b/Tests/RunCMake/file/COPY_FILE-link-to-file.cmake
index 93a0204..53a6b11 100644
--- a/Tests/RunCMake/file/COPY_FILE-link-to-file.cmake
+++ b/Tests/RunCMake/file/COPY_FILE-link-to-file.cmake
@@ -6,5 +6,5 @@
 file(COPY_FILE "${oldname}" "${newname}")
 file(READ "${newname}" new)
 if(NOT "${new}" STREQUAL "a")
-  message(FATAL_ERROR "New name:\n  ${newname}\ndoes not contain expected content 'a'.")
+  message(FATAL_ERROR "New name:\n  ${newname}\n" "does not contain expected content 'a'.")
 endif()
diff --git a/Tests/RunCMake/file/LOCK-error-guard-incorrect-stderr.txt b/Tests/RunCMake/file/LOCK-error-guard-incorrect-stderr.txt
index 85136b4..815ab5b 100644
--- a/Tests/RunCMake/file/LOCK-error-guard-incorrect-stderr.txt
+++ b/Tests/RunCMake/file/LOCK-error-guard-incorrect-stderr.txt
@@ -1,6 +1,6 @@
 CMake Error at LOCK-error-guard-incorrect\.cmake:[0-9]+ \(file\):
   expected FUNCTION, FILE or PROCESS after GUARD, but got:
 
-    "FUNCTIO"\.
+    "FUNCTIO_"\.
 Call Stack \(most recent call first\):
   CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/file/LOCK-error-guard-incorrect.cmake b/Tests/RunCMake/file/LOCK-error-guard-incorrect.cmake
index 51daa7c..dddd4c0 100644
--- a/Tests/RunCMake/file/LOCK-error-guard-incorrect.cmake
+++ b/Tests/RunCMake/file/LOCK-error-guard-incorrect.cmake
@@ -1 +1 @@
-file(LOCK "${CMAKE_CURRENT_BINARY_DIR}/file-to-lock" GUARD FUNCTIO)
+file(LOCK "${CMAKE_CURRENT_BINARY_DIR}/file-to-lock" GUARD FUNCTIO_)
diff --git a/Tests/RunCMake/file/REAL_PATH.cmake b/Tests/RunCMake/file/REAL_PATH.cmake
index 0b5d3c0..9c5d4ea 100644
--- a/Tests/RunCMake/file/REAL_PATH.cmake
+++ b/Tests/RunCMake/file/REAL_PATH.cmake
@@ -34,3 +34,19 @@
 if (NOT real_path STREQUAL "${HOME_DIR}/test.txt")
   message(SEND_ERROR "real path is \"${real_path}\", should be \"${HOME_DIR}/test.txt\"")
 endif()
+
+if (WIN32)
+  cmake_policy(SET CMP0139 NEW)
+
+  set(in "${CMAKE_CURRENT_BINARY_DIR}/AbC.TxT")
+
+  file(REMOVE "${in}")
+  file(TOUCH "${in}")
+
+  string(TOLOWER "${in}" low)
+  file(REAL_PATH "${low}" out)
+
+  if(NOT "${out}" PATH_EQUAL "${in}")
+    message(SEND_ERROR "real path is \"${out}\", should be \"${in}\"")
+  endif()
+endif()
diff --git a/Tests/RunCMake/file/RENAME-file-replace.cmake b/Tests/RunCMake/file/RENAME-file-replace.cmake
index efbfaed..454e27e 100644
--- a/Tests/RunCMake/file/RENAME-file-replace.cmake
+++ b/Tests/RunCMake/file/RENAME-file-replace.cmake
@@ -5,5 +5,5 @@
 file(RENAME "${oldname}" "${newname}")
 file(READ "${newname}" new)
 if(NOT "${new}" STREQUAL "a")
-  message(FATAL_ERROR "New name:\n  ${newname}\ndoes not contain expected content 'a'.")
+  message(FATAL_ERROR "New name:\n  ${newname}\n" "does not contain expected content 'a'.")
 endif()
diff --git a/Tests/RunCMake/find_dependency/CMakeLists.txt b/Tests/RunCMake/find_dependency/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/find_dependency/CMakeLists.txt
+++ b/Tests/RunCMake/find_dependency/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/find_file/CMakeLists.txt b/Tests/RunCMake/find_file/CMakeLists.txt
index ef2163c..93ee9df 100644
--- a/Tests/RunCMake/find_file/CMakeLists.txt
+++ b/Tests/RunCMake/find_file/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/find_library/CMakeLists.txt b/Tests/RunCMake/find_library/CMakeLists.txt
index ef2163c..93ee9df 100644
--- a/Tests/RunCMake/find_library/CMakeLists.txt
+++ b/Tests/RunCMake/find_library/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/find_package/CMP0144-NEW-CaseInsensitive-stderr.txt b/Tests/RunCMake/find_package/CMP0144-NEW-CaseInsensitive-stderr.txt
new file mode 100644
index 0000000..3ccd101
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0144-NEW-CaseInsensitive-stderr.txt
@@ -0,0 +1,45 @@
+^----------
+FOO_ROOT     : ''
+ENV{FOO_ROOT}: ''
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: FOO_TEST_FILE_FOO-NOTFOUND
+FOO_TEST_FILE_ZOT: FOO_TEST_FILE_ZOT-NOTFOUND
+FOO_TEST_PATH_FOO: FOO_TEST_PATH_FOO-NOTFOUND
+FOO_TEST_PATH_ZOT: FOO_TEST_PATH_ZOT-NOTFOUND
+FOO_TEST_PROG_FOO: FOO_TEST_PROG_FOO-NOTFOUND
+
+----------
+FOO_ROOT     : '<base>/foo/cmake_root'
+ENV{FOO_ROOT}: '<base>/foo/env_root'
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: <base>/foo/cmake_root/include/foo.h
+FOO_TEST_FILE_ZOT: <base>/foo/cmake_root/include/zot/zot.h
+FOO_TEST_PATH_FOO: <base>/foo/cmake_root/include
+FOO_TEST_PATH_ZOT: <base>/foo/cmake_root/include/zot
+FOO_TEST_PROG_FOO: <base>/foo/cmake_root/bin/foo.exe
+
+----------
+FOO_ROOT     : '<base>/foo/cmake_root'
+ENV{FOO_ROOT}: ''
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: <base>/foo/cmake_root/include/foo.h
+FOO_TEST_FILE_ZOT: <base>/foo/cmake_root/include/zot/zot.h
+FOO_TEST_PATH_FOO: <base>/foo/cmake_root/include
+FOO_TEST_PATH_ZOT: <base>/foo/cmake_root/include/zot
+FOO_TEST_PROG_FOO: <base>/foo/cmake_root/bin/foo.exe
+
+----------
+FOO_ROOT     : ''
+ENV{FOO_ROOT}: '<base>/foo/env_root'
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: <base>/foo/env_root/include/foo.h
+FOO_TEST_FILE_ZOT: <base>/foo/env_root/include/zot/zot.h
+FOO_TEST_PATH_FOO: <base>/foo/env_root/include
+FOO_TEST_PATH_ZOT: <base>/foo/env_root/include/zot
+FOO_TEST_PROG_FOO: <base>/foo/env_root/bin/foo.exe
+
+----------$
diff --git a/Tests/RunCMake/find_package/CMP0144-NEW-CaseInsensitive.cmake b/Tests/RunCMake/find_package/CMP0144-NEW-CaseInsensitive.cmake
new file mode 100644
index 0000000..f0853ee
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0144-NEW-CaseInsensitive.cmake
@@ -0,0 +1,3 @@
+cmake_policy(VERSION 3.26)
+cmake_policy(SET CMP0144 NEW)
+include(CMP0144-common.cmake)
diff --git a/Tests/RunCMake/find_package/CMP0144-NEW-CaseSensitive-stderr.txt b/Tests/RunCMake/find_package/CMP0144-NEW-CaseSensitive-stderr.txt
new file mode 100644
index 0000000..3ccd101
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0144-NEW-CaseSensitive-stderr.txt
@@ -0,0 +1,45 @@
+^----------
+FOO_ROOT     : ''
+ENV{FOO_ROOT}: ''
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: FOO_TEST_FILE_FOO-NOTFOUND
+FOO_TEST_FILE_ZOT: FOO_TEST_FILE_ZOT-NOTFOUND
+FOO_TEST_PATH_FOO: FOO_TEST_PATH_FOO-NOTFOUND
+FOO_TEST_PATH_ZOT: FOO_TEST_PATH_ZOT-NOTFOUND
+FOO_TEST_PROG_FOO: FOO_TEST_PROG_FOO-NOTFOUND
+
+----------
+FOO_ROOT     : '<base>/foo/cmake_root'
+ENV{FOO_ROOT}: '<base>/foo/env_root'
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: <base>/foo/cmake_root/include/foo.h
+FOO_TEST_FILE_ZOT: <base>/foo/cmake_root/include/zot/zot.h
+FOO_TEST_PATH_FOO: <base>/foo/cmake_root/include
+FOO_TEST_PATH_ZOT: <base>/foo/cmake_root/include/zot
+FOO_TEST_PROG_FOO: <base>/foo/cmake_root/bin/foo.exe
+
+----------
+FOO_ROOT     : '<base>/foo/cmake_root'
+ENV{FOO_ROOT}: ''
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: <base>/foo/cmake_root/include/foo.h
+FOO_TEST_FILE_ZOT: <base>/foo/cmake_root/include/zot/zot.h
+FOO_TEST_PATH_FOO: <base>/foo/cmake_root/include
+FOO_TEST_PATH_ZOT: <base>/foo/cmake_root/include/zot
+FOO_TEST_PROG_FOO: <base>/foo/cmake_root/bin/foo.exe
+
+----------
+FOO_ROOT     : ''
+ENV{FOO_ROOT}: '<base>/foo/env_root'
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: <base>/foo/env_root/include/foo.h
+FOO_TEST_FILE_ZOT: <base>/foo/env_root/include/zot/zot.h
+FOO_TEST_PATH_FOO: <base>/foo/env_root/include
+FOO_TEST_PATH_ZOT: <base>/foo/env_root/include/zot
+FOO_TEST_PROG_FOO: <base>/foo/env_root/bin/foo.exe
+
+----------$
diff --git a/Tests/RunCMake/find_package/CMP0144-NEW-CaseSensitive.cmake b/Tests/RunCMake/find_package/CMP0144-NEW-CaseSensitive.cmake
new file mode 100644
index 0000000..f0853ee
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0144-NEW-CaseSensitive.cmake
@@ -0,0 +1,3 @@
+cmake_policy(VERSION 3.26)
+cmake_policy(SET CMP0144 NEW)
+include(CMP0144-common.cmake)
diff --git a/Tests/RunCMake/find_package/CMP0144-OLD-CaseInsensitive-stderr.txt b/Tests/RunCMake/find_package/CMP0144-OLD-CaseInsensitive-stderr.txt
new file mode 100644
index 0000000..3274b82
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0144-OLD-CaseInsensitive-stderr.txt
@@ -0,0 +1,45 @@
+^----------
+FOO_ROOT     : ''
+ENV{FOO_ROOT}: ''
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: FOO_TEST_FILE_FOO-NOTFOUND
+FOO_TEST_FILE_ZOT: FOO_TEST_FILE_ZOT-NOTFOUND
+FOO_TEST_PATH_FOO: FOO_TEST_PATH_FOO-NOTFOUND
+FOO_TEST_PATH_ZOT: FOO_TEST_PATH_ZOT-NOTFOUND
+FOO_TEST_PROG_FOO: FOO_TEST_PROG_FOO-NOTFOUND
+
+----------
+FOO_ROOT     : '<base>/foo/cmake_root'
+ENV{FOO_ROOT}: '<base>/foo/env_root'
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: <base>/foo/env_root/include/foo.h
+FOO_TEST_FILE_ZOT: <base>/foo/env_root/include/zot/zot.h
+FOO_TEST_PATH_FOO: <base>/foo/env_root/include
+FOO_TEST_PATH_ZOT: <base>/foo/env_root/include/zot
+FOO_TEST_PROG_FOO: <base>/foo/env_root/bin/foo.exe
+
+----------
+FOO_ROOT     : '<base>/foo/cmake_root'
+ENV{FOO_ROOT}: ''
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: FOO_TEST_FILE_FOO-NOTFOUND
+FOO_TEST_FILE_ZOT: FOO_TEST_FILE_ZOT-NOTFOUND
+FOO_TEST_PATH_FOO: FOO_TEST_PATH_FOO-NOTFOUND
+FOO_TEST_PATH_ZOT: FOO_TEST_PATH_ZOT-NOTFOUND
+FOO_TEST_PROG_FOO: FOO_TEST_PROG_FOO-NOTFOUND
+
+----------
+FOO_ROOT     : ''
+ENV{FOO_ROOT}: '<base>/foo/env_root'
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: <base>/foo/env_root/include/foo.h
+FOO_TEST_FILE_ZOT: <base>/foo/env_root/include/zot/zot.h
+FOO_TEST_PATH_FOO: <base>/foo/env_root/include
+FOO_TEST_PATH_ZOT: <base>/foo/env_root/include/zot
+FOO_TEST_PROG_FOO: <base>/foo/env_root/bin/foo.exe
+
+----------$
diff --git a/Tests/RunCMake/find_package/CMP0144-OLD-CaseInsensitive.cmake b/Tests/RunCMake/find_package/CMP0144-OLD-CaseInsensitive.cmake
new file mode 100644
index 0000000..f1560a9
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0144-OLD-CaseInsensitive.cmake
@@ -0,0 +1,3 @@
+cmake_policy(VERSION 3.26)
+cmake_policy(SET CMP0144 OLD)
+include(CMP0144-common.cmake)
diff --git a/Tests/RunCMake/find_package/CMP0144-OLD-CaseSensitive-stderr.txt b/Tests/RunCMake/find_package/CMP0144-OLD-CaseSensitive-stderr.txt
new file mode 100644
index 0000000..c02a797
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0144-OLD-CaseSensitive-stderr.txt
@@ -0,0 +1,45 @@
+^----------
+FOO_ROOT     : ''
+ENV{FOO_ROOT}: ''
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: FOO_TEST_FILE_FOO-NOTFOUND
+FOO_TEST_FILE_ZOT: FOO_TEST_FILE_ZOT-NOTFOUND
+FOO_TEST_PATH_FOO: FOO_TEST_PATH_FOO-NOTFOUND
+FOO_TEST_PATH_ZOT: FOO_TEST_PATH_ZOT-NOTFOUND
+FOO_TEST_PROG_FOO: FOO_TEST_PROG_FOO-NOTFOUND
+
+----------
+FOO_ROOT     : '<base>/foo/cmake_root'
+ENV{FOO_ROOT}: '<base>/foo/env_root'
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: FOO_TEST_FILE_FOO-NOTFOUND
+FOO_TEST_FILE_ZOT: FOO_TEST_FILE_ZOT-NOTFOUND
+FOO_TEST_PATH_FOO: FOO_TEST_PATH_FOO-NOTFOUND
+FOO_TEST_PATH_ZOT: FOO_TEST_PATH_ZOT-NOTFOUND
+FOO_TEST_PROG_FOO: FOO_TEST_PROG_FOO-NOTFOUND
+
+----------
+FOO_ROOT     : '<base>/foo/cmake_root'
+ENV{FOO_ROOT}: ''
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: FOO_TEST_FILE_FOO-NOTFOUND
+FOO_TEST_FILE_ZOT: FOO_TEST_FILE_ZOT-NOTFOUND
+FOO_TEST_PATH_FOO: FOO_TEST_PATH_FOO-NOTFOUND
+FOO_TEST_PATH_ZOT: FOO_TEST_PATH_ZOT-NOTFOUND
+FOO_TEST_PROG_FOO: FOO_TEST_PROG_FOO-NOTFOUND
+
+----------
+FOO_ROOT     : ''
+ENV{FOO_ROOT}: '<base>/foo/env_root'
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: FOO_TEST_FILE_FOO-NOTFOUND
+FOO_TEST_FILE_ZOT: FOO_TEST_FILE_ZOT-NOTFOUND
+FOO_TEST_PATH_FOO: FOO_TEST_PATH_FOO-NOTFOUND
+FOO_TEST_PATH_ZOT: FOO_TEST_PATH_ZOT-NOTFOUND
+FOO_TEST_PROG_FOO: FOO_TEST_PROG_FOO-NOTFOUND
+
+----------$
diff --git a/Tests/RunCMake/find_package/CMP0144-OLD-CaseSensitive.cmake b/Tests/RunCMake/find_package/CMP0144-OLD-CaseSensitive.cmake
new file mode 100644
index 0000000..f1560a9
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0144-OLD-CaseSensitive.cmake
@@ -0,0 +1,3 @@
+cmake_policy(VERSION 3.26)
+cmake_policy(SET CMP0144 OLD)
+include(CMP0144-common.cmake)
diff --git a/Tests/RunCMake/find_package/CMP0144-WARN-CaseInsensitive-stderr.txt b/Tests/RunCMake/find_package/CMP0144-WARN-CaseInsensitive-stderr.txt
new file mode 100644
index 0000000..3540dc9
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0144-WARN-CaseInsensitive-stderr.txt
@@ -0,0 +1,63 @@
+^----------
+FOO_ROOT     : ''
+ENV{FOO_ROOT}: ''
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: FOO_TEST_FILE_FOO-NOTFOUND
+FOO_TEST_FILE_ZOT: FOO_TEST_FILE_ZOT-NOTFOUND
+FOO_TEST_PATH_FOO: FOO_TEST_PATH_FOO-NOTFOUND
+FOO_TEST_PATH_ZOT: FOO_TEST_PATH_ZOT-NOTFOUND
+FOO_TEST_PROG_FOO: FOO_TEST_PROG_FOO-NOTFOUND
+
+----------
+FOO_ROOT     : '<base>/foo/cmake_root'
+ENV{FOO_ROOT}: '<base>/foo/env_root'
++
+CMake Warning \(dev\) at CMP0144-common.cmake:[0-9]+ \(find_package\):
+  Policy CMP0144 is not set: find_package uses upper-case <PACKAGENAME>_ROOT
+  variables.  Run "cmake --help-policy CMP0144" for policy details\.  Use the
+  cmake_policy command to set the policy and suppress this warning\.
+
+  CMake variable FOO_ROOT is set to:
+
+    [^
+]*/Tests/RunCMake/find_package/PackageRoot/foo/cmake_root
+
+  For compatibility, find_package is ignoring the variable, but code in a
+  \.cmake module might still use it\.
+Call Stack \(most recent call first\):
+  CMP0144-common.cmake:[0-9]+ \(RunTestCase\)
+  CMP0144-WARN-CaseInsensitive.cmake:[0-9]+ \(include\)
+  CMakeLists.txt:[0-9]+ \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.
++
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: <base>/foo/env_root/include/foo.h
+FOO_TEST_FILE_ZOT: <base>/foo/env_root/include/zot/zot.h
+FOO_TEST_PATH_FOO: <base>/foo/env_root/include
+FOO_TEST_PATH_ZOT: <base>/foo/env_root/include/zot
+FOO_TEST_PROG_FOO: <base>/foo/env_root/bin/foo.exe
+
+----------
+FOO_ROOT     : '<base>/foo/cmake_root'
+ENV{FOO_ROOT}: ''
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: FOO_TEST_FILE_FOO-NOTFOUND
+FOO_TEST_FILE_ZOT: FOO_TEST_FILE_ZOT-NOTFOUND
+FOO_TEST_PATH_FOO: FOO_TEST_PATH_FOO-NOTFOUND
+FOO_TEST_PATH_ZOT: FOO_TEST_PATH_ZOT-NOTFOUND
+FOO_TEST_PROG_FOO: FOO_TEST_PROG_FOO-NOTFOUND
+
+----------
+FOO_ROOT     : ''
+ENV{FOO_ROOT}: '<base>/foo/env_root'
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: <base>/foo/env_root/include/foo.h
+FOO_TEST_FILE_ZOT: <base>/foo/env_root/include/zot/zot.h
+FOO_TEST_PATH_FOO: <base>/foo/env_root/include
+FOO_TEST_PATH_ZOT: <base>/foo/env_root/include/zot
+FOO_TEST_PROG_FOO: <base>/foo/env_root/bin/foo.exe
+
+----------$
diff --git a/Tests/RunCMake/find_package/CMP0144-WARN-CaseInsensitive.cmake b/Tests/RunCMake/find_package/CMP0144-WARN-CaseInsensitive.cmake
new file mode 100644
index 0000000..10f6b66
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0144-WARN-CaseInsensitive.cmake
@@ -0,0 +1,3 @@
+cmake_policy(VERSION 3.26)
+# (do not set CMP0144)
+include(CMP0144-common.cmake)
diff --git a/Tests/RunCMake/find_package/CMP0144-WARN-CaseSensitive-Mixed-stderr.txt b/Tests/RunCMake/find_package/CMP0144-WARN-CaseSensitive-Mixed-stderr.txt
new file mode 100644
index 0000000..3ccd101
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0144-WARN-CaseSensitive-Mixed-stderr.txt
@@ -0,0 +1,45 @@
+^----------
+FOO_ROOT     : ''
+ENV{FOO_ROOT}: ''
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: FOO_TEST_FILE_FOO-NOTFOUND
+FOO_TEST_FILE_ZOT: FOO_TEST_FILE_ZOT-NOTFOUND
+FOO_TEST_PATH_FOO: FOO_TEST_PATH_FOO-NOTFOUND
+FOO_TEST_PATH_ZOT: FOO_TEST_PATH_ZOT-NOTFOUND
+FOO_TEST_PROG_FOO: FOO_TEST_PROG_FOO-NOTFOUND
+
+----------
+FOO_ROOT     : '<base>/foo/cmake_root'
+ENV{FOO_ROOT}: '<base>/foo/env_root'
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: <base>/foo/cmake_root/include/foo.h
+FOO_TEST_FILE_ZOT: <base>/foo/cmake_root/include/zot/zot.h
+FOO_TEST_PATH_FOO: <base>/foo/cmake_root/include
+FOO_TEST_PATH_ZOT: <base>/foo/cmake_root/include/zot
+FOO_TEST_PROG_FOO: <base>/foo/cmake_root/bin/foo.exe
+
+----------
+FOO_ROOT     : '<base>/foo/cmake_root'
+ENV{FOO_ROOT}: ''
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: <base>/foo/cmake_root/include/foo.h
+FOO_TEST_FILE_ZOT: <base>/foo/cmake_root/include/zot/zot.h
+FOO_TEST_PATH_FOO: <base>/foo/cmake_root/include
+FOO_TEST_PATH_ZOT: <base>/foo/cmake_root/include/zot
+FOO_TEST_PROG_FOO: <base>/foo/cmake_root/bin/foo.exe
+
+----------
+FOO_ROOT     : ''
+ENV{FOO_ROOT}: '<base>/foo/env_root'
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: <base>/foo/env_root/include/foo.h
+FOO_TEST_FILE_ZOT: <base>/foo/env_root/include/zot/zot.h
+FOO_TEST_PATH_FOO: <base>/foo/env_root/include
+FOO_TEST_PATH_ZOT: <base>/foo/env_root/include/zot
+FOO_TEST_PROG_FOO: <base>/foo/env_root/bin/foo.exe
+
+----------$
diff --git a/Tests/RunCMake/find_package/CMP0144-WARN-CaseSensitive-Mixed.cmake b/Tests/RunCMake/find_package/CMP0144-WARN-CaseSensitive-Mixed.cmake
new file mode 100644
index 0000000..10f6b66
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0144-WARN-CaseSensitive-Mixed.cmake
@@ -0,0 +1,3 @@
+cmake_policy(VERSION 3.26)
+# (do not set CMP0144)
+include(CMP0144-common.cmake)
diff --git a/Tests/RunCMake/find_package/CMP0144-WARN-CaseSensitive-stderr.txt b/Tests/RunCMake/find_package/CMP0144-WARN-CaseSensitive-stderr.txt
new file mode 100644
index 0000000..b828f05
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0144-WARN-CaseSensitive-stderr.txt
@@ -0,0 +1,68 @@
+^----------
+FOO_ROOT     : ''
+ENV{FOO_ROOT}: ''
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: FOO_TEST_FILE_FOO-NOTFOUND
+FOO_TEST_FILE_ZOT: FOO_TEST_FILE_ZOT-NOTFOUND
+FOO_TEST_PATH_FOO: FOO_TEST_PATH_FOO-NOTFOUND
+FOO_TEST_PATH_ZOT: FOO_TEST_PATH_ZOT-NOTFOUND
+FOO_TEST_PROG_FOO: FOO_TEST_PROG_FOO-NOTFOUND
+
+----------
+FOO_ROOT     : '<base>/foo/cmake_root'
+ENV{FOO_ROOT}: '<base>/foo/env_root'
++
+CMake Warning \(dev\) at CMP0144-common.cmake:[0-9]+ \(find_package\):
+  Policy CMP0144 is not set: find_package uses upper-case <PACKAGENAME>_ROOT
+  variables.  Run "cmake --help-policy CMP0144" for policy details\.  Use the
+  cmake_policy command to set the policy and suppress this warning\.
+
+  CMake variable FOO_ROOT is set to:
+
+    [^
+]*/Tests/RunCMake/find_package/PackageRoot/foo/cmake_root
+
+  Environment variable FOO_ROOT is set to:
+
+    [^
+]*/Tests/RunCMake/find_package/PackageRoot/foo/env_root
+
+  For compatibility, find_package is ignoring the variable, but code in a
+  \.cmake module might still use it\.
+Call Stack \(most recent call first\):
+  CMP0144-common.cmake:[0-9]+ \(RunTestCase\)
+  CMP0144-WARN-CaseSensitive.cmake:[0-9]+ \(include\)
+  CMakeLists.txt:[0-9]+ \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.
++
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: FOO_TEST_FILE_FOO-NOTFOUND
+FOO_TEST_FILE_ZOT: FOO_TEST_FILE_ZOT-NOTFOUND
+FOO_TEST_PATH_FOO: FOO_TEST_PATH_FOO-NOTFOUND
+FOO_TEST_PATH_ZOT: FOO_TEST_PATH_ZOT-NOTFOUND
+FOO_TEST_PROG_FOO: FOO_TEST_PROG_FOO-NOTFOUND
+
+----------
+FOO_ROOT     : '<base>/foo/cmake_root'
+ENV{FOO_ROOT}: ''
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: FOO_TEST_FILE_FOO-NOTFOUND
+FOO_TEST_FILE_ZOT: FOO_TEST_FILE_ZOT-NOTFOUND
+FOO_TEST_PATH_FOO: FOO_TEST_PATH_FOO-NOTFOUND
+FOO_TEST_PATH_ZOT: FOO_TEST_PATH_ZOT-NOTFOUND
+FOO_TEST_PROG_FOO: FOO_TEST_PROG_FOO-NOTFOUND
+
+----------
+FOO_ROOT     : ''
+ENV{FOO_ROOT}: '<base>/foo/env_root'
+
+find_package\(Foo\)
+FOO_TEST_FILE_FOO: FOO_TEST_FILE_FOO-NOTFOUND
+FOO_TEST_FILE_ZOT: FOO_TEST_FILE_ZOT-NOTFOUND
+FOO_TEST_PATH_FOO: FOO_TEST_PATH_FOO-NOTFOUND
+FOO_TEST_PATH_ZOT: FOO_TEST_PATH_ZOT-NOTFOUND
+FOO_TEST_PROG_FOO: FOO_TEST_PROG_FOO-NOTFOUND
+
+----------$
diff --git a/Tests/RunCMake/find_package/CMP0144-WARN-CaseSensitive.cmake b/Tests/RunCMake/find_package/CMP0144-WARN-CaseSensitive.cmake
new file mode 100644
index 0000000..10f6b66
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0144-WARN-CaseSensitive.cmake
@@ -0,0 +1,3 @@
+cmake_policy(VERSION 3.26)
+# (do not set CMP0144)
+include(CMP0144-common.cmake)
diff --git a/Tests/RunCMake/find_package/CMP0144-common.cmake b/Tests/RunCMake/find_package/CMP0144-common.cmake
new file mode 100644
index 0000000..58eccca
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0144-common.cmake
@@ -0,0 +1,78 @@
+# (includer selects CMP0144)
+list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_CURRENT_SOURCE_DIR}/PackageRoot)
+set(PackageRoot_BASE ${CMAKE_CURRENT_SOURCE_DIR}/PackageRoot)
+
+function(PrintPath label path)
+  string(REPLACE "${PackageRoot_BASE}" "<base>" out "${path}")
+  message("${label} ${out}")
+endfunction()
+
+macro(RunTestCase)
+  message("----------")
+  PrintPath("FOO_ROOT     :" "'${FOO_ROOT}'")
+  PrintPath("ENV{FOO_ROOT}:" "'$ENV{FOO_ROOT}'")
+  message("")
+
+  find_package(Foo)
+  message("find_package(Foo)")
+  PrintPath("FOO_TEST_FILE_FOO:" "${FOO_TEST_FILE_FOO}")
+  PrintPath("FOO_TEST_FILE_ZOT:" "${FOO_TEST_FILE_ZOT}")
+  PrintPath("FOO_TEST_PATH_FOO:" "${FOO_TEST_PATH_FOO}")
+  PrintPath("FOO_TEST_PATH_ZOT:" "${FOO_TEST_PATH_ZOT}")
+  PrintPath("FOO_TEST_PROG_FOO:" "${FOO_TEST_PROG_FOO}")
+  message("")
+
+  unset(FOO_ROOT)
+  unset(ENV{FOO_ROOT})
+  if(NOT CMAKE_HOST_WIN32)
+    unset(Foo_ROOT)
+    unset(ENV{Foo_ROOT})
+  endif()
+  unset(FOO_TEST_FILE_FOO)
+  unset(FOO_TEST_FILE_ZOT)
+  unset(FOO_TEST_PATH_FOO)
+  unset(FOO_TEST_PATH_ZOT)
+  unset(FOO_TEST_PROG_FOO)
+  unset(FOO_TEST_FILE_FOO CACHE)
+  unset(FOO_TEST_FILE_ZOT CACHE)
+  unset(FOO_TEST_PATH_FOO CACHE)
+  unset(FOO_TEST_PATH_ZOT CACHE)
+  unset(FOO_TEST_PROG_FOO CACHE)
+endmacro()
+
+RunTestCase()
+
+set(FOO_ROOT      ${PackageRoot_BASE}/foo/cmake_root)
+set(ENV{FOO_ROOT} ${PackageRoot_BASE}/foo/env_root)
+if(RunCMake_TEST MATCHES "CaseSensitive")
+  if(RunCMake_TEST STREQUAL "CMP0144-WARN-CaseSensitive-Mixed")
+    set(Foo_ROOT      "${FOO_ROOT}")
+    set(ENV{Foo_ROOT} "$ENV{FOO_ROOT}")
+  else()
+    set(Foo_ROOT      ${PackageRoot_BASE}/does_not_exist)
+    set(ENV{Foo_ROOT} ${PackageRoot_BASE}/does_not_exist)
+  endif()
+endif()
+RunTestCase()
+
+set(FOO_ROOT      ${PackageRoot_BASE}/foo/cmake_root)
+if(RunCMake_TEST MATCHES "CaseSensitive")
+  if(RunCMake_TEST STREQUAL "CMP0144-WARN-CaseSensitive-Mixed")
+    set(Foo_ROOT      "${FOO_ROOT}")
+  else()
+    set(Foo_ROOT      ${PackageRoot_BASE}/does_not_exist)
+  endif()
+endif()
+RunTestCase()
+
+set(ENV{FOO_ROOT} ${PackageRoot_BASE}/foo/env_root)
+if(RunCMake_TEST MATCHES "CaseSensitive")
+  if(RunCMake_TEST STREQUAL "CMP0144-WARN-CaseSensitive-Mixed")
+    set(ENV{Foo_ROOT} "$ENV{FOO_ROOT}")
+  else()
+    set(ENV{Foo_ROOT} ${PackageRoot_BASE}/does_not_exist)
+  endif()
+endif()
+RunTestCase()
+
+message("----------")
diff --git a/Tests/RunCMake/find_package/CMP0145-NEW-stderr.txt b/Tests/RunCMake/find_package/CMP0145-NEW-stderr.txt
new file mode 100644
index 0000000..8249211
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0145-NEW-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Warning at CMP0145-NEW\.cmake:[0-9]+ \(find_package\):
+  No "FindDart\.cmake" found in CMAKE_MODULE_PATH\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/find_package/CMP0145-NEW.cmake b/Tests/RunCMake/find_package/CMP0145-NEW.cmake
new file mode 100644
index 0000000..f3e7bef
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0145-NEW.cmake
@@ -0,0 +1,7 @@
+cmake_policy(SET CMP0145 NEW)
+set(_FindDart_testing TRUE)
+find_package(Dart MODULE)
+
+if(_FindDart_included)
+  message(FATAL_ERROR "FindDart.cmake erroneously included")
+endif()
diff --git a/Tests/RunCMake/find_package/CMP0145-OLD.cmake b/Tests/RunCMake/find_package/CMP0145-OLD.cmake
new file mode 100644
index 0000000..9a73f68
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0145-OLD.cmake
@@ -0,0 +1,7 @@
+cmake_policy(SET CMP0145 OLD)
+set(_FindDart_testing TRUE)
+find_package(Dart MODULE)
+
+if(NOT _FindDart_included)
+  message(FATAL_ERROR "FindDart.cmake not included")
+endif()
diff --git a/Tests/RunCMake/find_package/CMP0145-WARN-stderr.txt b/Tests/RunCMake/find_package/CMP0145-WARN-stderr.txt
new file mode 100644
index 0000000..36c66ec
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0145-WARN-stderr.txt
@@ -0,0 +1,8 @@
+CMake Warning \(dev\) at CMP0145-WARN\.cmake:[0-9]+ \(find_package\):
+  Policy CMP0145 is not set: The Dart and FindDart modules are removed\.  Run
+  "cmake --help-policy CMP0145" for policy details\.  Use the cmake_policy
+  command to set the policy and suppress this warning\.
+
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.$
diff --git a/Tests/RunCMake/find_package/CMP0145-WARN.cmake b/Tests/RunCMake/find_package/CMP0145-WARN.cmake
new file mode 100644
index 0000000..76da752
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0145-WARN.cmake
@@ -0,0 +1,6 @@
+set(_FindDart_testing TRUE)
+find_package(Dart MODULE)
+
+if(NOT _FindDart_included)
+  message(FATAL_ERROR "FindDart.cmake not included")
+endif()
diff --git a/Tests/RunCMake/find_package/CMP0146-NEW-stderr.txt b/Tests/RunCMake/find_package/CMP0146-NEW-stderr.txt
new file mode 100644
index 0000000..0162852
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0146-NEW-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Warning at CMP0146-NEW\.cmake:[0-9]+ \(find_package\):
+  No "FindCUDA\.cmake" found in CMAKE_MODULE_PATH\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/find_package/CMP0146-NEW.cmake b/Tests/RunCMake/find_package/CMP0146-NEW.cmake
new file mode 100644
index 0000000..b373227
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0146-NEW.cmake
@@ -0,0 +1,7 @@
+cmake_policy(SET CMP0146 NEW)
+set(_FindCUDA_testing TRUE)
+find_package(CUDA MODULE)
+
+if(_FindCUDA_included)
+  message(FATAL_ERROR "FindCUDA.cmake erroneously included")
+endif()
diff --git a/Tests/RunCMake/find_package/CMP0146-OLD.cmake b/Tests/RunCMake/find_package/CMP0146-OLD.cmake
new file mode 100644
index 0000000..77cd1f5
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0146-OLD.cmake
@@ -0,0 +1,7 @@
+cmake_policy(SET CMP0146 OLD)
+set(_FindCUDA_testing TRUE)
+find_package(CUDA MODULE)
+
+if(NOT _FindCUDA_included)
+  message(FATAL_ERROR "FindCUDA.cmake not included")
+endif()
diff --git a/Tests/RunCMake/find_package/CMP0146-WARN-stderr.txt b/Tests/RunCMake/find_package/CMP0146-WARN-stderr.txt
new file mode 100644
index 0000000..2cd9c5f
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0146-WARN-stderr.txt
@@ -0,0 +1,8 @@
+CMake Warning \(dev\) at CMP0146-WARN\.cmake:[0-9]+ \(find_package\):
+  Policy CMP0146 is not set: The FindCUDA module is removed\.  Run "cmake
+  --help-policy CMP0146" for policy details\.  Use the cmake_policy command to
+  set the policy and suppress this warning\.
+
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.$
diff --git a/Tests/RunCMake/find_package/CMP0146-WARN.cmake b/Tests/RunCMake/find_package/CMP0146-WARN.cmake
new file mode 100644
index 0000000..276daf2
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0146-WARN.cmake
@@ -0,0 +1,6 @@
+set(_FindCUDA_testing TRUE)
+find_package(CUDA MODULE)
+
+if(NOT _FindCUDA_included)
+  message(FATAL_ERROR "FindCUDA.cmake not included")
+endif()
diff --git a/Tests/RunCMake/find_package/CMP0147-NEW-result.txt b/Tests/RunCMake/find_package/CMP0147-NEW-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0147-NEW-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/find_package/CMP0147-NEW-stderr.txt b/Tests/RunCMake/find_package/CMP0147-NEW-stderr.txt
new file mode 100644
index 0000000..1caf6c5
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0147-NEW-stderr.txt
@@ -0,0 +1,7 @@
+^CMake Error at [^
+]*/Modules/FindCUDA.cmake:[0-9]+ \(message\):
+  The FindCUDA module does not work in Visual Studio with policy CMP0147\.
+Call Stack \(most recent call first\):
+  CMP0147-common\.cmake:[0-9]+ \(find_package\)
+  CMP0147-NEW\.cmake:[0-9]+ \(include\)
+  CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/find_package/CMP0147-NEW.cmake b/Tests/RunCMake/find_package/CMP0147-NEW.cmake
new file mode 100644
index 0000000..0ca5b6c
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0147-NEW.cmake
@@ -0,0 +1,2 @@
+cmake_policy(SET CMP0147 NEW)
+include(CMP0147-common.cmake)
diff --git a/Tests/RunCMake/find_package/CMP0147-OLD.cmake b/Tests/RunCMake/find_package/CMP0147-OLD.cmake
new file mode 100644
index 0000000..61ecee5
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0147-OLD.cmake
@@ -0,0 +1,2 @@
+cmake_policy(SET CMP0147 OLD)
+include(CMP0147-common.cmake)
diff --git a/Tests/RunCMake/find_package/CMP0147-WARN.cmake b/Tests/RunCMake/find_package/CMP0147-WARN.cmake
new file mode 100644
index 0000000..d5f3e91
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0147-WARN.cmake
@@ -0,0 +1,2 @@
+# leave CMP0147 unset
+include(CMP0147-common.cmake)
diff --git a/Tests/RunCMake/find_package/CMP0147-common.cmake b/Tests/RunCMake/find_package/CMP0147-common.cmake
new file mode 100644
index 0000000..68a86ee
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0147-common.cmake
@@ -0,0 +1,3 @@
+cmake_policy(SET CMP0146 OLD)
+set(_FindCUDA_testing TRUE)
+find_package(CUDA MODULE)
diff --git a/Tests/RunCMake/find_package/CMP0148-Interp-NEW-stderr.txt b/Tests/RunCMake/find_package/CMP0148-Interp-NEW-stderr.txt
new file mode 100644
index 0000000..68b40f7
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0148-Interp-NEW-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Warning at CMP0148-Interp-NEW\.cmake:[0-9]+ \(find_package\):
+  No "FindPythonInterp\.cmake" found in CMAKE_MODULE_PATH\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/find_package/CMP0148-Interp-NEW.cmake b/Tests/RunCMake/find_package/CMP0148-Interp-NEW.cmake
new file mode 100644
index 0000000..ccd04f5
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0148-Interp-NEW.cmake
@@ -0,0 +1,7 @@
+cmake_policy(SET CMP0148 NEW)
+set(_FindPythonInterp_testing TRUE)
+find_package(PythonInterp MODULE)
+
+if(_FindPythonInterp_included)
+  message(FATAL_ERROR "FindPythonInterp.cmake erroneously included")
+endif()
diff --git a/Tests/RunCMake/find_package/CMP0148-Interp-OLD.cmake b/Tests/RunCMake/find_package/CMP0148-Interp-OLD.cmake
new file mode 100644
index 0000000..1879d19
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0148-Interp-OLD.cmake
@@ -0,0 +1,7 @@
+cmake_policy(SET CMP0148 OLD)
+set(_FindPythonInterp_testing TRUE)
+find_package(PythonInterp MODULE)
+
+if(NOT _FindPythonInterp_included)
+  message(FATAL_ERROR "FindPythonInterp.cmake not included")
+endif()
diff --git a/Tests/RunCMake/find_package/CMP0148-Interp-WARN-stderr.txt b/Tests/RunCMake/find_package/CMP0148-Interp-WARN-stderr.txt
new file mode 100644
index 0000000..2666c22
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0148-Interp-WARN-stderr.txt
@@ -0,0 +1,8 @@
+CMake Warning \(dev\) at CMP0148-Interp-WARN\.cmake:[0-9]+ \(find_package\):
+  Policy CMP0148 is not set: The FindPythonInterp and FindPythonLibs modules
+  are removed\.  Run "cmake --help-policy CMP0148" for policy details\.  Use
+  the cmake_policy command to set the policy and suppress this warning\.
+
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.$
diff --git a/Tests/RunCMake/find_package/CMP0148-Interp-WARN.cmake b/Tests/RunCMake/find_package/CMP0148-Interp-WARN.cmake
new file mode 100644
index 0000000..53b60af
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0148-Interp-WARN.cmake
@@ -0,0 +1,6 @@
+set(_FindPythonInterp_testing TRUE)
+find_package(PythonInterp MODULE)
+
+if(NOT _FindPythonInterp_included)
+  message(FATAL_ERROR "FindPythonInterp.cmake not included")
+endif()
diff --git a/Tests/RunCMake/find_package/CMP0148-Libs-NEW-stderr.txt b/Tests/RunCMake/find_package/CMP0148-Libs-NEW-stderr.txt
new file mode 100644
index 0000000..d92b434
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0148-Libs-NEW-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Warning at CMP0148-Libs-NEW\.cmake:[0-9]+ \(find_package\):
+  No "FindPythonLibs\.cmake" found in CMAKE_MODULE_PATH\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/find_package/CMP0148-Libs-NEW.cmake b/Tests/RunCMake/find_package/CMP0148-Libs-NEW.cmake
new file mode 100644
index 0000000..2ef8c46
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0148-Libs-NEW.cmake
@@ -0,0 +1,7 @@
+cmake_policy(SET CMP0148 NEW)
+set(_FindPythonLibs_testing TRUE)
+find_package(PythonLibs MODULE)
+
+if(_FindPythonLibs_included)
+  message(FATAL_ERROR "FindPythonLibs.cmake erroneously included")
+endif()
diff --git a/Tests/RunCMake/find_package/CMP0148-Libs-OLD.cmake b/Tests/RunCMake/find_package/CMP0148-Libs-OLD.cmake
new file mode 100644
index 0000000..06fd6a6
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0148-Libs-OLD.cmake
@@ -0,0 +1,7 @@
+cmake_policy(SET CMP0148 OLD)
+set(_FindPythonLibs_testing TRUE)
+find_package(PythonLibs MODULE)
+
+if(NOT _FindPythonLibs_included)
+  message(FATAL_ERROR "FindPythonLibs.cmake not included")
+endif()
diff --git a/Tests/RunCMake/find_package/CMP0148-Libs-WARN-stderr.txt b/Tests/RunCMake/find_package/CMP0148-Libs-WARN-stderr.txt
new file mode 100644
index 0000000..5210e51
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0148-Libs-WARN-stderr.txt
@@ -0,0 +1,8 @@
+CMake Warning \(dev\) at CMP0148-Libs-WARN\.cmake:[0-9]+ \(find_package\):
+  Policy CMP0148 is not set: The FindPythonInterp and FindPythonLibs modules
+  are removed\.  Run "cmake --help-policy CMP0148" for policy details\.  Use
+  the cmake_policy command to set the policy and suppress this warning\.
+
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.$
diff --git a/Tests/RunCMake/find_package/CMP0148-Libs-WARN.cmake b/Tests/RunCMake/find_package/CMP0148-Libs-WARN.cmake
new file mode 100644
index 0000000..9bbe066
--- /dev/null
+++ b/Tests/RunCMake/find_package/CMP0148-Libs-WARN.cmake
@@ -0,0 +1,6 @@
+set(_FindPythonLibs_testing TRUE)
+find_package(PythonLibs MODULE)
+
+if(NOT _FindPythonLibs_included)
+  message(FATAL_ERROR "FindPythonLibs.cmake not included")
+endif()
diff --git a/Tests/RunCMake/find_package/CMakeLists.txt b/Tests/RunCMake/find_package/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/find_package/CMakeLists.txt
+++ b/Tests/RunCMake/find_package/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/find_package/RunCMakeTest.cmake b/Tests/RunCMake/find_package/RunCMakeTest.cmake
index fa41fc1..006757a 100644
--- a/Tests/RunCMake/find_package/RunCMakeTest.cmake
+++ b/Tests/RunCMake/find_package/RunCMakeTest.cmake
@@ -36,6 +36,23 @@
 run_cmake(CMP0084-OLD)
 run_cmake(CMP0084-WARN)
 run_cmake(CMP0084-NEW)
+run_cmake(CMP0145-OLD)
+run_cmake(CMP0145-WARN)
+run_cmake(CMP0145-NEW)
+run_cmake(CMP0146-OLD)
+run_cmake(CMP0146-WARN)
+run_cmake(CMP0146-NEW)
+if(RunCMake_GENERATOR MATCHES "Visual Studio")
+  run_cmake(CMP0147-OLD)
+  run_cmake(CMP0147-WARN)
+  run_cmake(CMP0147-NEW)
+endif()
+run_cmake(CMP0148-Interp-OLD)
+run_cmake(CMP0148-Interp-WARN)
+run_cmake(CMP0148-Interp-NEW)
+run_cmake(CMP0148-Libs-OLD)
+run_cmake(CMP0148-Libs-WARN)
+run_cmake(CMP0148-Libs-NEW)
 run_cmake(WrongVersionRange)
 run_cmake(EmptyVersionRange)
 run_cmake(VersionRangeWithEXACT)
@@ -55,6 +72,17 @@
 run_cmake(REGISTRY_VIEW-wrong-view)
 run_cmake(REGISTRY_VIEW-propagated)
 
+if(CMAKE_HOST_WIN32)
+  run_cmake(CMP0144-WARN-CaseInsensitive)
+  run_cmake(CMP0144-OLD-CaseInsensitive)
+  run_cmake(CMP0144-NEW-CaseInsensitive)
+else()
+  run_cmake(CMP0144-WARN-CaseSensitive)
+  run_cmake(CMP0144-WARN-CaseSensitive-Mixed)
+  run_cmake(CMP0144-OLD-CaseSensitive)
+  run_cmake(CMP0144-NEW-CaseSensitive)
+endif()
+
 file(
     GLOB SearchPaths_TEST_CASE_LIST
     LIST_DIRECTORIES TRUE
diff --git a/Tests/RunCMake/find_path/CMakeLists.txt b/Tests/RunCMake/find_path/CMakeLists.txt
index ef2163c..93ee9df 100644
--- a/Tests/RunCMake/find_path/CMakeLists.txt
+++ b/Tests/RunCMake/find_path/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/find_program/CMP0109-OLD-stderr.txt b/Tests/RunCMake/find_program/CMP0109-OLD-stderr.txt
new file mode 100644
index 0000000..fa767b9
--- /dev/null
+++ b/Tests/RunCMake/find_program/CMP0109-OLD-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at CMP0109-OLD.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0109 will be removed from a future version
+  of CMake.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/find_program/CMakeLists.txt b/Tests/RunCMake/find_program/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/find_program/CMakeLists.txt
+++ b/Tests/RunCMake/find_program/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/get_filename_component/CMakeLists.txt b/Tests/RunCMake/get_filename_component/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/get_filename_component/CMakeLists.txt
+++ b/Tests/RunCMake/get_filename_component/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/get_property/CMakeLists.txt b/Tests/RunCMake/get_property/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/get_property/CMakeLists.txt
+++ b/Tests/RunCMake/get_property/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/if/CMakeLists.txt b/Tests/RunCMake/if/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/if/CMakeLists.txt
+++ b/Tests/RunCMake/if/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/include/CMP0024-NEW-stderr.txt b/Tests/RunCMake/include/CMP0024-NEW-stderr.txt
index 0fdb3ca..26ced80 100644
--- a/Tests/RunCMake/include/CMP0024-NEW-stderr.txt
+++ b/Tests/RunCMake/include/CMP0024-NEW-stderr.txt
@@ -1,8 +1,9 @@
-CMake Error at subdir2/CMakeLists.txt:2 \(include\):
+^CMake Error at subdir2/CMakeLists\.txt:2 \(include\):
   The file
 
-    .*/Tests/RunCMake/include/CMP0024-NEW-build/subdir1/theTargets.cmake
+    [^
+]*/Tests/RunCMake/include/CMP0024-NEW-build/subdir1/theTargets\.cmake
 
-  was generated by the export\(\) command.  It may not be used as the argument
-  to the include\(\) command.  Use ALIAS targets instead to refer to targets by
-  alternative names.
+  was generated by the export\(\) command\.  It may not be used as the argument
+  to the include\(\) command\.  Use ALIAS targets instead to refer to targets by
+  alternative names\.
diff --git a/Tests/RunCMake/include/CMP0024-NEW.cmake b/Tests/RunCMake/include/CMP0024-NEW.cmake
index 0e03d2a..783cf78 100644
--- a/Tests/RunCMake/include/CMP0024-NEW.cmake
+++ b/Tests/RunCMake/include/CMP0024-NEW.cmake
@@ -1,8 +1,6 @@
 
 enable_language(CXX)
 
-cmake_policy(SET CMP0024 NEW)
-
 add_library(foo SHARED empty.cpp)
 
 add_subdirectory(subdir1)
diff --git a/Tests/RunCMake/include/CMP0024-WARN-stderr.txt b/Tests/RunCMake/include/CMP0024-WARN-stderr.txt
index 9c79007..8472f41 100644
--- a/Tests/RunCMake/include/CMP0024-WARN-stderr.txt
+++ b/Tests/RunCMake/include/CMP0024-WARN-stderr.txt
@@ -1,14 +1,24 @@
-CMake Warning \(dev\) at subdir2/CMakeLists.txt:2 \(include\):
+^CMake Deprecation Warning at CMP0024-WARN\.cmake:[0-9]+ \(cmake_policy\):
+  Compatibility with CMake < 3\.5 will be removed from a future version of
+  CMake.
+
+  Update the VERSION argument <min> value or use a \.\.\.<max> suffix to tell
+  CMake that the project does not need compatibility with older versions\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
++
+CMake Warning \(dev\) at subdir2/CMakeLists\.txt:2 \(include\):
   Policy CMP0024 is not set: Disallow include export result.  Run "cmake
-  --help-policy CMP0024" for policy details.  Use the cmake_policy command to
-  set the policy and suppress this warning.
+  --help-policy CMP0024" for policy details\.  Use the cmake_policy command to
+  set the policy and suppress this warning\.
 
   The file
 
-    .*/Tests/RunCMake/include/CMP0024-WARN-build/subdir1/theTargets.cmake
+    [^
+]*/Tests/RunCMake/include/CMP0024-WARN-build/subdir1/theTargets\.cmake
 
-  was generated by the export\(\) command.  It should not be used as the
-  argument to the include\(\) command.  Use ALIAS targets instead to refer to
-  targets by alternative names.
+  was generated by the export\(\) command\.  It should not be used as the
+  argument to the include\(\) command\.  Use ALIAS targets instead to refer to
+  targets by alternative names\.
 
-This warning is for project developers.  Use -Wno-dev to suppress it.
+This warning is for project developers\.  Use -Wno-dev to suppress it\.
diff --git a/Tests/RunCMake/include/CMP0024-WARN.cmake b/Tests/RunCMake/include/CMP0024-WARN.cmake
index 783cf78..f267a5a 100644
--- a/Tests/RunCMake/include/CMP0024-WARN.cmake
+++ b/Tests/RunCMake/include/CMP0024-WARN.cmake
@@ -1,3 +1,4 @@
+cmake_policy(VERSION 2.8.12) # Leave CMP0024 unset.
 
 enable_language(CXX)
 
diff --git a/Tests/RunCMake/include/CMP0146-NEW-name-result.txt b/Tests/RunCMake/include/CMP0146-NEW-name-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0146-NEW-name-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/include/CMP0146-NEW-name-stderr.txt b/Tests/RunCMake/include/CMP0146-NEW-name-stderr.txt
new file mode 100644
index 0000000..7d9e7d8
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0146-NEW-name-stderr.txt
@@ -0,0 +1,6 @@
+^CMake Error at CMP0146-NEW-name\.cmake:[0-9]+ \(include\):
+  include could not find requested file:
+
+    FindCUDA
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/include/CMP0146-NEW-name.cmake b/Tests/RunCMake/include/CMP0146-NEW-name.cmake
new file mode 100644
index 0000000..feedc6f
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0146-NEW-name.cmake
@@ -0,0 +1,2 @@
+cmake_policy(SET CMP0146 NEW)
+include(FindCUDA)
diff --git a/Tests/RunCMake/include/CMP0146-NEW-path-result.txt b/Tests/RunCMake/include/CMP0146-NEW-path-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0146-NEW-path-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/include/CMP0146-NEW-path-stderr.txt b/Tests/RunCMake/include/CMP0146-NEW-path-stderr.txt
new file mode 100644
index 0000000..916672b
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0146-NEW-path-stderr.txt
@@ -0,0 +1,6 @@
+^CMake Error at [^
+]*/Modules/FindCUDA.cmake:[0-9]+ \(message\):
+  The FindCUDA module has been removed by policy CMP0146\.
+Call Stack \(most recent call first\):
+  CMP0146-NEW-path\.cmake:[0-9]+ \(include\)
+  CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/include/CMP0146-NEW-path.cmake b/Tests/RunCMake/include/CMP0146-NEW-path.cmake
new file mode 100644
index 0000000..6768d4d
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0146-NEW-path.cmake
@@ -0,0 +1,2 @@
+cmake_policy(SET CMP0146 NEW)
+include(${CMAKE_ROOT}/Modules/FindCUDA.cmake)
diff --git a/Tests/RunCMake/include/CMP0146-OLD.cmake b/Tests/RunCMake/include/CMP0146-OLD.cmake
new file mode 100644
index 0000000..654cdf7
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0146-OLD.cmake
@@ -0,0 +1,7 @@
+cmake_policy(SET CMP0146 OLD)
+set(_FindCUDA_testing 1)
+include(FindCUDA)
+
+if(NOT _FindCUDA_included)
+  message(FATAL_ERROR "FindCUDA.cmake not included")
+endif()
diff --git a/Tests/RunCMake/include/CMP0146-WARN-stderr.txt b/Tests/RunCMake/include/CMP0146-WARN-stderr.txt
new file mode 100644
index 0000000..aaaf1dc
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0146-WARN-stderr.txt
@@ -0,0 +1,8 @@
+^CMake Warning \(dev\) at CMP0146-WARN\.cmake:[0-9]+ \(include\):
+  Policy CMP0146 is not set: The FindCUDA module is removed\.  Run "cmake
+  --help-policy CMP0146" for policy details\.  Use the cmake_policy command to
+  set the policy and suppress this warning\.
+
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.$
diff --git a/Tests/RunCMake/include/CMP0146-WARN.cmake b/Tests/RunCMake/include/CMP0146-WARN.cmake
new file mode 100644
index 0000000..bce1ae8
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0146-WARN.cmake
@@ -0,0 +1,7 @@
+# Do not set CMP0146.
+set(_FindCUDA_testing 1)
+include(FindCUDA)
+
+if(NOT _FindCUDA_included)
+  message(FATAL_ERROR "FindCUDA.cmake not included")
+endif()
diff --git a/Tests/RunCMake/include/CMP0148-Interp-NEW-name-result.txt b/Tests/RunCMake/include/CMP0148-Interp-NEW-name-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0148-Interp-NEW-name-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/include/CMP0148-Interp-NEW-name-stderr.txt b/Tests/RunCMake/include/CMP0148-Interp-NEW-name-stderr.txt
new file mode 100644
index 0000000..8627c2e
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0148-Interp-NEW-name-stderr.txt
@@ -0,0 +1,6 @@
+^CMake Error at CMP0148-Interp-NEW-name\.cmake:[0-9]+ \(include\):
+  include could not find requested file:
+
+    FindPythonInterp
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/include/CMP0148-Interp-NEW-name.cmake b/Tests/RunCMake/include/CMP0148-Interp-NEW-name.cmake
new file mode 100644
index 0000000..7d73dc4
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0148-Interp-NEW-name.cmake
@@ -0,0 +1,2 @@
+cmake_policy(SET CMP0148 NEW)
+include(FindPythonInterp)
diff --git a/Tests/RunCMake/include/CMP0148-Interp-NEW-path-result.txt b/Tests/RunCMake/include/CMP0148-Interp-NEW-path-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0148-Interp-NEW-path-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/include/CMP0148-Interp-NEW-path-stderr.txt b/Tests/RunCMake/include/CMP0148-Interp-NEW-path-stderr.txt
new file mode 100644
index 0000000..0b0f29d
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0148-Interp-NEW-path-stderr.txt
@@ -0,0 +1,6 @@
+^CMake Error at [^
+]*/Modules/FindPythonInterp.cmake:[0-9]+ \(message\):
+  The FindPythonInterp module has been removed by policy CMP0148\.
+Call Stack \(most recent call first\):
+  CMP0148-Interp-NEW-path\.cmake:[0-9]+ \(include\)
+  CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/include/CMP0148-Interp-NEW-path.cmake b/Tests/RunCMake/include/CMP0148-Interp-NEW-path.cmake
new file mode 100644
index 0000000..1f8630e
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0148-Interp-NEW-path.cmake
@@ -0,0 +1,2 @@
+cmake_policy(SET CMP0148 NEW)
+include(${CMAKE_ROOT}/Modules/FindPythonInterp.cmake)
diff --git a/Tests/RunCMake/include/CMP0148-Interp-OLD.cmake b/Tests/RunCMake/include/CMP0148-Interp-OLD.cmake
new file mode 100644
index 0000000..8905221
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0148-Interp-OLD.cmake
@@ -0,0 +1,7 @@
+cmake_policy(SET CMP0148 OLD)
+set(_FindPythonInterp_testing 1)
+include(FindPythonInterp)
+
+if(NOT _FindPythonInterp_included)
+  message(FATAL_ERROR "FindPythonInterp.cmake not included")
+endif()
diff --git a/Tests/RunCMake/include/CMP0148-Interp-WARN-stderr.txt b/Tests/RunCMake/include/CMP0148-Interp-WARN-stderr.txt
new file mode 100644
index 0000000..5bd6935
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0148-Interp-WARN-stderr.txt
@@ -0,0 +1,8 @@
+^CMake Warning \(dev\) at CMP0148-Interp-WARN\.cmake:[0-9]+ \(include\):
+  Policy CMP0148 is not set: The FindPythonInterp and FindPythonLibs modules
+  are removed\.  Run "cmake --help-policy CMP0148" for policy details\.  Use
+  the cmake_policy command to set the policy and suppress this warning\.
+
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.$
diff --git a/Tests/RunCMake/include/CMP0148-Interp-WARN.cmake b/Tests/RunCMake/include/CMP0148-Interp-WARN.cmake
new file mode 100644
index 0000000..18f302d
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0148-Interp-WARN.cmake
@@ -0,0 +1,7 @@
+# Do not set CMP0148.
+set(_FindPythonInterp_testing 1)
+include(FindPythonInterp)
+
+if(NOT _FindPythonInterp_included)
+  message(FATAL_ERROR "FindPythonInterp.cmake not included")
+endif()
diff --git a/Tests/RunCMake/include/CMP0148-Libs-NEW-name-result.txt b/Tests/RunCMake/include/CMP0148-Libs-NEW-name-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0148-Libs-NEW-name-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/include/CMP0148-Libs-NEW-name-stderr.txt b/Tests/RunCMake/include/CMP0148-Libs-NEW-name-stderr.txt
new file mode 100644
index 0000000..4e1f97c
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0148-Libs-NEW-name-stderr.txt
@@ -0,0 +1,6 @@
+^CMake Error at CMP0148-Libs-NEW-name\.cmake:[0-9]+ \(include\):
+  include could not find requested file:
+
+    FindPythonLibs
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/include/CMP0148-Libs-NEW-name.cmake b/Tests/RunCMake/include/CMP0148-Libs-NEW-name.cmake
new file mode 100644
index 0000000..c9877bc
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0148-Libs-NEW-name.cmake
@@ -0,0 +1,2 @@
+cmake_policy(SET CMP0148 NEW)
+include(FindPythonLibs)
diff --git a/Tests/RunCMake/include/CMP0148-Libs-NEW-path-result.txt b/Tests/RunCMake/include/CMP0148-Libs-NEW-path-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0148-Libs-NEW-path-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/include/CMP0148-Libs-NEW-path-stderr.txt b/Tests/RunCMake/include/CMP0148-Libs-NEW-path-stderr.txt
new file mode 100644
index 0000000..5f1037d
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0148-Libs-NEW-path-stderr.txt
@@ -0,0 +1,6 @@
+^CMake Error at [^
+]*/Modules/FindPythonLibs.cmake:[0-9]+ \(message\):
+  The FindPythonLibs module has been removed by policy CMP0148\.
+Call Stack \(most recent call first\):
+  CMP0148-Libs-NEW-path\.cmake:[0-9]+ \(include\)
+  CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/include/CMP0148-Libs-NEW-path.cmake b/Tests/RunCMake/include/CMP0148-Libs-NEW-path.cmake
new file mode 100644
index 0000000..29b315f
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0148-Libs-NEW-path.cmake
@@ -0,0 +1,2 @@
+cmake_policy(SET CMP0148 NEW)
+include(${CMAKE_ROOT}/Modules/FindPythonLibs.cmake)
diff --git a/Tests/RunCMake/include/CMP0148-Libs-OLD.cmake b/Tests/RunCMake/include/CMP0148-Libs-OLD.cmake
new file mode 100644
index 0000000..b69e160
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0148-Libs-OLD.cmake
@@ -0,0 +1,7 @@
+cmake_policy(SET CMP0148 OLD)
+set(_FindPythonLibs_testing 1)
+include(FindPythonLibs)
+
+if(NOT _FindPythonLibs_included)
+  message(FATAL_ERROR "FindPythonLibs.cmake not included")
+endif()
diff --git a/Tests/RunCMake/include/CMP0148-Libs-WARN-stderr.txt b/Tests/RunCMake/include/CMP0148-Libs-WARN-stderr.txt
new file mode 100644
index 0000000..c24abd4
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0148-Libs-WARN-stderr.txt
@@ -0,0 +1,8 @@
+^CMake Warning \(dev\) at CMP0148-Libs-WARN\.cmake:[0-9]+ \(include\):
+  Policy CMP0148 is not set: The FindPythonInterp and FindPythonLibs modules
+  are removed\.  Run "cmake --help-policy CMP0148" for policy details\.  Use
+  the cmake_policy command to set the policy and suppress this warning\.
+
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.$
diff --git a/Tests/RunCMake/include/CMP0148-Libs-WARN.cmake b/Tests/RunCMake/include/CMP0148-Libs-WARN.cmake
new file mode 100644
index 0000000..d8dc00c
--- /dev/null
+++ b/Tests/RunCMake/include/CMP0148-Libs-WARN.cmake
@@ -0,0 +1,7 @@
+# Do not set CMP0148.
+set(_FindPythonLibs_testing 1)
+include(FindPythonLibs)
+
+if(NOT _FindPythonLibs_included)
+  message(FATAL_ERROR "FindPythonLibs.cmake not included")
+endif()
diff --git a/Tests/RunCMake/include/CMakeLists.txt b/Tests/RunCMake/include/CMakeLists.txt
index 4b3de84..93ee9df 100644
--- a/Tests/RunCMake/include/CMakeLists.txt
+++ b/Tests/RunCMake/include/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/include/EmptyString-stderr.txt b/Tests/RunCMake/include/EmptyString-stderr.txt
index 006c647..dab16ea 100644
--- a/Tests/RunCMake/include/EmptyString-stderr.txt
+++ b/Tests/RunCMake/include/EmptyString-stderr.txt
@@ -1,5 +1,5 @@
-CMake Warning \(dev\) at EmptyString.cmake:1 \(include\):
+^CMake Warning \(dev\) at EmptyString\.cmake:1 \(include\):
   include\(\) given empty file name \(ignored\).
 Call Stack \(most recent call first\):
-  CMakeLists.txt:3 \(include\)
-This warning is for project developers.  Use -Wno-dev to suppress it.
+  CMakeLists\.txt:3 \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.$
diff --git a/Tests/RunCMake/include/EmptyStringOptional-stderr.txt b/Tests/RunCMake/include/EmptyStringOptional-stderr.txt
index b61c679..1b763f2 100644
--- a/Tests/RunCMake/include/EmptyStringOptional-stderr.txt
+++ b/Tests/RunCMake/include/EmptyStringOptional-stderr.txt
@@ -1,5 +1,5 @@
-CMake Warning \(dev\) at EmptyStringOptional.cmake:1 \(include\):
+^CMake Warning \(dev\) at EmptyStringOptional\.cmake:1 \(include\):
   include\(\) given empty file name \(ignored\).
 Call Stack \(most recent call first\):
-  CMakeLists.txt:3 \(include\)
-This warning is for project developers.  Use -Wno-dev to suppress it.
+  CMakeLists\.txt:3 \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.$
diff --git a/Tests/RunCMake/include/ExportExportInclude-stderr.txt b/Tests/RunCMake/include/ExportExportInclude-stderr.txt
index 6d5c02f..22bf7a1 100644
--- a/Tests/RunCMake/include/ExportExportInclude-stderr.txt
+++ b/Tests/RunCMake/include/ExportExportInclude-stderr.txt
@@ -1,6 +1,7 @@
-CMake Error at ExportExportInclude.cmake:6 \(include\):
+^CMake Error at ExportExportInclude.cmake:6 \(include\):
   include could not find requested file:
 
-    .*/Tests/RunCMake/include/ExportExportInclude-build/theTargets.cmake
+    [^
+]*/Tests/RunCMake/include/ExportExportInclude-build/theTargets\.cmake
 Call Stack \(most recent call first\):
-  CMakeLists.txt:3 \(include\)
+  CMakeLists\.txt:3 \(include\)$
diff --git a/Tests/RunCMake/include/IncludeIsDirectory-stderr.txt b/Tests/RunCMake/include/IncludeIsDirectory-stderr.txt
index 5735c29..6ce934d 100644
--- a/Tests/RunCMake/include/IncludeIsDirectory-stderr.txt
+++ b/Tests/RunCMake/include/IncludeIsDirectory-stderr.txt
@@ -1,6 +1,7 @@
-CMake Error at IncludeIsDirectory.cmake:1 \(include\):
+^CMake Error at IncludeIsDirectory.cmake:1 \(include\):
   include requested file is a directory:
 
-    .*/Tests/RunCMake/include/IncludeIsDirectory-build
+    [^
+]*/Tests/RunCMake/include/IncludeIsDirectory-build
 Call Stack \(most recent call first\):
-  CMakeLists.txt:3 \(include\)
+  CMakeLists\.txt:3 \(include\)$
diff --git a/Tests/RunCMake/include/IncludeMalformed-stderr.txt b/Tests/RunCMake/include/IncludeMalformed-stderr.txt
index fc75549..e34e9bb 100644
--- a/Tests/RunCMake/include/IncludeMalformed-stderr.txt
+++ b/Tests/RunCMake/include/IncludeMalformed-stderr.txt
@@ -1,13 +1,12 @@
-CMake Error at malformedInclude.cmake:1:
-  Parse error.  Function missing ending "\)".  End of file reached.
+^CMake Error at malformedInclude\.cmake:1:
+  Parse error\.  Function missing ending "\)"\.  End of file reached\.
 Call Stack \(most recent call first\):
-  IncludeMalformed.cmake:1 \(include\)
-  CMakeLists.txt:3 \(include\)
-
-
-CMake Error at IncludeMalformed.cmake:1 \(include\):
+  IncludeMalformed\.cmake:1 \(include\)
+  CMakeLists\.txt:3 \(include\)
++
+CMake Error at IncludeMalformed\.cmake:1 \(include\):
   include could not load requested file:
 
-    malformedInclude.cmake
+    malformedInclude\.cmake
 Call Stack \(most recent call first\):
-  CMakeLists.txt:3 \(include\)
+  CMakeLists\.txt:3 \(include\)$
diff --git a/Tests/RunCMake/include/RunCMakeTest.cmake b/Tests/RunCMake/include/RunCMakeTest.cmake
index 8fb7201..685173e 100644
--- a/Tests/RunCMake/include/RunCMakeTest.cmake
+++ b/Tests/RunCMake/include/RunCMakeTest.cmake
@@ -7,3 +7,17 @@
 run_cmake(ExportExportInclude)
 run_cmake(IncludeIsDirectory)
 run_cmake(IncludeMalformed)
+
+run_cmake(CMP0146-OLD)
+run_cmake(CMP0146-WARN)
+run_cmake(CMP0146-NEW-name)
+run_cmake(CMP0146-NEW-path)
+
+run_cmake(CMP0148-Interp-OLD)
+run_cmake(CMP0148-Interp-WARN)
+run_cmake(CMP0148-Interp-NEW-name)
+run_cmake(CMP0148-Interp-NEW-path)
+run_cmake(CMP0148-Libs-OLD)
+run_cmake(CMP0148-Libs-WARN)
+run_cmake(CMP0148-Libs-NEW-name)
+run_cmake(CMP0148-Libs-NEW-path)
diff --git a/Tests/RunCMake/include_directories/CMakeLists.txt b/Tests/RunCMake/include_directories/CMakeLists.txt
index 2897109..93ee9df 100644
--- a/Tests/RunCMake/include_directories/CMakeLists.txt
+++ b/Tests/RunCMake/include_directories/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/include_external_msproject/CMakeLists.txt b/Tests/RunCMake/include_external_msproject/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/include_external_msproject/CMakeLists.txt
+++ b/Tests/RunCMake/include_external_msproject/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/install/CMP0062-OLD-stderr.txt b/Tests/RunCMake/install/CMP0062-OLD-stderr.txt
index de0b70f..6b4c4b0 100644
--- a/Tests/RunCMake/install/CMP0062-OLD-stderr.txt
+++ b/Tests/RunCMake/install/CMP0062-OLD-stderr.txt
@@ -1,10 +1,19 @@
-^CMake Deprecation Warning at CMP0062-OLD.cmake:[0-9]+ \(cmake_policy\):
+^CMake Deprecation Warning at CMP0062-OLD\.cmake:[0-9]+ \(cmake_policy\):
+  Compatibility with CMake < 3\.5 will be removed from a future version of
+  CMake\.
+
+  Update the VERSION argument <min> value or use a \.\.\.<max> suffix to tell
+  CMake that the project does not need compatibility with older versions\.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
++
+CMake Deprecation Warning at CMP0062-OLD\.cmake:[0-9]+ \(cmake_policy\):
   The OLD behavior for policy CMP0062 will be removed from a future version
-  of CMake.
+  of CMake\.
 
   The cmake-policies\(7\) manual explains that the OLD behaviors of all
   policies are deprecated and that a policy should be set to OLD only under
   specific short-term circumstances.  Projects should be ported to the NEW
   behavior and not rely on setting a policy to OLD.
 Call Stack \(most recent call first\):
-  CMakeLists.txt:[0-9]+ \(include\)$
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/install/CMakeLists.txt b/Tests/RunCMake/install/CMakeLists.txt
index 6dd8cdf..93ee9df 100644
--- a/Tests/RunCMake/install/CMakeLists.txt
+++ b/Tests/RunCMake/install/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.4)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/install/FILES-EXTRA_ISPC_TARGET_OBJECTS-all-check.cmake b/Tests/RunCMake/install/FILES-EXTRA_ISPC_TARGET_OBJECTS-all-check.cmake
index 8750a76..2d4ce66 100644
--- a/Tests/RunCMake/install/FILES-EXTRA_ISPC_TARGET_OBJECTS-all-check.cmake
+++ b/Tests/RunCMake/install/FILES-EXTRA_ISPC_TARGET_OBJECTS-all-check.cmake
@@ -1,6 +1,10 @@
 
 set(objs obj1 obj2)
-set(targets  sse2 sse4 avx avx2)
+if("${CMAKE_SYSTEM_NAME};${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "Darwin;arm64")
+  set(targets  "")
+else()
+  set(targets  sse2 sse4 avx avx2)
+endif()
 foreach(o IN LISTS objs)
   set(item "objs/${o}\\.ispc\\.(o|obj)")
   check_installed("${item}")
diff --git a/Tests/RunCMake/install/FILES-EXTRA_ISPC_TARGET_OBJECTS.cmake b/Tests/RunCMake/install/FILES-EXTRA_ISPC_TARGET_OBJECTS.cmake
index ad542ed..91ed458 100644
--- a/Tests/RunCMake/install/FILES-EXTRA_ISPC_TARGET_OBJECTS.cmake
+++ b/Tests/RunCMake/install/FILES-EXTRA_ISPC_TARGET_OBJECTS.cmake
@@ -1,4 +1,8 @@
 enable_language(ISPC)
 add_library(objs OBJECT obj1.ispc obj2.ispc)
-set_target_properties(objs PROPERTIES ISPC_INSTRUCTION_SETS "sse2-i32x4;sse4-i16x8;avx1-i32x16;avx2-i32x4")
+if("${CMAKE_SYSTEM_NAME};${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "Darwin;arm64")
+  set_property(TARGET objs PROPERTY ISPC_INSTRUCTION_SETS "neon-i32x4")
+else()
+  set_property(TARGET objs PROPERTY ISPC_INSTRUCTION_SETS "sse2-i32x4;sse4-i16x8;avx1-i32x16;avx2-i32x4")
+endif()
 install(FILES $<TARGET_OBJECTS:objs> DESTINATION objs)
diff --git a/Tests/RunCMake/install/RunCMakeTest.cmake b/Tests/RunCMake/install/RunCMakeTest.cmake
index 477ffe0..efafdd1 100644
--- a/Tests/RunCMake/install/RunCMakeTest.cmake
+++ b/Tests/RunCMake/install/RunCMakeTest.cmake
@@ -1,4 +1,3 @@
-cmake_minimum_required(VERSION 3.4)
 include(RunCMake)
 
 # Function to build and install a project.  The latter step *-check.cmake
@@ -122,6 +121,10 @@
 run_install_test(DIRECTORY-OPTIONAL)
 run_install_test(TARGETS-Defaults)
 
+if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
+  run_install_test(TARGETS-NAMELINK-No-Tweak)
+endif()
+
 set(RunCMake_TEST_OPTIONS
   "-DCMAKE_INSTALL_BINDIR:PATH=mybin"
   "-DCMAKE_INSTALL_LIBDIR:PATH=mylib"
@@ -167,7 +170,6 @@
 
 run_install_test(Deprecated)
 run_install_test(PRE_POST_INSTALL_SCRIPT)
-run_install_test(SCRIPT)
 run_install_test(TARGETS-CONFIGURATIONS)
 run_install_test(DIRECTORY-PATTERN)
 run_install_test(TARGETS-Parts)
@@ -175,6 +177,10 @@
 run_install_test(TARGETS-RPATH)
 run_install_test(InstallRequiredSystemLibraries)
 
+set(RunCMake_TEST_OPTIONS "-DCMAKE_POLICY_DEFAULT_CMP0087:STRING=NEW")
+run_install_test(SCRIPT)
+unset(RunCMake_TEST_OPTIONS)
+
 if(UNIX)
   run_install_test(DIRECTORY-symlink-clobber)
 endif()
diff --git a/Tests/RunCMake/install/SCRIPT-all-check.cmake b/Tests/RunCMake/install/SCRIPT-all-check.cmake
index 48d8e1a..b9c38e3 100644
--- a/Tests/RunCMake/install/SCRIPT-all-check.cmake
+++ b/Tests/RunCMake/install/SCRIPT-all-check.cmake
@@ -1 +1 @@
-check_installed([[^empty1.txt;empty2.txt$]])
+check_installed([[^empty1.txt;empty2.txt;empty3.cmake;empty3.txt;empty4.cmake;empty4.txt$]])
diff --git a/Tests/RunCMake/install/SCRIPT.cmake b/Tests/RunCMake/install/SCRIPT.cmake
index f857b54..ee0c80e 100644
--- a/Tests/RunCMake/install/SCRIPT.cmake
+++ b/Tests/RunCMake/install/SCRIPT.cmake
@@ -1,4 +1,10 @@
 install(
+  FILES ${CMAKE_CURRENT_SOURCE_DIR}/empty3.cmake ${CMAKE_CURRENT_SOURCE_DIR}/empty4.cmake
+  DESTINATION .
+  )
+install(
   SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/install_script.cmake"
   CODE "write_empty_file(empty2.txt)"
+  SCRIPT "$<INSTALL_PREFIX>/empty3.cmake"
+  CODE [[include($<INSTALL_PREFIX>/empty4.cmake)]]
   )
diff --git a/Tests/RunCMake/install/TARGETS-Defaults-Cache-stderr.txt b/Tests/RunCMake/install/TARGETS-Defaults-Cache-stderr.txt
index 138a69d..7d1477f 100644
--- a/Tests/RunCMake/install/TARGETS-Defaults-Cache-stderr.txt
+++ b/Tests/RunCMake/install/TARGETS-Defaults-Cache-stderr.txt
@@ -1,11 +1,11 @@
 ^CMake Warning \(dev\) at TARGETS-Defaults-Cache.cmake:[0-9]+ \(install\):
   Target lib3 has PRIVATE_HEADER files but no PRIVATE_HEADER DESTINATION.
 Call Stack \(most recent call first\):
-  CMakeLists.txt:3 \(include\)
+  CMakeLists.txt:[0-9]+ \(include\)
 This warning is for project developers.  Use -Wno-dev to suppress it.
 
 CMake Warning \(dev\) at TARGETS-Defaults-Cache.cmake:[0-9]+ \(install\):
   Target lib4 has PUBLIC_HEADER files but no PUBLIC_HEADER DESTINATION.
 Call Stack \(most recent call first\):
-  CMakeLists.txt:3 \(include\)
+  CMakeLists.txt:[0-9]+ \(include\)
 This warning is for project developers.  Use -Wno-dev to suppress it.$
diff --git a/Tests/RunCMake/install/TARGETS-Defaults-stderr.txt b/Tests/RunCMake/install/TARGETS-Defaults-stderr.txt
index 5f56986..5600801 100644
--- a/Tests/RunCMake/install/TARGETS-Defaults-stderr.txt
+++ b/Tests/RunCMake/install/TARGETS-Defaults-stderr.txt
@@ -1,11 +1,11 @@
 ^CMake Warning \(dev\) at TARGETS-Defaults.cmake:[0-9]+ \(install\):
   Target lib3 has PRIVATE_HEADER files but no PRIVATE_HEADER DESTINATION.
 Call Stack \(most recent call first\):
-  CMakeLists.txt:3 \(include\)
+  CMakeLists.txt:[0-9]+ \(include\)
 This warning is for project developers.  Use -Wno-dev to suppress it.
 +
 CMake Warning \(dev\) at TARGETS-Defaults.cmake:[0-9]+ \(install\):
   Target lib4 has PUBLIC_HEADER files but no PUBLIC_HEADER DESTINATION.
 Call Stack \(most recent call first\):
-  CMakeLists.txt:3 \(include\)
+  CMakeLists.txt:[0-9]+ \(include\)
 This warning is for project developers.  Use -Wno-dev to suppress it.$
diff --git a/Tests/RunCMake/install/TARGETS-FILE_RPATH_CHANGE-new_rpath.cmake b/Tests/RunCMake/install/TARGETS-FILE_RPATH_CHANGE-new_rpath.cmake
index cba04b2..c72e405 100644
--- a/Tests/RunCMake/install/TARGETS-FILE_RPATH_CHANGE-new_rpath.cmake
+++ b/Tests/RunCMake/install/TARGETS-FILE_RPATH_CHANGE-new_rpath.cmake
@@ -1,4 +1,3 @@
-cmake_minimum_required(VERSION 3.14)
 enable_language(C)
 
 # test matrix
diff --git a/Tests/RunCMake/install/TARGETS-FILE_RPATH_CHANGE-old_rpath.cmake b/Tests/RunCMake/install/TARGETS-FILE_RPATH_CHANGE-old_rpath.cmake
index 43ae787..891b1ac 100644
--- a/Tests/RunCMake/install/TARGETS-FILE_RPATH_CHANGE-old_rpath.cmake
+++ b/Tests/RunCMake/install/TARGETS-FILE_RPATH_CHANGE-old_rpath.cmake
@@ -1,4 +1,3 @@
-cmake_minimum_required(VERSION 3.14)
 enable_language(C)
 
 add_library(utils SHARED obj1.c)
diff --git a/Tests/RunCMake/install/TARGETS-NAMELINK-No-Tweak.cmake b/Tests/RunCMake/install/TARGETS-NAMELINK-No-Tweak.cmake
new file mode 100644
index 0000000..879f4b8
--- /dev/null
+++ b/Tests/RunCMake/install/TARGETS-NAMELINK-No-Tweak.cmake
@@ -0,0 +1,20 @@
+enable_language(C)
+
+add_library(foo SHARED obj1.c)
+set_target_properties(foo PROPERTIES
+  VERSION 1.0
+  SOVERSION 1
+  INSTALL_RPATH "$ORIGIN"
+  )
+install(TARGETS foo DESTINATION lib)
+
+# Replace the .so "namelink" symlink with a linker script.
+# It is no longer a symlink, so any install tweaks would break.
+# This verifies that no install tweaks are added for the namelink.
+set(linker_script "INPUT($<TARGET_SONAME_FILE_NAME:foo>)")
+add_custom_command(TARGET foo POST_BUILD
+  COMMAND "${CMAKE_COMMAND}" -E remove "$<TARGET_LINKER_FILE:foo>"
+  COMMAND "${CMAKE_COMMAND}" -E echo "${linker_script}" > "$<TARGET_LINKER_FILE:foo>"
+  COMMENT "Generating linker script: '${linker_script}' as file $<TARGET_LINKER_FILE:foo>"
+  VERBATIM
+  )
diff --git a/Tests/RunCMake/install/TARGETS-NAMELINK_COMPONENT-bad-all-stderr.txt b/Tests/RunCMake/install/TARGETS-NAMELINK_COMPONENT-bad-all-stderr.txt
index fe65fd3..26dcd42 100644
--- a/Tests/RunCMake/install/TARGETS-NAMELINK_COMPONENT-bad-all-stderr.txt
+++ b/Tests/RunCMake/install/TARGETS-NAMELINK_COMPONENT-bad-all-stderr.txt
@@ -1,5 +1,6 @@
 ^CMake Error at TARGETS-NAMELINK_COMPONENT-bad-all\.cmake:5 \(install\):
-  install TARGETS given NAMELINK_COMPONENT option not in LIBRARY group\.  The
-  NAMELINK_COMPONENT option may be specified only following LIBRARY\.
+  install TARGETS given NAMELINK_COMPONENT option not in LIBRARY or ARCHIVE
+  group\.  The NAMELINK_COMPONENT option may be specified only following
+  LIBRARY or ARCHIVE\.
 Call Stack \(most recent call first\):
   CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/install/TARGETS-NAMELINK_COMPONENT-bad-exc-stderr.txt b/Tests/RunCMake/install/TARGETS-NAMELINK_COMPONENT-bad-exc-stderr.txt
index 60f52c4..8aed62b 100644
--- a/Tests/RunCMake/install/TARGETS-NAMELINK_COMPONENT-bad-exc-stderr.txt
+++ b/Tests/RunCMake/install/TARGETS-NAMELINK_COMPONENT-bad-exc-stderr.txt
@@ -1,5 +1,6 @@
 ^CMake Error at TARGETS-NAMELINK_COMPONENT-bad-exc\.cmake:5 \(install\):
-  install TARGETS given NAMELINK_COMPONENT option not in LIBRARY group\.  The
-  NAMELINK_COMPONENT option may be specified only following LIBRARY\.
+  install TARGETS given NAMELINK_COMPONENT option not in LIBRARY or ARCHIVE
+  group\.  The NAMELINK_COMPONENT option may be specified only following
+  LIBRARY or ARCHIVE\.
 Call Stack \(most recent call first\):
   CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/install/TARGETS-RPATH.cmake b/Tests/RunCMake/install/TARGETS-RPATH.cmake
index b75deff..3e182f8 100644
--- a/Tests/RunCMake/install/TARGETS-RPATH.cmake
+++ b/Tests/RunCMake/install/TARGETS-RPATH.cmake
@@ -1,5 +1,4 @@
-cmake_minimum_required(VERSION 3.9)
-
+cmake_policy(SET CMP0068 NEW)
 enable_language(C)
 
 set(CMAKE_BUILD_WITH_INSTALL_RPATH 1)
diff --git a/Tests/RunCMake/install/empty3.cmake b/Tests/RunCMake/install/empty3.cmake
new file mode 100644
index 0000000..18c2ac5
--- /dev/null
+++ b/Tests/RunCMake/install/empty3.cmake
@@ -0,0 +1 @@
+write_empty_file(empty3.txt)
diff --git a/Tests/RunCMake/install/empty4.cmake b/Tests/RunCMake/install/empty4.cmake
new file mode 100644
index 0000000..026a4d2
--- /dev/null
+++ b/Tests/RunCMake/install/empty4.cmake
@@ -0,0 +1 @@
+write_empty_file(empty4.txt)
diff --git a/Tests/RunCMake/list/CMakeLists.txt b/Tests/RunCMake/list/CMakeLists.txt
index 4b3de84..93ee9df 100644
--- a/Tests/RunCMake/list/CMakeLists.txt
+++ b/Tests/RunCMake/list/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/list/GET-CMP0007-WARN-stderr.txt b/Tests/RunCMake/list/GET-CMP0007-WARN-stderr.txt
index 9103bd2..5dd76f7 100644
--- a/Tests/RunCMake/list/GET-CMP0007-WARN-stderr.txt
+++ b/Tests/RunCMake/list/GET-CMP0007-WARN-stderr.txt
@@ -1,13 +1,13 @@
-^CMake Deprecation Warning at GET-CMP0007-WARN.cmake:1 \(cmake_policy\):
-  Compatibility with CMake < 2.8.12 will be removed from a future version of
-  CMake.
+^CMake Deprecation Warning at GET-CMP0007-WARN\.cmake:1 \(cmake_policy\):
+  Compatibility with CMake < 3\.5 will be removed from a future version of
+  CMake\.
 
-  Update the VERSION argument <min> value or use a ...<max> suffix to tell
-  CMake that the project does not need compatibility with older versions.
+  Update the VERSION argument <min> value or use a \.\.\.<max> suffix to tell
+  CMake that the project does not need compatibility with older versions\.
 Call Stack \(most recent call first\):
   CMakeLists.txt:3 \(include\)
 +
-CMake Warning \(dev\) at GET-CMP0007-WARN.cmake:4 \(list\):
+CMake Warning \(dev\) at GET-CMP0007-WARN\.cmake:4 \(list\):
   Policy CMP0007 is not set: list command no longer ignores empty elements.
   Run "cmake --help-policy CMP0007" for policy details.  Use the cmake_policy
   command to set the policy and suppress this warning.  List has value =
diff --git a/Tests/RunCMake/math/CMakeLists.txt b/Tests/RunCMake/math/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/math/CMakeLists.txt
+++ b/Tests/RunCMake/math/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/message/CMakeLists.txt b/Tests/RunCMake/message/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/message/CMakeLists.txt
+++ b/Tests/RunCMake/message/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/message/RunCMakeTest.cmake b/Tests/RunCMake/message/RunCMakeTest.cmake
index c54e8f2..786b49b 100644
--- a/Tests/RunCMake/message/RunCMakeTest.cmake
+++ b/Tests/RunCMake/message/RunCMakeTest.cmake
@@ -10,7 +10,7 @@
 run_cmake(nomessage-internal-warning)
 run_cmake(warnmessage)
 
-# Have to explicitly give the command for the working dir to be honoured
+# Have to explicitly give the command for the working dir to be honored
 set(RunCMake_TEST_COMMAND_WORKING_DIRECTORY /)
 run_cmake_command(
     warnmessage-rootdir
diff --git a/Tests/RunCMake/message/warnmessage-rootdir.cmake b/Tests/RunCMake/message/warnmessage-rootdir.cmake
index f82efb9..f400079 100644
--- a/Tests/RunCMake/message/warnmessage-rootdir.cmake
+++ b/Tests/RunCMake/message/warnmessage-rootdir.cmake
@@ -1,5 +1,3 @@
-cmake_minimum_required(VERSION 3.15)
-
 # Generating the backtrace for this warning message used to trigger a
 # spurious assertion when the current directory is the root directory
 message(WARNING "We expect to see this warning message")
diff --git a/Tests/RunCMake/no_install_prefix/CMakeLists.txt b/Tests/RunCMake/no_install_prefix/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/no_install_prefix/CMakeLists.txt
+++ b/Tests/RunCMake/no_install_prefix/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/project/CMP0048-NEW-stderr.txt b/Tests/RunCMake/project/CMP0048-NEW-stderr.txt
new file mode 100644
index 0000000..ad75f43
--- /dev/null
+++ b/Tests/RunCMake/project/CMP0048-NEW-stderr.txt
@@ -0,0 +1,6 @@
+^CMake Deprecation Warning at CMakeLists\.txt:[0-9]+ \(cmake_minimum_required\):
+  Compatibility with CMake < 3\.5 will be removed from a future version of
+  CMake\.
+
+  Update the VERSION argument <min> value or use a \.\.\.<max> suffix to tell
+  CMake that the project does not need compatibility with older versions\.$
diff --git a/Tests/RunCMake/project/CMP0048-OLD-stderr.txt b/Tests/RunCMake/project/CMP0048-OLD-stderr.txt
index 695fb70..376249b 100644
--- a/Tests/RunCMake/project/CMP0048-OLD-stderr.txt
+++ b/Tests/RunCMake/project/CMP0048-OLD-stderr.txt
@@ -1,6 +1,13 @@
-^CMake Deprecation Warning at CMP0048-OLD.cmake:1 \(cmake_policy\):
+^CMake Deprecation Warning at CMakeLists\.txt:[0-9]+ \(cmake_minimum_required\):
+  Compatibility with CMake < 3\.5 will be removed from a future version of
+  CMake\.
+
+  Update the VERSION argument <min> value or use a \.\.\.<max> suffix to tell
+  CMake that the project does not need compatibility with older versions\.
++
+CMake Deprecation Warning at CMP0048-OLD\.cmake:1 \(cmake_policy\):
   The OLD behavior for policy CMP0048 will be removed from a future version
-  of CMake.
+  of CMake\.
 
   The cmake-policies\(7\) manual explains that the OLD behaviors of all
   policies are deprecated and that a policy should be set to OLD only under
diff --git a/Tests/RunCMake/project/CMakeLists.txt b/Tests/RunCMake/project/CMakeLists.txt
index fdcaee9..a0ee6d7 100644
--- a/Tests/RunCMake/project/CMakeLists.txt
+++ b/Tests/RunCMake/project/CMakeLists.txt
@@ -1,5 +1,9 @@
-if(NOT "x${RunCMake_TEST}" STREQUAL "xNoMinimumRequired")
+if("x${RunCMake_TEST}" STREQUAL "xNoMinimumRequired")
+  # No cmake_minimum_required(VERSION)
+elseif(RunCMake_TEST MATCHES "^CMP0048")
   cmake_minimum_required(VERSION 2.8.12)
+else()
+  cmake_minimum_required(VERSION 3.5)
 endif()
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/project_injected/CMP0048-WARN-stderr.txt b/Tests/RunCMake/project_injected/CMP0048-WARN-stderr.txt
index fc1a02b..c72534f 100644
--- a/Tests/RunCMake/project_injected/CMP0048-WARN-stderr.txt
+++ b/Tests/RunCMake/project_injected/CMP0048-WARN-stderr.txt
@@ -1,4 +1,4 @@
-^CMake Warning \(dev\) in CMakeLists.txt:
+^CMake Warning \(dev\) in CMakeLists\.txt:
   No project\(\) command is present.  The top-level CMakeLists.txt file must
   contain a literal, direct call to the project\(\) command.  Add a line of
   code such as
@@ -8,5 +8,12 @@
   near the top of the file, but after cmake_minimum_required\(\).
 
   CMake is pretending there is a "project\(Project\)" command on the first
-  line.
-This warning is for project developers.  Use -Wno-dev to suppress it.$
+  line\.
+This warning is for project developers.  Use -Wno-dev to suppress it\.
++
+CMake Deprecation Warning at CMakeLists\.txt:1 \(cmake_minimum_required\):
+  Compatibility with CMake < 3\.5 will be removed from a future version of
+  CMake\.
+
+  Update the VERSION argument <min> value or use a \.\.\.<max> suffix to tell
+  CMake that the project does not need compatibility with older versions\.$
diff --git a/Tests/RunCMake/separate_arguments/CMakeLists.txt b/Tests/RunCMake/separate_arguments/CMakeLists.txt
index 2897109..93ee9df 100644
--- a/Tests/RunCMake/separate_arguments/CMakeLists.txt
+++ b/Tests/RunCMake/separate_arguments/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/set/CMakeLists.txt b/Tests/RunCMake/set/CMakeLists.txt
index 4b3de84..93ee9df 100644
--- a/Tests/RunCMake/set/CMakeLists.txt
+++ b/Tests/RunCMake/set/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/set/ParentPulling.cmake b/Tests/RunCMake/set/ParentPulling.cmake
index 2614533..5649cb2 100644
--- a/Tests/RunCMake/set/ParentPulling.cmake
+++ b/Tests/RunCMake/set/ParentPulling.cmake
@@ -1,6 +1,3 @@
-cmake_minimum_required(VERSION 3.0)
-project(Minimal NONE)
-
 function(test_set)
     set(blah "value2")
     message("before PARENT_SCOPE blah=${blah}")
diff --git a/Tests/RunCMake/set/ParentPullingRecursive.cmake b/Tests/RunCMake/set/ParentPullingRecursive.cmake
index a3e29f5..4d50561 100644
--- a/Tests/RunCMake/set/ParentPullingRecursive.cmake
+++ b/Tests/RunCMake/set/ParentPullingRecursive.cmake
@@ -1,6 +1,3 @@
-cmake_minimum_required(VERSION 3.0)
-project(Minimal NONE)
-
 function(report where)
     message("----------")
     message("variable values at ${where}:")
diff --git a/Tests/RunCMake/set_property/CMakeLists.txt b/Tests/RunCMake/set_property/CMakeLists.txt
index 18dfd26..93ee9df 100644
--- a/Tests/RunCMake/set_property/CMakeLists.txt
+++ b/Tests/RunCMake/set_property/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.2)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/string/CMakeLists.txt b/Tests/RunCMake/string/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/string/CMakeLists.txt
+++ b/Tests/RunCMake/string/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/string/RegexClear.cmake b/Tests/RunCMake/string/RegexClear.cmake
index d5edaac..4abe25e 100644
--- a/Tests/RunCMake/string/RegexClear.cmake
+++ b/Tests/RunCMake/string/RegexClear.cmake
@@ -1,6 +1,3 @@
-cmake_minimum_required (VERSION 3.0)
-project (RegexClear C)
-
 function (output_results msg)
   message("results from: ${msg}")
   message("CMAKE_MATCH_0: -->${CMAKE_MATCH_0}<--")
diff --git a/Tests/RunCMake/string/RegexMultiMatchClear.cmake b/Tests/RunCMake/string/RegexMultiMatchClear.cmake
index 80b6b3c..788ba5e 100644
--- a/Tests/RunCMake/string/RegexMultiMatchClear.cmake
+++ b/Tests/RunCMake/string/RegexMultiMatchClear.cmake
@@ -1,6 +1,3 @@
-cmake_minimum_required (VERSION 3.0)
-project (RegexClear NONE)
-
 function (output_results msg)
   message("results from: ${msg}")
   message("CMAKE_MATCH_0: -->${CMAKE_MATCH_0}<--")
diff --git a/Tests/RunCMake/target_compile_features/CMakeLists.txt b/Tests/RunCMake/target_compile_features/CMakeLists.txt
index 2897109..93ee9df 100644
--- a/Tests/RunCMake/target_compile_features/CMakeLists.txt
+++ b/Tests/RunCMake/target_compile_features/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/target_link_libraries-ALIAS/AliasTargets.cmake b/Tests/RunCMake/target_link_libraries-ALIAS/AliasTargets.cmake
index 316b74b..4a0f068 100644
--- a/Tests/RunCMake/target_link_libraries-ALIAS/AliasTargets.cmake
+++ b/Tests/RunCMake/target_link_libraries-ALIAS/AliasTargets.cmake
@@ -1,6 +1,3 @@
-
-cmake_minimum_required(VERSION 3.16...3.17)
-
 enable_language(C)
 
 add_library (func SHARED func.c)
diff --git a/Tests/RunCMake/target_link_libraries-LINK_LANGUAGE/genex.cmake b/Tests/RunCMake/target_link_libraries-LINK_LANGUAGE/genex.cmake
index 22d3df7..6610d40 100644
--- a/Tests/RunCMake/target_link_libraries-LINK_LANGUAGE/genex.cmake
+++ b/Tests/RunCMake/target_link_libraries-LINK_LANGUAGE/genex.cmake
@@ -1,6 +1,3 @@
-
-cmake_minimum_required(VERSION 3.16...3.17)
-
 enable_language(C)
 enable_language(CXX)
 
diff --git a/Tests/RunCMake/target_link_libraries-LINK_LANG_AND_ID/genex.cmake b/Tests/RunCMake/target_link_libraries-LINK_LANG_AND_ID/genex.cmake
index 9feccd0..e2bd669 100644
--- a/Tests/RunCMake/target_link_libraries-LINK_LANG_AND_ID/genex.cmake
+++ b/Tests/RunCMake/target_link_libraries-LINK_LANG_AND_ID/genex.cmake
@@ -1,6 +1,3 @@
-
-cmake_minimum_required(VERSION 3.16...3.17)
-
 enable_language(C)
 enable_language(CXX)
 
diff --git a/Tests/RunCMake/target_link_libraries/CMP0023-WARN-2.cmake b/Tests/RunCMake/target_link_libraries/CMP0023-WARN-2.cmake
index 6c72546..a5bf2fb 100644
--- a/Tests/RunCMake/target_link_libraries/CMP0023-WARN-2.cmake
+++ b/Tests/RunCMake/target_link_libraries/CMP0023-WARN-2.cmake
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.11)
+cmake_policy(VERSION 2.8.11)
 project(CMP0022-WARN)
 
 add_library(foo SHARED empty_vs6_1.cpp)
diff --git a/Tests/RunCMake/target_link_libraries/CMP0023-WARN.cmake b/Tests/RunCMake/target_link_libraries/CMP0023-WARN.cmake
index dfdf70b..d6f34a1 100644
--- a/Tests/RunCMake/target_link_libraries/CMP0023-WARN.cmake
+++ b/Tests/RunCMake/target_link_libraries/CMP0023-WARN.cmake
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.11)
+cmake_policy(VERSION 2.8.11)
 project(CMP0022-WARN)
 
 add_library(foo SHARED empty_vs6_1.cpp)
diff --git a/Tests/RunCMake/target_link_libraries/CMakeLists.txt b/Tests/RunCMake/target_link_libraries/CMakeLists.txt
index 667561e..8eb5748 100644
--- a/Tests/RunCMake/target_link_libraries/CMakeLists.txt
+++ b/Tests/RunCMake/target_link_libraries/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake NO_POLICY_SCOPE)
diff --git a/Tests/RunCMake/target_link_libraries/RunCMakeTest.cmake b/Tests/RunCMake/target_link_libraries/RunCMakeTest.cmake
index 189592d..7c5d77d 100644
--- a/Tests/RunCMake/target_link_libraries/RunCMakeTest.cmake
+++ b/Tests/RunCMake/target_link_libraries/RunCMakeTest.cmake
@@ -1,4 +1,5 @@
 include(RunCMake)
+set(RunCMake_IGNORE_POLICY_VERSION_DEPRECATION ON)
 
 if(RunCMake_GENERATOR_IS_MULTI_CONFIG)
   set(RunCMake_TEST_OPTIONS -DCMAKE_CONFIGURATION_TYPES=Debug)
diff --git a/Tests/RunCMake/target_sources/CMP0076-WARN.cmake b/Tests/RunCMake/target_sources/CMP0076-WARN.cmake
index 2e07331..20f1d5e 100644
--- a/Tests/RunCMake/target_sources/CMP0076-WARN.cmake
+++ b/Tests/RunCMake/target_sources/CMP0076-WARN.cmake
@@ -1,5 +1,3 @@
-cmake_minimum_required(VERSION 3.12)
-
 add_library(publiclib)
 
 add_subdirectory(CMP0076-WARN)
diff --git a/Tests/RunCMake/target_sources/CMakeLists.txt b/Tests/RunCMake/target_sources/CMakeLists.txt
index 727f93a..296fdda 100644
--- a/Tests/RunCMake/target_sources/CMakeLists.txt
+++ b/Tests/RunCMake/target_sources/CMakeLists.txt
@@ -1,3 +1,3 @@
 cmake_minimum_required(VERSION 3.11)
 project(${RunCMake_TEST} LANGUAGES NONE)
-include(${RunCMake_TEST}.cmake)
+include(${RunCMake_TEST}.cmake NO_POLICY_SCOPE)
diff --git a/Tests/RunCMake/target_sources/FileSetDirect-result.txt b/Tests/RunCMake/target_sources/FileSetDirect-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/target_sources/FileSetDirect-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/target_sources/FileSetDirect-stderr.txt b/Tests/RunCMake/target_sources/FileSetDirect-stderr.txt
new file mode 100644
index 0000000..06458b9
--- /dev/null
+++ b/Tests/RunCMake/target_sources/FileSetDirect-stderr.txt
@@ -0,0 +1,12 @@
+CMake Error at FileSetDirect.cmake:3 \(add_library\):
+  Cannot find source file:
+
+    FILE_SET
+
+  Tried extensions ([^
+]+
+)+
+  Hint: the FILE_SET keyword may only appear after a visibility specifier or
+  another FILE_SET within the target_sources\(\) command.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/target_sources/FileSetDirect.cmake b/Tests/RunCMake/target_sources/FileSetDirect.cmake
new file mode 100644
index 0000000..9f412c8
--- /dev/null
+++ b/Tests/RunCMake/target_sources/FileSetDirect.cmake
@@ -0,0 +1,3 @@
+enable_language(C)
+
+add_library(lib1 STATIC empty.c FILE_SET h1.h TYPE HEADERS)
diff --git a/Tests/RunCMake/target_sources/FileSetFileNoExist-stderr.txt b/Tests/RunCMake/target_sources/FileSetFileNoExist-stderr.txt
index 9a2ca6a..2b5db43 100644
--- a/Tests/RunCMake/target_sources/FileSetFileNoExist-stderr.txt
+++ b/Tests/RunCMake/target_sources/FileSetFileNoExist-stderr.txt
@@ -1,4 +1,4 @@
-^CMake Error at FileSetFileNoExist\.cmake:[0-9]+ \(add_library\):
+^CMake Error at FileSetFileNoExist\.cmake:[0-9]+ \(target_sources\):
   Cannot find source file:
 
     [^
diff --git a/Tests/RunCMake/target_sources/FileSetWrongSyntax-result.txt b/Tests/RunCMake/target_sources/FileSetWrongSyntax-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/target_sources/FileSetWrongSyntax-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/target_sources/FileSetWrongSyntax-stderr.txt b/Tests/RunCMake/target_sources/FileSetWrongSyntax-stderr.txt
new file mode 100644
index 0000000..01db002
--- /dev/null
+++ b/Tests/RunCMake/target_sources/FileSetWrongSyntax-stderr.txt
@@ -0,0 +1,12 @@
+CMake Error at FileSetWrongSyntax.cmake:4 \(target_sources\):
+  Cannot find source file:
+
+    FILE_SET
+
+  Tried extensions ([^
+]+
+)+
+  Hint: the FILE_SET keyword may only appear after a visibility specifier or
+  another FILE_SET within the target_sources\(\) command.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/target_sources/FileSetWrongSyntax.cmake b/Tests/RunCMake/target_sources/FileSetWrongSyntax.cmake
new file mode 100644
index 0000000..709fb23
--- /dev/null
+++ b/Tests/RunCMake/target_sources/FileSetWrongSyntax.cmake
@@ -0,0 +1,4 @@
+enable_language(C)
+
+add_library(lib1 STATIC)
+target_sources(lib1 PRIVATE empty.c FILE_SET h1.h TYPE HEADERS)
diff --git a/Tests/RunCMake/target_sources/MissingSource-result.txt b/Tests/RunCMake/target_sources/MissingSource-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/target_sources/MissingSource-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/target_sources/MissingSource-stderr.txt b/Tests/RunCMake/target_sources/MissingSource-stderr.txt
new file mode 100644
index 0000000..b4c81c2
--- /dev/null
+++ b/Tests/RunCMake/target_sources/MissingSource-stderr.txt
@@ -0,0 +1,6 @@
+^CMake Error at MissingSource\.cmake:[0-9]+ \(target_sources\):
+  Cannot find source file:
+
+    missing\.txt
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/target_sources/MissingSource.cmake b/Tests/RunCMake/target_sources/MissingSource.cmake
new file mode 100644
index 0000000..2bb71e4
--- /dev/null
+++ b/Tests/RunCMake/target_sources/MissingSource.cmake
@@ -0,0 +1,3 @@
+cmake_policy(SET CMP0115 NEW)
+add_custom_target(foo)
+target_sources(foo PRIVATE missing.txt)
diff --git a/Tests/RunCMake/target_sources/OriginDebug-stderr.txt b/Tests/RunCMake/target_sources/OriginDebug-stderr.txt
index 502d5f1..fd797cf 100644
--- a/Tests/RunCMake/target_sources/OriginDebug-stderr.txt
+++ b/Tests/RunCMake/target_sources/OriginDebug-stderr.txt
@@ -1,4 +1,4 @@
-CMake Debug Log at OriginDebug.cmake:13 \(add_library\):
+CMake Debug Log at OriginDebug\.cmake:10 \(add_library\):
   Used sources for target OriginDebug:
 
    \* .*Tests/RunCMake/target_sources/empty_2.cpp
@@ -6,7 +6,7 @@
 Call Stack \(most recent call first\):
   CMakeLists\.txt:[0-9]+ \(include\)
 .*
-CMake Debug Log at OriginDebug.cmake:16 \(set_property\):
+CMake Debug Log at OriginDebug\.cmake:13 \(set_property\):
   Used sources for target OriginDebug:
 
    \* .*Tests/RunCMake/target_sources/empty_3.cpp
@@ -14,7 +14,7 @@
 Call Stack \(most recent call first\):
   CMakeLists\.txt:[0-9]+ \(include\)
 .*
-CMake Debug Log at OriginDebug.cmake:20 \(target_sources\):
+CMake Debug Log at OriginDebug\.cmake:17 \(target_sources\):
   Used sources for target OriginDebug:
 
    \* .*Tests/RunCMake/target_sources/empty_4.cpp
@@ -22,7 +22,7 @@
 Call Stack \(most recent call first\):
   CMakeLists\.txt:[0-9]+ \(include\)
 .*
-CMake Debug Log at OriginDebug.cmake:14 \(target_link_libraries\):
+CMake Debug Log at OriginDebug\.cmake:11 \(target_link_libraries\):
   Used sources for target OriginDebug:
 
    \* .*Tests/RunCMake/target_sources/empty_1.cpp
diff --git a/Tests/RunCMake/target_sources/OriginDebug.cmake b/Tests/RunCMake/target_sources/OriginDebug.cmake
index d40a1d8..e2d8477 100644
--- a/Tests/RunCMake/target_sources/OriginDebug.cmake
+++ b/Tests/RunCMake/target_sources/OriginDebug.cmake
@@ -1,7 +1,4 @@
-
-cmake_minimum_required(VERSION 3.0)
-
-project(OriginDebug)
+enable_language(CXX)
 
 set(CMAKE_DEBUG_TARGET_PROPERTIES SOURCES)
 
diff --git a/Tests/RunCMake/target_sources/RunCMakeTest.cmake b/Tests/RunCMake/target_sources/RunCMakeTest.cmake
index 7c67c3f..90915cd 100644
--- a/Tests/RunCMake/target_sources/RunCMakeTest.cmake
+++ b/Tests/RunCMake/target_sources/RunCMakeTest.cmake
@@ -9,6 +9,7 @@
 run_cmake(CMP0026-LOCATION)
 run_cmake(CMP0076-OLD)
 run_cmake(CMP0076-WARN)
+run_cmake(MissingSource)
 run_cmake(RelativePathInInterface)
 run_cmake(RelativePathInSubdirGenEx)
 run_cmake(RelativePathInSubdirInterface)
@@ -43,6 +44,8 @@
 run_cmake(FileSetDirectories)
 run_cmake(FileSetCustomTarget)
 run_cmake(FileSetBadName)
+run_cmake(FileSetWrongSyntax)
+run_cmake(FileSetDirect)
 if(APPLE)
   run_cmake(FileSetFramework)
 endif()
diff --git a/Tests/RunCMake/try_compile/CMP0056.cmake b/Tests/RunCMake/try_compile/CMP0056.cmake
index 2ab79d5..634576e 100644
--- a/Tests/RunCMake/try_compile/CMP0056.cmake
+++ b/Tests/RunCMake/try_compile/CMP0056.cmake
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_policy(VERSION 3.1)
 enable_language(C)
 set(obj "${CMAKE_C_OUTPUT_EXTENSION}")
 if(BORLAND)
diff --git a/Tests/RunCMake/try_compile/CMakeLists.txt b/Tests/RunCMake/try_compile/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/try_compile/CMakeLists.txt
+++ b/Tests/RunCMake/try_compile/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/try_run/CMakeLists.txt b/Tests/RunCMake/try_run/CMakeLists.txt
index e93f0b6..e6c41a5 100644
--- a/Tests/RunCMake/try_run/CMakeLists.txt
+++ b/Tests/RunCMake/try_run/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} C)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/variable_watch/CMakeLists.txt b/Tests/RunCMake/variable_watch/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/variable_watch/CMakeLists.txt
+++ b/Tests/RunCMake/variable_watch/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/while/CMakeLists.txt b/Tests/RunCMake/while/CMakeLists.txt
index 74b3ff8..93ee9df 100644
--- a/Tests/RunCMake/while/CMakeLists.txt
+++ b/Tests/RunCMake/while/CMakeLists.txt
@@ -1,3 +1,3 @@
-cmake_minimum_required(VERSION 3.3)
+cmake_minimum_required(VERSION 3.5)
 project(${RunCMake_TEST} NONE)
 include(${RunCMake_TEST}.cmake)
diff --git a/Tests/StagingPrefix/Consumer/CMakeLists.txt b/Tests/StagingPrefix/Consumer/CMakeLists.txt
index a230441..f7195b0 100644
--- a/Tests/StagingPrefix/Consumer/CMakeLists.txt
+++ b/Tests/StagingPrefix/Consumer/CMakeLists.txt
@@ -1,5 +1,5 @@
 
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(Consumer)
 
 
diff --git a/Tests/StagingPrefix/Producer/CMakeLists.txt b/Tests/StagingPrefix/Producer/CMakeLists.txt
index eb3d98f..c02d5de 100644
--- a/Tests/StagingPrefix/Producer/CMakeLists.txt
+++ b/Tests/StagingPrefix/Producer/CMakeLists.txt
@@ -1,5 +1,5 @@
 
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(Producer)
 
 add_library(foo SHARED foo.cpp)
diff --git a/Tests/StringFileTest/CMakeLists.txt b/Tests/StringFileTest/CMakeLists.txt
index 068fae9..6c0de9d 100644
--- a/Tests/StringFileTest/CMakeLists.txt
+++ b/Tests/StringFileTest/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(StringFileTest)
 include_directories(${StringFileTest_BINARY_DIR})
 
diff --git a/Tests/SubDirSpaces/CMakeLists.txt b/Tests/SubDirSpaces/CMakeLists.txt
index ecd4353..26a7da9 100644
--- a/Tests/SubDirSpaces/CMakeLists.txt
+++ b/Tests/SubDirSpaces/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(SUBDIR)
 
 # Some systems do not seem to support rpath with spaces.
diff --git a/Tests/TargetName/CMakeLists.txt b/Tests/TargetName/CMakeLists.txt
index 21752b7..9864001 100644
--- a/Tests/TargetName/CMakeLists.txt
+++ b/Tests/TargetName/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(TargetName)
 
 add_subdirectory(executables)
diff --git a/Tests/TestDriver/CMakeLists.txt b/Tests/TestDriver/CMakeLists.txt
index 3cc69c0..cd79372 100644
--- a/Tests/TestDriver/CMakeLists.txt
+++ b/Tests/TestDriver/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(TestDriverTest)
 
 set(Extra_SRCS testExtraStuff.cxx testExtraStuff2.cxx )
diff --git a/Tests/Testing/CMakeLists.txt b/Tests/Testing/CMakeLists.txt
index 8f69cbe..44afd4e 100644
--- a/Tests/Testing/CMakeLists.txt
+++ b/Tests/Testing/CMakeLists.txt
@@ -4,6 +4,8 @@
 cmake_minimum_required (VERSION 2.7)
 project (Testing)
 
+include (CTest)
+
 #
 # Lib and exe path
 #
@@ -25,12 +27,6 @@
 endif ()
 
 #
-# Include Dart
-# (will also set NSLOOKUP, HOSTNAME, etc.)
-#
-include (${CMAKE_ROOT}/Modules/Dart.cmake)
-
-#
 # Extra coverage
 #
 build_command(BUILD_COMMAND_VAR ${CMAKE_MAKE_PROGRAM})
diff --git a/Tests/TestsWorkingDirectory/CMakeLists.txt b/Tests/TestsWorkingDirectory/CMakeLists.txt
index 2a0b015..f77370c 100644
--- a/Tests/TestsWorkingDirectory/CMakeLists.txt
+++ b/Tests/TestsWorkingDirectory/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(TestsWorkingDirectoryProj)
 
 add_executable(WorkingDirectory main.c)
diff --git a/Tests/TryCompile/CMakeLists.txt b/Tests/TryCompile/CMakeLists.txt
index 3e46ed5..ab2e007 100644
--- a/Tests/TryCompile/CMakeLists.txt
+++ b/Tests/TryCompile/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 if(POLICY CMP0129)
   cmake_policy(SET CMP0129 NEW)
 endif()
diff --git a/Tests/TryCompile/Inner/CMakeLists.txt b/Tests/TryCompile/Inner/CMakeLists.txt
index 9f89af2..262662d 100644
--- a/Tests/TryCompile/Inner/CMakeLists.txt
+++ b/Tests/TryCompile/Inner/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(TryCompileInner C)
 
 try_compile(SHOULD_PASS
diff --git a/Tests/VSExternalInclude/CMakeLists.txt b/Tests/VSExternalInclude/CMakeLists.txt
index a3fd8d8..a44988e 100644
--- a/Tests/VSExternalInclude/CMakeLists.txt
+++ b/Tests/VSExternalInclude/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.8.12)
+cmake_minimum_required (VERSION 3.5)
 project(VSExternalInclude)
 
 if(${CMAKE_GENERATOR} MATCHES "Visual Studio 1[0124567]")
diff --git a/Tests/VSGNUFortran/subdir/fortran/CMakeLists.txt b/Tests/VSGNUFortran/subdir/fortran/CMakeLists.txt
index 950ec25..8918d98 100644
--- a/Tests/VSGNUFortran/subdir/fortran/CMakeLists.txt
+++ b/Tests/VSGNUFortran/subdir/fortran/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(FortranHello Fortran C)
 
 # add a function to test for -lsunquad on sunpro sun systems.
diff --git a/Tests/VSMidl/CMakeLists.txt b/Tests/VSMidl/CMakeLists.txt
index 342b8fb..f24640f 100644
--- a/Tests/VSMidl/CMakeLists.txt
+++ b/Tests/VSMidl/CMakeLists.txt
@@ -12,7 +12,7 @@
 message(STATUS "CMAKE_BUILDNAME='${CMAKE_BUILDNAME}'")
 message(STATUS "THIS_TESTNAME='${THIS_TESTNAME}'")
 
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(${THIS_TESTNAME})
 
 include(ExternalProject)
diff --git a/Tests/VSMidl/src/CMakeLists.txt b/Tests/VSMidl/src/CMakeLists.txt
index 7e838b4..d8fd934 100644
--- a/Tests/VSMidl/src/CMakeLists.txt
+++ b/Tests/VSMidl/src/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(VSMidl)
 
 include_directories("${CMAKE_CURRENT_BINARY_DIR}/\$(IntDir)")
diff --git a/Tests/VSNASM/CMakeLists.txt b/Tests/VSNASM/CMakeLists.txt
index a038ddd..6f82425 100644
--- a/Tests/VSNASM/CMakeLists.txt
+++ b/Tests/VSNASM/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
 project(VSNASM C ASM_NASM)
 
 if(CMAKE_SIZEOF_VOID_P EQUAL 8)
diff --git a/Utilities/ClangTidyModule/.gitignore b/Utilities/ClangTidyModule/.gitignore
new file mode 100644
index 0000000..e3f48eb
--- /dev/null
+++ b/Utilities/ClangTidyModule/.gitignore
@@ -0,0 +1,8 @@
+/CMakeUserPresets.json
+
+# Common build directories
+/build*/
+
+# Visual Studio Code
+/.vscode/
+/.cache/
diff --git a/Utilities/ClangTidyModule/CMakeLists.txt b/Utilities/ClangTidyModule/CMakeLists.txt
index 97c176f..582d54a 100644
--- a/Utilities/ClangTidyModule/CMakeLists.txt
+++ b/Utilities/ClangTidyModule/CMakeLists.txt
@@ -6,11 +6,15 @@
 get_filename_component(tmp "${CMAKE_CURRENT_SOURCE_DIR}" PATH)
 get_filename_component(CMake_SOURCE_DIR "${tmp}" PATH)
 
-set(CMAKE_CXX_STANDARD 14)
-set(CMAKE_CXX_STANDARD_REQUIRED ON)
-
 find_package(Clang REQUIRED)
 
+if(LLVM_VERSION_MAJOR GREATER_EQUAL 16)
+  set(CMAKE_CXX_STANDARD 17)
+else()
+  set(CMAKE_CXX_STANDARD 14)
+endif()
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
 add_library(cmake-clang-tidy-module MODULE
   Module.cxx
 
diff --git a/Utilities/ClangTidyModule/UsePragmaOnceCheck.cxx b/Utilities/ClangTidyModule/UsePragmaOnceCheck.cxx
index 7a42798..37ecd70 100644
--- a/Utilities/ClangTidyModule/UsePragmaOnceCheck.cxx
+++ b/Utilities/ClangTidyModule/UsePragmaOnceCheck.cxx
@@ -218,8 +218,6 @@
         this
           ->EndIfs[this->Ifndefs[MacroEntry.first.getIdentifierInfo()].first];
 
-      StringRef CurHeaderGuard =
-        MacroEntry.first.getIdentifierInfo()->getName();
       std::vector<FixItHint> FixIts;
 
       HeaderSearch& HeaderInfo = this->PP->getHeaderSearchInfo();
diff --git a/Utilities/Doxygen/CMakeLists.txt b/Utilities/Doxygen/CMakeLists.txt
index b084dd5..fee21b6 100644
--- a/Utilities/Doxygen/CMakeLists.txt
+++ b/Utilities/Doxygen/CMakeLists.txt
@@ -3,7 +3,7 @@
 
 if(NOT CMake_SOURCE_DIR)
   set(CMakeDeveloperReference_STANDALONE 1)
-  cmake_minimum_required(VERSION 3.13...3.24 FATAL_ERROR)
+  cmake_minimum_required(VERSION 3.13...3.25 FATAL_ERROR)
   get_filename_component(tmp "${CMAKE_CURRENT_SOURCE_DIR}" PATH)
   get_filename_component(CMake_SOURCE_DIR "${tmp}" PATH)
   include(${CMake_SOURCE_DIR}/Modules/CTestUseLaunchers.cmake)
diff --git a/Utilities/IWYU/mapping.imp b/Utilities/IWYU/mapping.imp
index 1afad43..6c12ada 100644
--- a/Utilities/IWYU/mapping.imp
+++ b/Utilities/IWYU/mapping.imp
@@ -22,6 +22,7 @@
 
   # HACK: check whether this can be removed with next iwyu release.
   { include: [ "<bits/cxxabi_forced.h>", private, "<ctime>", public ] },
+  { include: [ "<bits/exception.h>", private, "<exception>", public ] },
   { include: [ "<bits/shared_ptr.h>", private, "<memory>", public ] },
   { include: [ "<bits/std_function.h>", private, "<functional>", public ] },
   { include: [ "<bits/refwrap.h>", private, "<functional>", public ] },
@@ -99,6 +100,9 @@
   { symbol: [ "std::enable_if<true, std::chrono::duration<long, std::ratio<60, 1> > >::type", private, "\"cmConfigure.h\"", public ] },
   { symbol: [ "std::enable_if<true, std::chrono::duration<long, std::ratio<1, 1000> > >::type", private, "\"cmConfigure.h\"", public ] },
   { symbol: [ "__gnu_cxx::__enable_if<true, bool>::__type", private, "\"cmConfigure.h\"", public ] },
+  { symbol: [ "std::remove_reference<std::basic_string<char, std::char_traits<char>, std::allocator<char> > &>::type", private, "\"cmConfigure.h\"", public ] },
+  { symbol: [ "std::remove_reference<Defer &>::type", private, "\"cmConfigure.h\"", public ] },
+  { symbol: [ "std::remove_reference<dap::StoppedEvent &>::type", private, "\"cmConfigure.h\"", public ] },
 
   # Wrappers for 3rd-party libraries
   { include: [ "@<.*curl/curlver.h>", private, "<cm3p/curl/curl.h>", public ] },
diff --git a/Utilities/Release/linux/aarch64/cache.txt b/Utilities/Release/linux/aarch64/cache.txt
index 87851d5..70739d4 100644
--- a/Utilities/Release/linux/aarch64/cache.txt
+++ b/Utilities/Release/linux/aarch64/cache.txt
@@ -32,7 +32,7 @@
 QHELPGENERATOR_EXECUTABLE:PATH=/opt/qt/bin/qhelpgenerator
 
 # We bootstrap as part of the build so skip its test.
-CMAKE_SKIP_BOOTSTRAP_TEST:STRING=TRUE
+CMake_TEST_BOOTSTRAP:BOOL=FALSE
 
 # Skip Qt5 tests because our Qt is static.
 CMake_TEST_Qt5:BOOL=FALSE
diff --git a/Utilities/Release/linux/x86_64/cache.txt b/Utilities/Release/linux/x86_64/cache.txt
index d32c3dd..0b9696b 100644
--- a/Utilities/Release/linux/x86_64/cache.txt
+++ b/Utilities/Release/linux/x86_64/cache.txt
@@ -32,7 +32,7 @@
 QHELPGENERATOR_EXECUTABLE:PATH=/opt/qt/bin/qhelpgenerator
 
 # We bootstrap as part of the build so skip its test.
-CMAKE_SKIP_BOOTSTRAP_TEST:STRING=TRUE
+CMake_TEST_BOOTSTRAP:BOOL=FALSE
 
 # Skip Qt5 tests because our Qt is static.
 CMake_TEST_Qt5:BOOL=FALSE
diff --git a/Utilities/Scripts/update-cppdap.bash b/Utilities/Scripts/update-cppdap.bash
new file mode 100755
index 0000000..fd4f8cb
--- /dev/null
+++ b/Utilities/Scripts/update-cppdap.bash
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+
+set -e
+set -x
+shopt -s dotglob
+
+readonly name="cppdap"
+readonly ownership="cppdap Upstream <kwrobot@kitware.com>"
+readonly subtree="Utilities/cmcppdap"
+readonly repo="https://github.com/google/cppdap.git"
+readonly tag="03cc18678ed2ed8b2424ec99dee7e4655d876db5" # 2023-05-25
+readonly shortlog=false
+readonly paths="
+  LICENSE
+  include
+  src
+"
+
+extract_source () {
+    git_archive
+
+    pushd "${extractdir}/${name}-reduced"
+    echo "* -whitespace" > .gitattributes
+    fromdos LICENSE include/dap/* src/*
+    echo "" >> LICENSE
+    echo "" >> src/nlohmann_json_serializer.h
+    popd
+}
+
+. "${BASH_SOURCE%/*}/update-third-party.bash"
diff --git a/Utilities/Scripts/update-curl.bash b/Utilities/Scripts/update-curl.bash
index 856ae81..5270903 100755
--- a/Utilities/Scripts/update-curl.bash
+++ b/Utilities/Scripts/update-curl.bash
@@ -8,7 +8,7 @@
 readonly ownership="Curl Upstream <curl-library@lists.haxx.se>"
 readonly subtree="Utilities/cmcurl"
 readonly repo="https://github.com/curl/curl.git"
-readonly tag="curl-7_87_0"
+readonly tag="curl-8_0_1"
 readonly shortlog=false
 readonly paths="
   CMake/*
diff --git a/Utilities/Scripts/update-libarchive.bash b/Utilities/Scripts/update-libarchive.bash
index 856a6ea..5a4f11a 100755
--- a/Utilities/Scripts/update-libarchive.bash
+++ b/Utilities/Scripts/update-libarchive.bash
@@ -8,7 +8,7 @@
 readonly ownership="LibArchive Upstream <libarchive-discuss@googlegroups.com>"
 readonly subtree="Utilities/cmlibarchive"
 readonly repo="https://github.com/libarchive/libarchive.git"
-readonly tag="v3.6.0"
+readonly tag="v3.6.2"
 readonly shortlog=false
 readonly paths="
   CMakeLists.txt
diff --git a/Utilities/Sphinx/CMakeLists.txt b/Utilities/Sphinx/CMakeLists.txt
index a9aa47d..bde6c6b 100644
--- a/Utilities/Sphinx/CMakeLists.txt
+++ b/Utilities/Sphinx/CMakeLists.txt
@@ -3,7 +3,7 @@
 
 if(NOT CMake_SOURCE_DIR)
   set(CMakeHelp_STANDALONE 1)
-  cmake_minimum_required(VERSION 3.13...3.24 FATAL_ERROR)
+  cmake_minimum_required(VERSION 3.13...3.25 FATAL_ERROR)
   get_filename_component(tmp "${CMAKE_CURRENT_SOURCE_DIR}" PATH)
   get_filename_component(CMake_SOURCE_DIR "${tmp}" PATH)
   include(${CMake_SOURCE_DIR}/Modules/CTestUseLaunchers.cmake)
diff --git a/Utilities/Sphinx/cmake.py b/Utilities/Sphinx/cmake.py
index e4e06ab..ffef5b3 100644
--- a/Utilities/Sphinx/cmake.py
+++ b/Utilities/Sphinx/cmake.py
@@ -1,103 +1,131 @@
 # Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
 # file Copyright.txt or https://cmake.org/licensing for details.
 
+# BEGIN imports
+
 import os
 import re
 
+from dataclasses import dataclass
+from typing import Any, List, Tuple, Type, cast
+
+import sphinx
+
+from docutils.utils.code_analyzer import Lexer, LexerError
+from docutils.parsers.rst import Directive, directives
+from docutils.transforms import Transform
+from docutils.nodes import Element, Node, TextElement, system_message
+from docutils import io, nodes
+
+from sphinx.directives import ObjectDescription, nl_escape_re
+from sphinx.domains import Domain, ObjType
+from sphinx.roles import XRefRole
+from sphinx.util.docutils import ReferenceRole
+from sphinx.util.nodes import make_refnode
+from sphinx.util import logging, ws_re
+from sphinx import addnodes
+
+# END imports
+
+# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+# BEGIN pygments tweaks
+
 # Override much of pygments' CMakeLexer.
 # We need to parse CMake syntax definitions, not CMake code.
 
 # For hard test cases that use much of the syntax below, see
-# - module/FindPkgConfig.html (with "glib-2.0>=2.10 gtk+-2.0" and similar)
-# - module/ExternalProject.html (with http:// https:// git@; also has command options -E --build)
-# - manual/cmake-buildsystem.7.html (with nested $<..>; relative and absolute paths, "::")
+# - module/FindPkgConfig.html
+#     (with "glib-2.0>=2.10 gtk+-2.0" and similar)
+# - module/ExternalProject.html
+#     (with http:// https:// git@; also has command options -E --build)
+# - manual/cmake-buildsystem.7.html
+#     (with nested $<..>; relative and absolute paths, "::")
 
 from pygments.lexers import CMakeLexer
-from pygments.token import Name, Operator, Punctuation, String, Text, Comment, Generic, Whitespace, Number
+from pygments.token import (Comment, Name, Number, Operator, Punctuation,
+                            String, Text, Whitespace)
 from pygments.lexer import bygroups
 
 # Notes on regular expressions below:
 # - [\.\+-] are needed for string constants like gtk+-2.0
-# - Unix paths are recognized by '/'; support for Windows paths may be added if needed
+# - Unix paths are recognized by '/'; support for Windows paths may be added
+#   if needed
 # - (\\.) allows for \-escapes (used in manual/cmake-language.7)
 # - $<..$<..$>..> nested occurrence in cmake-buildsystem
-# - Nested variable evaluations are only supported in a limited capacity. Only
-#   one level of nesting is supported and at most one nested variable can be present.
+# - Nested variable evaluations are only supported in a limited capacity.
+#   Only one level of nesting is supported and at most one nested variable can
+#   be present.
 
 CMakeLexer.tokens["root"] = [
-  (r'\b(\w+)([ \t]*)(\()', bygroups(Name.Function, Text, Name.Function), '#push'),     # fctn(
+  # fctn(
+  (r'\b(\w+)([ \t]*)(\()',
+   bygroups(Name.Function, Text, Name.Function), '#push'),
   (r'\(', Name.Function, '#push'),
   (r'\)', Name.Function, '#pop'),
   (r'\[', Punctuation, '#push'),
   (r'\]', Punctuation, '#pop'),
   (r'[|;,.=*\-]', Punctuation),
-  (r'\\\\', Punctuation),                                   # used in commands/source_group
+  # used in commands/source_group
+  (r'\\\\', Punctuation),
   (r'[:]', Operator),
-  (r'[<>]=', Punctuation),                                  # used in FindPkgConfig.cmake
-  (r'\$<', Operator, '#push'),                              # $<...>
-  (r'<[^<|]+?>(\w*\.\.\.)?', Name.Variable),                # <expr>
-  (r'(\$\w*\{)([^\}\$]*)?(?:(\$\w*\{)([^\}]+?)(\}))?([^\}]*?)(\})',  # ${..} $ENV{..}, possibly nested
-    bygroups(Operator, Name.Tag, Operator, Name.Tag, Operator, Name.Tag, Operator)),
-  (r'([A-Z]+\{)(.+?)(\})', bygroups(Operator, Name.Tag, Operator)),  # DATA{ ...}
-  (r'[a-z]+(@|(://))((\\.)|[\w.+-:/\\])+', Name.Attribute),          # URL, git@, ...
-  (r'/\w[\w\.\+-/\\]*', Name.Attribute),                    # absolute path
+  # used in FindPkgConfig.cmake
+  (r'[<>]=', Punctuation),
+  # $<...>
+  (r'\$<', Operator, '#push'),
+  # <expr>
+  (r'<[^<|]+?>(\w*\.\.\.)?', Name.Variable),
+  # ${..} $ENV{..}, possibly nested
+  (r'(\$\w*\{)([^\}\$]*)?(?:(\$\w*\{)([^\}]+?)(\}))?([^\}]*?)(\})',
+   bygroups(Operator, Name.Tag, Operator, Name.Tag, Operator, Name.Tag,
+            Operator)),
+  # DATA{ ...}
+  (r'([A-Z]+\{)(.+?)(\})', bygroups(Operator, Name.Tag, Operator)),
+  # URL, git@, ...
+  (r'[a-z]+(@|(://))((\\.)|[\w.+-:/\\])+', Name.Attribute),
+  # absolute path
+  (r'/\w[\w\.\+-/\\]*', Name.Attribute),
   (r'/', Name.Attribute),
-  (r'\w[\w\.\+-]*/[\w.+-/\\]*', Name.Attribute),            # relative path
-  (r'[A-Z]((\\.)|[\w.+-])*[a-z]((\\.)|[\w.+-])*', Name.Builtin), # initial A-Z, contains a-z
+  # relative path
+  (r'\w[\w\.\+-]*/[\w.+-/\\]*', Name.Attribute),
+  # initial A-Z, contains a-z
+  (r'[A-Z]((\\.)|[\w.+-])*[a-z]((\\.)|[\w.+-])*', Name.Builtin),
   (r'@?[A-Z][A-Z0-9_]*', Name.Constant),
   (r'[a-z_]((\\;)|(\\ )|[\w.+-])*', Name.Builtin),
   (r'[0-9][0-9\.]*', Number),
-  (r'(?s)"(\\"|[^"])*"', String),                           # "string"
+  # "string"
+  (r'(?s)"(\\"|[^"])*"', String),
   (r'\.\.\.', Name.Variable),
-  (r'<', Operator, '#push'),                                # <..|..> is different from <expr>
+  # <..|..> is different from <expr>
+  (r'<', Operator, '#push'),
   (r'>', Operator, '#pop'),
   (r'\n', Whitespace),
   (r'[ \t]+', Whitespace),
   (r'#.*\n', Comment),
-  #  (r'[^<>\])\}\|$"# \t\n]+', Name.Exception),            # fallback, for debugging only
+  # fallback, for debugging only
+  #  (r'[^<>\])\}\|$"# \t\n]+', Name.Exception),
 ]
 
-from docutils.parsers.rst import Directive, directives
-from docutils.transforms import Transform
-from docutils import io, nodes
+# END pygments tweaks
 
-from sphinx.directives import ObjectDescription
-from sphinx.domains import Domain, ObjType
-from sphinx.roles import XRefRole
-from sphinx.util.nodes import make_refnode
-from sphinx import addnodes
+# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
-sphinx_before_1_4 = False
-sphinx_before_1_7_2 = False
-try:
-    from sphinx import version_info
-    if version_info < (1, 4):
-        sphinx_before_1_4 = True
-    if version_info < (1, 7, 2):
-        sphinx_before_1_7_2 = True
-except ImportError:
-    # The `sphinx.version_info` tuple was added in Sphinx v1.2:
-    sphinx_before_1_4 = True
-    sphinx_before_1_7_2 = True
+# Require at least Sphinx 2.x.
+assert sphinx.version_info >= (2,)
 
-if sphinx_before_1_7_2:
-  # Monkey patch for sphinx generating invalid content for qcollectiongenerator
-  # https://github.com/sphinx-doc/sphinx/issues/1435
-  from sphinx.util.pycompat import htmlescape
-  from sphinx.builders.qthelp import QtHelpBuilder
-  old_build_keywords = QtHelpBuilder.build_keywords
-  def new_build_keywords(self, title, refs, subitems):
-    old_items = old_build_keywords(self, title, refs, subitems)
-    new_items = []
-    for item in old_items:
-      before, rest = item.split("ref=\"", 1)
-      ref, after = rest.split("\"")
-      if ("<" in ref and ">" in ref):
-        new_items.append(before + "ref=\"" + htmlescape(ref) + "\"" + after)
-      else:
-        new_items.append(item)
-    return new_items
-  QtHelpBuilder.build_keywords = new_build_keywords
+logger = logging.getLogger(__name__)
+
+# RE to split multiple command signatures.
+sig_end_re = re.compile(r'(?<=[)])\n')
+
+
+@dataclass
+class ObjectEntry:
+    docname: str
+    objtype: str
+    node_id: str
+    name: str
+
 
 class CMakeModule(Directive):
     required_arguments = 1
@@ -112,7 +140,7 @@
     def run(self):
         settings = self.state.document.settings
         if not settings.file_insertion_enabled:
-            raise self.warning('"%s" directive disabled.' % self.name)
+            raise self.warning(f'{self.name!r} directive disabled.')
 
         env = self.state.document.settings.env
         rel_path, path = env.relfn2path(self.arguments[0])
@@ -123,13 +151,12 @@
             settings.record_dependencies.add(path)
             f = io.FileInput(source_path=path, encoding=encoding,
                              error_handler=e_handler)
-        except UnicodeEncodeError as error:
-            msg = ('Problems with "%s" directive path:\n'
-                   'Cannot encode input file path "%s" '
-                   '(wrong locale?).' % (self.name, path))
+        except UnicodeEncodeError:
+            msg = (f'Problems with {self.name!r} directive path:\n'
+                   f'Cannot encode input file path {path!r} (wrong locale?).')
             raise self.severe(msg)
         except IOError as error:
-            msg = 'Problems with "%s" directive path:\n%s.' % (self.name, error)
+            msg = f'Problems with {self.name!r} directive path:\n{error}.'
             raise self.severe(msg)
         raw_lines = f.read().splitlines()
         f.close()
@@ -149,7 +176,7 @@
                 # Line mode: check for .rst start (bracket or line)
                 m = self.re_start.match(line)
                 if m:
-                    rst = ']%s]' % m.group('eq')
+                    rst = f']{m.group("eq")}]'
                     line = ''
                 elif line == '#.rst:':
                     rst = '#'
@@ -164,21 +191,19 @@
                     line = ''
             lines.append(line)
         if rst is not None and rst != '#':
-            raise self.warning('"%s" found unclosed bracket "#[%s[.rst:" in %s' %
-                               (self.name, rst[1:-1], path))
+            raise self.warning(f'{self.name!r} found unclosed bracket '
+                               f'"#[{rst[1:-1]}[.rst:" in {path!r}')
         self.state_machine.insert_input(lines, path)
         return []
 
+
 class _cmake_index_entry:
     def __init__(self, desc):
         self.desc = desc
 
-    def __call__(self, title, targetid, main = 'main'):
-        # See https://github.com/sphinx-doc/sphinx/issues/2673
-        if sphinx_before_1_4:
-            return ('pair', u'%s ; %s' % (self.desc, title), targetid, main)
-        else:
-            return ('pair', u'%s ; %s' % (self.desc, title), targetid, main, None)
+    def __call__(self, title, targetid, main='main'):
+        return ('pair', f'{self.desc} ; {title}', targetid, main, None)
+
 
 _cmake_index_objs = {
     'command':    _cmake_index_entry('command'),
@@ -200,13 +225,6 @@
     'variable':   _cmake_index_entry('variable'),
     }
 
-def _cmake_object_inventory(env, document, line, objtype, targetid):
-    inv = env.domaindata['cmake']['objects']
-    if targetid in inv:
-        document.reporter.warning(
-            'CMake object "%s" also described in "%s".' %
-            (targetid, env.doc2path(inv[targetid][0])), line=line)
-    inv[targetid] = (env.docname, objtype)
 
 class CMakeTransform(Transform):
 
@@ -235,7 +253,8 @@
                 title = False
             else:
                 for line in f:
-                    if len(line) > 0 and (line[0].isalnum() or line[0] == '<' or line[0] == '$'):
+                    if len(line) > 0 and (line[0].isalnum() or
+                                          line[0] == '<' or line[0] == '$'):
                         title = line.rstrip()
                         break
                 f.close()
@@ -263,7 +282,7 @@
                     if m:
                         title = m.group(1)
                 targetname = title
-            targetid = '%s:%s' % (objtype, targetname)
+            targetid = f'{objtype}:{targetname}'
             targetnode = nodes.target('', '', ids=[targetid])
             self.document.note_explicit_target(targetnode)
             self.document.insert(0, targetnode)
@@ -271,72 +290,277 @@
             indexnode = addnodes.index()
             indexnode['entries'] = [make_index_entry(title, targetid)]
             self.document.insert(0, indexnode)
+
             # Add to cmake domain object inventory
-            _cmake_object_inventory(env, self.document, 1, objtype, targetid)
+            domain = cast(CMakeDomain, env.get_domain('cmake'))
+            domain.note_object(objtype, targetname, targetid, targetid)
+
 
 class CMakeObject(ObjectDescription):
+    def __init__(self, *args, **kwargs):
+        self.targetname = None
+        super().__init__(*args, **kwargs)
 
     def handle_signature(self, sig, signode):
         # called from sphinx.directives.ObjectDescription.run()
         signode += addnodes.desc_name(sig, sig)
-        if self.objtype == 'genex':
-            m = CMakeXRefRole._re_genex.match(sig)
-            if m:
-                sig = m.group(1)
         return sig
 
     def add_target_and_index(self, name, sig, signode):
         if self.objtype == 'command':
-           targetname = name.lower()
+            targetname = name.lower()
+        elif self.targetname:
+            targetname = self.targetname
         else:
-           targetname = name
-        targetid = '%s:%s' % (self.objtype, targetname)
+            targetname = name
+        targetid = f'{self.objtype}:{targetname}'
         if targetid not in self.state.document.ids:
             signode['names'].append(targetid)
             signode['ids'].append(targetid)
             signode['first'] = (not self.names)
             self.state.document.note_explicit_target(signode)
-            _cmake_object_inventory(self.env, self.state.document,
-                                    self.lineno, self.objtype, targetid)
+
+            domain = cast(CMakeDomain, self.env.get_domain('cmake'))
+            domain.note_object(self.objtype, targetname, targetid, targetid,
+                               location=signode)
 
         make_index_entry = _cmake_index_objs.get(self.objtype)
         if make_index_entry:
             self.indexnode['entries'].append(make_index_entry(name, targetid))
 
-class CMakeXRefRole(XRefRole):
 
+class CMakeGenexObject(CMakeObject):
+    option_spec = {
+        'target': directives.unchanged,
+    }
+
+    def handle_signature(self, sig, signode):
+        name = super().handle_signature(sig, signode)
+
+        m = CMakeXRefRole._re_genex.match(sig)
+        if m:
+            name = m.group(1)
+
+        return name
+
+    def run(self):
+        target = self.options.get('target')
+        if target is not None:
+            self.targetname = target
+
+        return super().run()
+
+
+class CMakeSignatureObject(CMakeObject):
+    object_type = 'signature'
+
+    BREAK_ALL = 'all'
+    BREAK_SMART = 'smart'
+    BREAK_VERBATIM = 'verbatim'
+
+    BREAK_CHOICES = {BREAK_ALL, BREAK_SMART, BREAK_VERBATIM}
+
+    def break_option(argument):
+        return directives.choice(argument, CMakeSignatureObject.BREAK_CHOICES)
+
+    option_spec = {
+        'target': directives.unchanged,
+        'break': break_option,
+    }
+
+    def _break_signature_all(sig: str) -> str:
+        return ws_re.sub(' ', sig)
+
+    def _break_signature_verbatim(sig: str) -> str:
+        lines = [ws_re.sub('\xa0', line.strip()) for line in sig.split('\n')]
+        return ' '.join(lines)
+
+    def _break_signature_smart(sig: str) -> str:
+        tokens = []
+        for line in sig.split('\n'):
+            token = ''
+            delim = ''
+
+            for c in line.strip():
+                if len(delim) == 0 and ws_re.match(c):
+                    if len(token):
+                        tokens.append(ws_re.sub('\xa0', token))
+                        token = ''
+                else:
+                    if c == '[':
+                        delim += ']'
+                    elif c == '<':
+                        delim += '>'
+                    elif len(delim) and c == delim[-1]:
+                        delim = delim[:-1]
+                    token += c
+
+            if len(token):
+                tokens.append(ws_re.sub('\xa0', token))
+
+        return ' '.join(tokens)
+
+    def __init__(self, *args, **kwargs):
+        self.targetnames = {}
+        self.break_style = CMakeSignatureObject.BREAK_SMART
+        super().__init__(*args, **kwargs)
+
+    def get_signatures(self) -> List[str]:
+        content = nl_escape_re.sub('', self.arguments[0])
+        lines = sig_end_re.split(content)
+
+        if self.break_style == CMakeSignatureObject.BREAK_VERBATIM:
+            fixup = CMakeSignatureObject._break_signature_verbatim
+        elif self.break_style == CMakeSignatureObject.BREAK_SMART:
+            fixup = CMakeSignatureObject._break_signature_smart
+        else:
+            fixup = CMakeSignatureObject._break_signature_all
+
+        return [fixup(line.strip()) for line in lines]
+
+    def handle_signature(self, sig, signode):
+        language = 'cmake'
+        classes = ['code', 'cmake', 'highlight']
+
+        node = addnodes.desc_name(sig, '', classes=classes)
+
+        try:
+            tokens = Lexer(sig, language, 'short')
+        except LexerError as error:
+            if self.state.document.settings.report_level > 2:
+                # Silently insert without syntax highlighting.
+                tokens = Lexer(sig, language, 'none')
+            else:
+                raise self.warning(error)
+
+        for classes, value in tokens:
+            if value == '\xa0':
+                node += nodes.inline(value, value, classes=['nbsp'])
+            elif classes:
+                node += nodes.inline(value, value, classes=classes)
+            else:
+                node += nodes.Text(value)
+
+        signode.clear()
+        signode += node
+
+        return sig
+
+    def add_target_and_index(self, name, sig, signode):
+        sig = sig.replace('\xa0', ' ')
+        if sig in self.targetnames:
+            sigargs = self.targetnames[sig]
+        else:
+            def extract_keywords(params):
+                for p in params:
+                    if p[0].isalpha():
+                        yield p
+                    else:
+                        return
+
+            keywords = extract_keywords(sig.split('(')[1].split())
+            sigargs = ' '.join(keywords)
+        targetname = sigargs.lower()
+        targetid = nodes.make_id(targetname)
+
+        if targetid not in self.state.document.ids:
+            signode['names'].append(targetname)
+            signode['ids'].append(targetid)
+            signode['first'] = (not self.names)
+            self.state.document.note_explicit_target(signode)
+
+            # Register the signature as a command object.
+            command = sig.split('(')[0].lower()
+            refname = f'{command}({sigargs})'
+            refid = f'command:{command}({targetname})'
+
+            domain = cast(CMakeDomain, self.env.get_domain('cmake'))
+            domain.note_object('command', name=refname, target_id=refid,
+                               node_id=targetid, location=signode)
+
+    def run(self):
+        self.break_style = CMakeSignatureObject.BREAK_ALL
+
+        targets = self.options.get('target')
+        if targets is not None:
+            signatures = self.get_signatures()
+            targets = [t.strip() for t in targets.split('\n')]
+            for signature, target in zip(signatures, targets):
+                self.targetnames[signature] = target
+
+        self.break_style = (
+            self.options.get('break', CMakeSignatureObject.BREAK_SMART))
+
+        return super().run()
+
+
+class CMakeReferenceRole:
     # See sphinx.util.nodes.explicit_title_re; \x00 escapes '<'.
     _re = re.compile(r'^(.+?)(\s*)(?<!\x00)<(.*?)>$', re.DOTALL)
+
+    @staticmethod
+    def _escape_angle_brackets(text: str) -> str:
+        # CMake cross-reference targets frequently contain '<' so escape
+        # any explicit `<target>` with '<' not preceded by whitespace.
+        while True:
+            m = CMakeReferenceRole._re.match(text)
+            if m and len(m.group(2)) == 0:
+                text = f'{m.group(1)}\x00<{m.group(3)}>'
+            else:
+                break
+        return text
+
+    def __class_getitem__(cls, parent: Any):
+        class Class(parent):
+            def __call__(self, name: str, rawtext: str, text: str,
+                         *args, **kwargs
+                        ) -> Tuple[List[Node], List[system_message]]:
+                text = CMakeReferenceRole._escape_angle_brackets(text)
+                return super().__call__(name, rawtext, text, *args, **kwargs)
+        return Class
+
+
+class CMakeCRefRole(CMakeReferenceRole[ReferenceRole]):
+    nodeclass: Type[Element] = nodes.reference
+    innernodeclass: Type[TextElement] = nodes.literal
+    classes: List[str] = ['cmake', 'literal']
+
+    def run(self) -> Tuple[List[Node], List[system_message]]:
+        refnode = self.nodeclass(self.rawtext)
+        self.set_source_info(refnode)
+
+        refnode['refid'] = nodes.make_id(self.target)
+        refnode += self.innernodeclass(self.rawtext, self.title,
+                                       classes=self.classes)
+
+        return [refnode], []
+
+
+class CMakeXRefRole(CMakeReferenceRole[XRefRole]):
+
     _re_sub = re.compile(r'^([^()\s]+)\s*\(([^()]*)\)$', re.DOTALL)
     _re_genex = re.compile(r'^\$<([^<>:]+)(:[^<>]+)?>$', re.DOTALL)
     _re_guide = re.compile(r'^([^<>/]+)/([^<>]*)$', re.DOTALL)
 
-    def __call__(self, typ, rawtext, text, *args, **keys):
-        # Translate CMake command cross-references of the form:
-        #  `command_name(SUB_COMMAND)`
-        # to have an explicit target:
-        #  `command_name(SUB_COMMAND) <command_name>`
+    def __call__(self, typ, rawtext, text, *args, **kwargs):
         if typ == 'cmake:command':
+            # Translate a CMake command cross-reference of the form:
+            #  `command_name(SUB_COMMAND)`
+            # to be its own explicit target:
+            #  `command_name(SUB_COMMAND) <command_name(SUB_COMMAND)>`
+            # so the XRefRole `fix_parens` option does not add more `()`.
             m = CMakeXRefRole._re_sub.match(text)
             if m:
-                text = '%s <%s>' % (text, m.group(1))
+                text = f'{text} <{text}>'
         elif typ == 'cmake:genex':
             m = CMakeXRefRole._re_genex.match(text)
             if m:
-                text = '%s <%s>' % (text, m.group(1))
+                text = f'{text} <{m.group(1)}>'
         elif typ == 'cmake:guide':
             m = CMakeXRefRole._re_guide.match(text)
             if m:
-                text = '%s <%s>' % (m.group(2), text)
-        # CMake cross-reference targets frequently contain '<' so escape
-        # any explicit `<target>` with '<' not preceded by whitespace.
-        while True:
-            m = CMakeXRefRole._re.match(text)
-            if m and len(m.group(2)) == 0:
-                text = '%s\x00<%s>' % (m.group(1), m.group(3))
-            else:
-                break
-        return XRefRole.__call__(self, typ, rawtext, text, *args, **keys)
+                text = f'{m.group(2)} <{text}>'
+        return super().__call__(typ, rawtext, text, *args, **kwargs)
 
     # We cannot insert index nodes using the result_nodes method
     # because CMakeXRefRole is processed before substitution_reference
@@ -349,6 +573,7 @@
     # def result_nodes(self, document, env, node, is_ref):
     #     pass
 
+
 class CMakeXRefTransform(Transform):
 
     # Run this transform early since we insert nodes we want
@@ -375,9 +600,13 @@
                 # Do not index cross-references to guide sections.
                 continue
 
-            targetnum = env.new_serialno('index-%s:%s' % (objtype, objname))
+            if objtype == 'command':
+                # Index signature references to their parent command.
+                objname = objname.split('(')[0].lower()
 
-            targetid = 'index-%s-%s:%s' % (targetnum, objtype, objname)
+            targetnum = env.new_serialno(f'index-{objtype}:{objname}')
+
+            targetid = f'index-{targetnum}-{objtype}:{objname}'
             targetnode = nodes.target('', '', ids=[targetid])
             self.document.note_explicit_target(targetnode)
 
@@ -385,6 +614,7 @@
             indexnode['entries'] = [make_index_entry(objname, targetid, '')]
             ref.replace_self([indexnode, targetnode, ref])
 
+
 class CMakeDomain(Domain):
     """CMake domain."""
     name = 'cmake'
@@ -411,23 +641,14 @@
     directives = {
         'command':    CMakeObject,
         'envvar':     CMakeObject,
-        'genex':      CMakeObject,
+        'genex':      CMakeGenexObject,
+        'signature':  CMakeSignatureObject,
         'variable':   CMakeObject,
-        # Other object types cannot be created except by the CMakeTransform
-        # 'generator':  CMakeObject,
-        # 'module':     CMakeObject,
-        # 'policy':     CMakeObject,
-        # 'prop_cache': CMakeObject,
-        # 'prop_dir':   CMakeObject,
-        # 'prop_gbl':   CMakeObject,
-        # 'prop_inst':  CMakeObject,
-        # 'prop_sf':    CMakeObject,
-        # 'prop_test':  CMakeObject,
-        # 'prop_tgt':   CMakeObject,
-        # 'manual':     CMakeObject,
+        # Other `object_types` cannot be created except by the `CMakeTransform`
     }
     roles = {
-        'command':    CMakeXRefRole(fix_parens = True, lowercase = True),
+        'cref':       CMakeCRefRole(),
+        'command':    CMakeXRefRole(fix_parens=True, lowercase=True),
         'cpack_gen':  CMakeXRefRole(),
         'envvar':     CMakeXRefRole(),
         'generator':  CMakeXRefRole(),
@@ -451,25 +672,47 @@
 
     def clear_doc(self, docname):
         to_clear = set()
-        for fullname, (fn, _) in self.data['objects'].items():
-            if fn == docname:
+        for fullname, obj in self.data['objects'].items():
+            if obj.docname == docname:
                 to_clear.add(fullname)
         for fullname in to_clear:
             del self.data['objects'][fullname]
 
     def resolve_xref(self, env, fromdocname, builder,
                      typ, target, node, contnode):
-        targetid = '%s:%s' % (typ, target)
+        targetid = f'{typ}:{target}'
         obj = self.data['objects'].get(targetid)
+
+        if obj is None and typ == 'command':
+            # If 'command(args)' wasn't found, try just 'command'.
+            # TODO: remove this fallback? warn?
+            # logger.warning(f'no match for {targetid}')
+            command = target.split('(')[0]
+            targetid = f'{typ}:{command}'
+            obj = self.data['objects'].get(targetid)
+
         if obj is None:
             # TODO: warn somehow?
             return None
-        return make_refnode(builder, fromdocname, obj[0], targetid,
+
+        return make_refnode(builder, fromdocname, obj.docname, obj.node_id,
                             contnode, target)
 
+    def note_object(self, objtype: str, name: str, target_id: str,
+                    node_id: str, location: Any = None):
+        if target_id in self.data['objects']:
+            other = self.data['objects'][target_id].docname
+            logger.warning(
+                f'CMake object {target_id!r} also described in {other!r}',
+                location=location)
+
+        self.data['objects'][target_id] = ObjectEntry(
+            self.env.docname, objtype, node_id, name)
+
     def get_objects(self):
-        for refname, (docname, type) in self.data['objects'].items():
-            yield (refname, refname, type, docname, refname, 1)
+        for refname, obj in self.data['objects'].items():
+            yield (refname, obj.name, obj.objtype, obj.docname, obj.node_id, 1)
+
 
 def setup(app):
     app.add_directive('cmake-module', CMakeModule)
diff --git a/Utilities/Sphinx/conf.py.in b/Utilities/Sphinx/conf.py.in
index fc3ecb5..d4e4059 100644
--- a/Utilities/Sphinx/conf.py.in
+++ b/Utilities/Sphinx/conf.py.in
@@ -89,3 +89,11 @@
 # qthelp_qch_name = "CMake.qch"
 
 linkcheck_ignore = [r'about:|https://gitlab.kitware.com/cmake/community/-/wikis/doc/cpack']
+
+linkcheck_allowed_redirects = {
+    r'https://cdash\.org': r'https://www\.cdash\.org/',
+    r'https://docs\.nvidia\.com/cuda/': r'https://docs\.nvidia\.com/cuda/index\.html',
+    r'https://learn\.microsoft\.com/en-us/cpp/c-language/parsing-c-command-line-arguments': r'https://learn\.microsoft\.com/en-us/cpp/c-language/parsing-c-command-line-arguments\?.*',
+    r'https://openjdk\.java\.net/jeps/313': r'https://openjdk\.org:443/jeps/313',
+    r'https://www\.sphinx-doc\.org': r'https://www\.sphinx-doc\.org/en/master/',
+}
diff --git a/Utilities/Sphinx/static/cmake.css b/Utilities/Sphinx/static/cmake.css
index 324cd92..6303cb1 100644
--- a/Utilities/Sphinx/static/cmake.css
+++ b/Utilities/Sphinx/static/cmake.css
@@ -17,6 +17,45 @@
   background-color: #dfdfdf;
 }
 
+/* Apply <pre> style (from classic.css) to signature directive argument. */
+.signature .sig {
+  padding: 5px;
+  background-color: #eeeeee;
+  color: #333333;
+  line-height: 120%;
+  border: 1px solid #ac9;
+  border-left: none;
+  border-right: none;
+}
+
+/* Add additional styling to signature directive argument. */
+.signature .sig {
+  margin-bottom: 5px;
+  padding-left: calc(5px + 3em);
+  text-indent: -3em;
+  font-family: monospace;
+}
+
+.signature .sig .code.sig-name {
+  font-weight: normal;
+}
+
+/* Implement non-breaking spaces in signatures. */
+.nbsp {
+  white-space: nowrap;
+}
+
+/* Add hanging indent to version-{added,changed} content. */
+div .versionadded > *,
+div .versionchanged > * {
+  padding-left: 2em;
+}
+
+div.versionadded > :first-child,
+div.versionchanged > :first-child {
+  text-indent: -2em;
+}
+
 /* Remove unwanted margin in case list item contains a div-wrapping
    directive like `.. versionadded` or `.. deprecated`. */
 dd > :first-child > p {
diff --git a/Utilities/cm3p/cppdap/dap.h b/Utilities/cm3p/cppdap/dap.h
new file mode 100644
index 0000000..84fd332
--- /dev/null
+++ b/Utilities/cm3p/cppdap/dap.h
@@ -0,0 +1,11 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+/* Use the cppdap library configured for CMake.  */
+#include "cmThirdParty.h"
+#ifdef CMAKE_USE_SYSTEM_CPPDAP
+#  include <dap/dap.h> // IWYU pragma: export
+#else
+#  include <cmcppdap/include/dap/dap.h> // IWYU pragma: export
+#endif
diff --git a/Utilities/cm3p/cppdap/future.h b/Utilities/cm3p/cppdap/future.h
new file mode 100644
index 0000000..ad45b6b
--- /dev/null
+++ b/Utilities/cm3p/cppdap/future.h
@@ -0,0 +1,11 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+/* Use the cppdap library configured for CMake.  */
+#include "cmThirdParty.h"
+#ifdef CMAKE_USE_SYSTEM_CPPDAP
+#  include <dap/future.h> // IWYU pragma: export
+#else
+#  include <cmcppdap/include/dap/future.h> // IWYU pragma: export
+#endif
diff --git a/Utilities/cm3p/cppdap/io.h b/Utilities/cm3p/cppdap/io.h
new file mode 100644
index 0000000..e0401f8
--- /dev/null
+++ b/Utilities/cm3p/cppdap/io.h
@@ -0,0 +1,11 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+/* Use the cppdap library configured for CMake.  */
+#include "cmThirdParty.h"
+#ifdef CMAKE_USE_SYSTEM_CPPDAP
+#  include <dap/io.h> // IWYU pragma: export
+#else
+#  include <cmcppdap/include/dap/io.h> // IWYU pragma: export
+#endif
diff --git a/Utilities/cm3p/cppdap/optional.h b/Utilities/cm3p/cppdap/optional.h
new file mode 100644
index 0000000..777184d
--- /dev/null
+++ b/Utilities/cm3p/cppdap/optional.h
@@ -0,0 +1,11 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+/* Use the cppdap library configured for CMake.  */
+#include "cmThirdParty.h"
+#ifdef CMAKE_USE_SYSTEM_CPPDAP
+#  include <dap/optional.h> // IWYU pragma: export
+#else
+#  include <cmcppdap/include/dap/optional.h> // IWYU pragma: export
+#endif
diff --git a/Utilities/cm3p/cppdap/protocol.h b/Utilities/cm3p/cppdap/protocol.h
new file mode 100644
index 0000000..da70369
--- /dev/null
+++ b/Utilities/cm3p/cppdap/protocol.h
@@ -0,0 +1,11 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+/* Use the cppdap library configured for CMake.  */
+#include "cmThirdParty.h"
+#ifdef CMAKE_USE_SYSTEM_CPPDAP
+#  include <dap/protocol.h> // IWYU pragma: export
+#else
+#  include <cmcppdap/include/dap/protocol.h> // IWYU pragma: export
+#endif
diff --git a/Utilities/cm3p/cppdap/session.h b/Utilities/cm3p/cppdap/session.h
new file mode 100644
index 0000000..d4468e7
--- /dev/null
+++ b/Utilities/cm3p/cppdap/session.h
@@ -0,0 +1,11 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+/* Use the cppdap library configured for CMake.  */
+#include "cmThirdParty.h"
+#ifdef CMAKE_USE_SYSTEM_CPPDAP
+#  include <dap/session.h> // IWYU pragma: export
+#else
+#  include <cmcppdap/include/dap/session.h> // IWYU pragma: export
+#endif
diff --git a/Utilities/cm3p/cppdap/types.h b/Utilities/cm3p/cppdap/types.h
new file mode 100644
index 0000000..3fc2a88
--- /dev/null
+++ b/Utilities/cm3p/cppdap/types.h
@@ -0,0 +1,11 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+/* Use the cppdap library configured for CMake.  */
+#include "cmThirdParty.h"
+#ifdef CMAKE_USE_SYSTEM_CPPDAP
+#  include <dap/types.h> // IWYU pragma: export
+#else
+#  include <cmcppdap/include/dap/types.h> // IWYU pragma: export
+#endif
diff --git a/Utilities/cm3p/json/forwards.h b/Utilities/cm3p/json/forwards.h
new file mode 100644
index 0000000..c55c5c1
--- /dev/null
+++ b/Utilities/cm3p/json/forwards.h
@@ -0,0 +1,11 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+/* Use the jsoncpp library configured for CMake.  */
+#include "cmThirdParty.h"
+#ifdef CMAKE_USE_SYSTEM_JSONCPP
+#  include <json/forwards.h> // IWYU pragma: export
+#else
+#  include <cmjsoncpp/include/json/forwards.h> // IWYU pragma: export
+#endif
diff --git a/Utilities/cm3p/json/json.h b/Utilities/cm3p/json/json.h
new file mode 100644
index 0000000..5671e91
--- /dev/null
+++ b/Utilities/cm3p/json/json.h
@@ -0,0 +1,11 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+/* Use the jsoncpp library configured for CMake.  */
+#include "cmThirdParty.h"
+#ifdef CMAKE_USE_SYSTEM_JSONCPP
+#  include <json/json.h> // IWYU pragma: export
+#else
+#  include <cmjsoncpp/include/json/json.h> // IWYU pragma: export
+#endif
diff --git a/Utilities/cmThirdParty.h.in b/Utilities/cmThirdParty.h.in
index bd0edb7..da325b1 100644
--- a/Utilities/cmThirdParty.h.in
+++ b/Utilities/cmThirdParty.h.in
@@ -3,6 +3,7 @@
 #pragma once
 
 /* Whether CMake is using its own utility libraries.  */
+#cmakedefine CMAKE_USE_SYSTEM_CPPDAP
 #cmakedefine CMAKE_USE_SYSTEM_CURL
 #cmakedefine CMAKE_USE_SYSTEM_EXPAT
 #cmakedefine CMAKE_USE_SYSTEM_KWIML
diff --git a/Utilities/cmThirdPartyChecks.cmake b/Utilities/cmThirdPartyChecks.cmake
index a38c101..0f2bdb4 100644
--- a/Utilities/cmThirdPartyChecks.cmake
+++ b/Utilities/cmThirdPartyChecks.cmake
@@ -161,6 +161,7 @@
   set(HAVE_REGEX_H 0)
   set(HAVE_RSA_H 0)
   set(HAVE_SELECT 0)
+  set(HAVE_SENDMSG 0)
   set(HAVE_SETENV 0)
   set(HAVE_SETMODE 1)
   set(HAVE_SETRLIMIT 0)
diff --git a/Utilities/cmbzip2/bzlib.c b/Utilities/cmbzip2/bzlib.c
index 2178655..af3673d 100644
--- a/Utilities/cmbzip2/bzlib.c
+++ b/Utilities/cmbzip2/bzlib.c
@@ -444,6 +444,10 @@
          if (s->avail_in_expect != s->strm->avail_in) 
             return BZ_SEQUENCE_ERROR;
          progress = handle_compress ( strm );
+         #ifdef __clang_analyzer__
+         /* Tolerate deadcode.DeadStores to avoid modifying upstream.  */
+         (void)progress;
+         #endif
          if (s->avail_in_expect > 0 || !isempty_RL(s) ||
              s->state_out_pos < s->numZ) return BZ_FLUSH_OK;
          s->mode = BZ_M_RUNNING;
diff --git a/Utilities/cmbzip2/compress.c b/Utilities/cmbzip2/compress.c
index 5dfa002..a044c16 100644
--- a/Utilities/cmbzip2/compress.c
+++ b/Utilities/cmbzip2/compress.c
@@ -151,6 +151,10 @@
    UChar* block  = s->block;
    UInt16* mtfv  = s->mtfv;
 
+#ifdef __clang_analyzer__
+   memset(yy, 0, sizeof(yy));
+#endif
+
    makeMaps_e ( s );
    EOB = s->nInUse+1;
 
@@ -223,6 +227,10 @@
          zPend = (zPend - 2) / 2;
       };
       zPend = 0;
+      #ifdef __clang_analyzer__
+      /* Tolerate deadcode.DeadStores to avoid modifying upstream.  */
+      (void)zPend;
+      #endif
    }
 
    mtfv[wr] = EOB; wr++; s->mtfFreq[EOB]++;
diff --git a/Utilities/cmcppdap/.gitattributes b/Utilities/cmcppdap/.gitattributes
new file mode 100644
index 0000000..562b12e
--- /dev/null
+++ b/Utilities/cmcppdap/.gitattributes
@@ -0,0 +1 @@
+* -whitespace
diff --git a/Utilities/cmcppdap/CMakeLists.txt b/Utilities/cmcppdap/CMakeLists.txt
new file mode 100644
index 0000000..39f72a2
--- /dev/null
+++ b/Utilities/cmcppdap/CMakeLists.txt
@@ -0,0 +1,37 @@
+# Disable warnings to avoid changing 3rd party code.
+if(CMAKE_CXX_COMPILER_ID MATCHES
+    "^(GNU|LCC|Clang|AppleClang|IBMClang|XLClang|XL|VisualAge|SunPro|HP|Intel|IntelLLVM|NVHPC)$")
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w")
+elseif(CMAKE_CXX_COMPILER_ID STREQUAL "PathScale")
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -woffall")
+endif()
+
+add_library(cmcppdap STATIC
+  src/content_stream.cpp
+  src/io.cpp
+  src/jsoncpp_json_serializer.cpp
+  src/network.cpp
+  src/null_json_serializer.cpp
+  src/protocol_events.cpp
+  src/protocol_requests.cpp
+  src/protocol_response.cpp
+  src/protocol_types.cpp
+  src/session.cpp
+  src/socket.cpp
+  src/typeinfo.cpp
+  src/typeof.cpp
+)
+
+target_compile_definitions(cmcppdap PRIVATE CPPDAP_JSON_JSONCPP=1)
+target_include_directories(cmcppdap PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
+set_property(TARGET cmcppdap PROPERTY CXX_CLANG_TIDY "")
+set_property(TARGET cmcppdap PROPERTY CXX_INCLUDE_WHAT_YOU_USE "")
+
+target_link_libraries(cmcppdap PRIVATE JsonCpp::JsonCpp)
+if(WIN32)
+  target_link_libraries(cmcppdap PRIVATE ws2_32)
+elseif(NOT APPLE)
+  target_link_libraries(cmcppdap PRIVATE Threads::Threads)
+endif()
+
+install(FILES NOTICE DESTINATION ${CMAKE_DOC_DIR}/cmcppdap)
diff --git a/Utilities/cmcppdap/LICENSE b/Utilities/cmcppdap/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/Utilities/cmcppdap/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/Utilities/cmcppdap/NOTICE b/Utilities/cmcppdap/NOTICE
new file mode 100644
index 0000000..5ad206c
--- /dev/null
+++ b/Utilities/cmcppdap/NOTICE
@@ -0,0 +1,5 @@
+'cppdap' is a C++11 library implementation of the Debug Adapter Protocol.
+Version as of 2023-01-06
+Copyright Google LLC
+
+This product includes software developed at Google.
diff --git a/Utilities/cmcppdap/include/dap/any.h b/Utilities/cmcppdap/include/dap/any.h
new file mode 100644
index 0000000..b05f03d
--- /dev/null
+++ b/Utilities/cmcppdap/include/dap/any.h
@@ -0,0 +1,211 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef dap_any_h
+#define dap_any_h
+
+#include "typeinfo.h"
+
+#include <assert.h>
+#include <stdint.h>
+
+namespace dap {
+
+template <typename T>
+struct TypeOf;
+class Deserializer;
+class Serializer;
+
+// any provides a type-safe container for values of any of dap type (boolean,
+// integer, number, array, variant, any, null, dap-structs).
+class any {
+ public:
+  // constructors
+  inline any() = default;
+  inline any(const any& other) noexcept;
+  inline any(any&& other) noexcept;
+
+  template <typename T>
+  inline any(const T& val);
+
+  // destructors
+  inline ~any();
+
+  // replaces the contained value with a null.
+  inline void reset();
+
+  // assignment
+  inline any& operator=(const any& rhs);
+  inline any& operator=(any&& rhs) noexcept;
+  template <typename T>
+  inline any& operator=(const T& val);
+  inline any& operator=(const std::nullptr_t& val);
+
+  // get() returns the contained value of the type T.
+  // If the any does not contain a value of type T, then get() will assert.
+  template <typename T>
+  inline T& get() const;
+
+  // is() returns true iff the contained value is of type T.
+  template <typename T>
+  inline bool is() const;
+
+ private:
+  friend class Deserializer;
+  friend class Serializer;
+
+  static inline void* alignUp(void* val, size_t alignment);
+  inline void alloc(size_t size, size_t align);
+  inline void free();
+  inline bool isInBuffer(void* ptr) const;
+
+  void* value = nullptr;
+  const TypeInfo* type = nullptr;
+  void* heap = nullptr;  // heap allocation
+  uint8_t buffer[32];    // or internal allocation
+};
+
+inline any::~any() {
+  reset();
+}
+
+template <typename T>
+inline any::any(const T& val) {
+  *this = val;
+}
+
+any::any(const any& other) noexcept : type(other.type) {
+  if (other.value != nullptr) {
+    alloc(type->size(), type->alignment());
+    type->copyConstruct(value, other.value);
+  }
+}
+
+any::any(any&& other) noexcept : type(other.type) {
+  if (other.isInBuffer(other.value)) {
+    alloc(type->size(), type->alignment());
+    type->copyConstruct(value, other.value);
+  } else {
+    value = other.value;
+  }
+  other.value = nullptr;
+  other.type = nullptr;
+}
+
+void any::reset() {
+  if (value != nullptr) {
+    type->destruct(value);
+    free();
+  }
+  value = nullptr;
+  type = nullptr;
+}
+
+any& any::operator=(const any& rhs) {
+  reset();
+  type = rhs.type;
+  if (rhs.value != nullptr) {
+    alloc(type->size(), type->alignment());
+    type->copyConstruct(value, rhs.value);
+  }
+  return *this;
+}
+
+any& any::operator=(any&& rhs) noexcept {
+  reset();
+  type = rhs.type;
+  if (rhs.isInBuffer(rhs.value)) {
+    alloc(type->size(), type->alignment());
+    type->copyConstruct(value, rhs.value);
+  } else {
+    value = rhs.value;
+  }
+  rhs.value = nullptr;
+  rhs.type = nullptr;
+  return *this;
+}
+
+template <typename T>
+any& any::operator=(const T& val) {
+  if (!is<T>()) {
+    reset();
+    type = TypeOf<T>::type();
+    alloc(type->size(), type->alignment());
+    type->copyConstruct(value, &val);
+  } else {
+#ifdef __clang_analyzer__
+    assert(value != nullptr);
+#endif
+    *reinterpret_cast<T*>(value) = val;
+  }
+  return *this;
+}
+
+any& any::operator=(const std::nullptr_t&) {
+  reset();
+  return *this;
+}
+
+template <typename T>
+T& any::get() const {
+  static_assert(!std::is_same<T, std::nullptr_t>(),
+                "Cannot get nullptr from 'any'.");
+  assert(is<T>());
+  return *reinterpret_cast<T*>(value);
+}
+
+template <typename T>
+bool any::is() const {
+  return type == TypeOf<T>::type();
+}
+
+template <>
+inline bool any::is<std::nullptr_t>() const {
+  return value == nullptr;
+}
+
+void* any::alignUp(void* val, size_t alignment) {
+  auto ptr = reinterpret_cast<uintptr_t>(val);
+  return reinterpret_cast<void*>(alignment *
+                                 ((ptr + alignment - 1) / alignment));
+}
+
+void any::alloc(size_t size, size_t align) {
+  assert(value == nullptr);
+  value = alignUp(buffer, align);
+  if (isInBuffer(reinterpret_cast<uint8_t*>(value) + size - 1)) {
+    return;
+  }
+  heap = new uint8_t[size + align];
+  value = alignUp(heap, align);
+}
+
+void any::free() {
+  assert(value != nullptr);
+  if (heap != nullptr) {
+    delete[] reinterpret_cast<uint8_t*>(heap);
+    heap = nullptr;
+  }
+  value = nullptr;
+}
+
+bool any::isInBuffer(void* ptr) const {
+  auto addr = reinterpret_cast<uintptr_t>(ptr);
+  return addr >= reinterpret_cast<uintptr_t>(buffer) &&
+         addr < reinterpret_cast<uintptr_t>(buffer + sizeof(buffer));
+}
+
+}  // namespace dap
+
+#endif  // dap_any_h
diff --git a/Utilities/cmcppdap/include/dap/dap.h b/Utilities/cmcppdap/include/dap/dap.h
new file mode 100644
index 0000000..587e80c
--- /dev/null
+++ b/Utilities/cmcppdap/include/dap/dap.h
@@ -0,0 +1,35 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef dap_dap_h
+#define dap_dap_h
+
+namespace dap {
+
+// Explicit library initialization and termination functions.
+//
+// cppdap automatically initializes and terminates its internal state using lazy
+// static initialization, and so will usually work fine without explicit calls
+// to these functions.
+// However, if you use cppdap types in global state, you may need to call these
+// functions to ensure that cppdap is not uninitialized before the last usage.
+//
+// Each call to initialize() must have a corresponding call to terminate().
+// It is undefined behaviour to call initialize() after terminate().
+void initialize();
+void terminate();
+
+}  // namespace dap
+
+#endif  // dap_dap_h
diff --git a/Utilities/cmcppdap/include/dap/future.h b/Utilities/cmcppdap/include/dap/future.h
new file mode 100644
index 0000000..af103c3
--- /dev/null
+++ b/Utilities/cmcppdap/include/dap/future.h
@@ -0,0 +1,179 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef dap_future_h
+#define dap_future_h
+
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+
+namespace dap {
+
+// internal functionality
+namespace detail {
+template <typename T>
+struct promise_state {
+  T val;
+  std::mutex mutex;
+  std::condition_variable cv;
+  bool hasVal = false;
+};
+}  // namespace detail
+
+// forward declaration
+template <typename T>
+class promise;
+
+// future_status is the enumeration returned by future::wait_for and
+// future::wait_until.
+enum class future_status {
+  ready,
+  timeout,
+};
+
+// future is a minimal reimplementation of std::future, that does not suffer
+// from TSAN false positives. See:
+// https://gcc.gnu.org/bugzilla//show_bug.cgi?id=69204
+template <typename T>
+class future {
+ public:
+  using State = detail::promise_state<T>;
+
+  // constructors
+  inline future() = default;
+  inline future(future&&) = default;
+
+  // valid() returns true if the future has an internal state.
+  bool valid() const;
+
+  // get() blocks until the future has a valid result, and returns it.
+  // The future must have a valid internal state to call this method.
+  inline T get();
+
+  // wait() blocks until the future has a valid result.
+  // The future must have a valid internal state to call this method.
+  void wait() const;
+
+  // wait_for() blocks until the future has a valid result, or the timeout is
+  // reached.
+  // The future must have a valid internal state to call this method.
+  template <class Rep, class Period>
+  future_status wait_for(
+      const std::chrono::duration<Rep, Period>& timeout) const;
+
+  // wait_until() blocks until the future has a valid result, or the timeout is
+  // reached.
+  // The future must have a valid internal state to call this method.
+  template <class Clock, class Duration>
+  future_status wait_until(
+      const std::chrono::time_point<Clock, Duration>& timeout) const;
+
+ private:
+  friend promise<T>;
+  future(const future&) = delete;
+  inline future(const std::shared_ptr<State>& state);
+
+  std::shared_ptr<State> state = std::make_shared<State>();
+};
+
+template <typename T>
+future<T>::future(const std::shared_ptr<State>& s) : state(s) {}
+
+template <typename T>
+bool future<T>::valid() const {
+  return static_cast<bool>(state);
+}
+
+template <typename T>
+T future<T>::get() {
+  std::unique_lock<std::mutex> lock(state->mutex);
+  state->cv.wait(lock, [&] { return state->hasVal; });
+  return state->val;
+}
+
+template <typename T>
+void future<T>::wait() const {
+  std::unique_lock<std::mutex> lock(state->mutex);
+  state->cv.wait(lock, [&] { return state->hasVal; });
+}
+
+template <typename T>
+template <class Rep, class Period>
+future_status future<T>::wait_for(
+    const std::chrono::duration<Rep, Period>& timeout) const {
+  std::unique_lock<std::mutex> lock(state->mutex);
+  return state->cv.wait_for(lock, timeout, [&] { return state->hasVal; })
+             ? future_status::ready
+             : future_status::timeout;
+}
+
+template <typename T>
+template <class Clock, class Duration>
+future_status future<T>::wait_until(
+    const std::chrono::time_point<Clock, Duration>& timeout) const {
+  std::unique_lock<std::mutex> lock(state->mutex);
+  return state->cv.wait_until(lock, timeout, [&] { return state->hasVal; })
+             ? future_status::ready
+             : future_status::timeout;
+}
+
+// promise is a minimal reimplementation of std::promise, that does not suffer
+// from TSAN false positives. See:
+// https://gcc.gnu.org/bugzilla//show_bug.cgi?id=69204
+template <typename T>
+class promise {
+ public:
+  // constructors
+  inline promise() = default;
+  inline promise(promise&& other) = default;
+  inline promise(const promise& other) = default;
+
+  // set_value() stores value to the shared state.
+  // set_value() must only be called once.
+  inline void set_value(const T& value) const;
+  inline void set_value(T&& value) const;
+
+  // get_future() returns a future sharing this promise's state.
+  future<T> get_future();
+
+ private:
+  using State = detail::promise_state<T>;
+  std::shared_ptr<State> state = std::make_shared<State>();
+};
+
+template <typename T>
+future<T> promise<T>::get_future() {
+  return future<T>(state);
+}
+
+template <typename T>
+void promise<T>::set_value(const T& value) const {
+  std::unique_lock<std::mutex> lock(state->mutex);
+  state->val = value;
+  state->hasVal = true;
+  state->cv.notify_all();
+}
+
+template <typename T>
+void promise<T>::set_value(T&& value) const {
+  std::unique_lock<std::mutex> lock(state->mutex);
+  state->val = std::move(value);
+  state->hasVal = true;
+  state->cv.notify_all();
+}
+
+}  // namespace dap
+
+#endif  // dap_future_h
diff --git a/Utilities/cmcppdap/include/dap/io.h b/Utilities/cmcppdap/include/dap/io.h
new file mode 100644
index 0000000..61681cc
--- /dev/null
+++ b/Utilities/cmcppdap/include/dap/io.h
@@ -0,0 +1,97 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef dap_io_h
+#define dap_io_h
+
+#include <stddef.h>  // size_t
+#include <stdio.h>   // FILE
+#include <memory>    // std::unique_ptr
+#include <utility>   // std::pair
+
+namespace dap {
+
+class Closable {
+ public:
+  virtual ~Closable() = default;
+
+  // isOpen() returns true if the stream has not been closed.
+  virtual bool isOpen() = 0;
+
+  // close() closes the stream.
+  virtual void close() = 0;
+};
+
+// Reader is an interface for reading from a byte stream.
+class Reader : virtual public Closable {
+ public:
+  // read() attempts to read at most n bytes into buffer, returning the number
+  // of bytes read.
+  // read() will block until the stream is closed or at least one byte is read.
+  virtual size_t read(void* buffer, size_t n) = 0;
+};
+
+// Writer is an interface for writing to a byte stream.
+class Writer : virtual public Closable {
+ public:
+  // write() writes n bytes from buffer into the stream.
+  // Returns true on success, or false if there was an error or the stream was
+  // closed.
+  virtual bool write(const void* buffer, size_t n) = 0;
+};
+
+// ReaderWriter is an interface that combines the Reader and Writer interfaces.
+class ReaderWriter : public Reader, public Writer {
+ public:
+  // create() returns a ReaderWriter that delegates the interface methods on to
+  // the provided Reader and Writer.
+  // isOpen() returns true if the Reader and Writer both return true for
+  // isOpen().
+  // close() closes both the Reader and Writer.
+  static std::shared_ptr<ReaderWriter> create(const std::shared_ptr<Reader>&,
+                                              const std::shared_ptr<Writer>&);
+};
+
+// pipe() returns a ReaderWriter where the Writer streams to the Reader.
+// Writes are internally buffered.
+// Calling close() on either the Reader or Writer will close both ends of the
+// stream.
+std::shared_ptr<ReaderWriter> pipe();
+
+// file() wraps file with a ReaderWriter.
+// If closable is false, then a call to ReaderWriter::close() will not close the
+// underlying file.
+std::shared_ptr<ReaderWriter> file(FILE* file, bool closable = true);
+
+// file() opens (or creates) the file with the given path.
+std::shared_ptr<ReaderWriter> file(const char* path);
+
+// spy() returns a Reader that copies all reads from the Reader r to the Writer
+// s, using the given optional prefix.
+std::shared_ptr<Reader> spy(const std::shared_ptr<Reader>& r,
+                            const std::shared_ptr<Writer>& s,
+                            const char* prefix = "\n->");
+
+// spy() returns a Writer that copies all writes to the Writer w to the Writer
+// s, using the given optional prefix.
+std::shared_ptr<Writer> spy(const std::shared_ptr<Writer>& w,
+                            const std::shared_ptr<Writer>& s,
+                            const char* prefix = "\n<-");
+
+// writef writes the printf style string to the writer w.
+bool writef(const std::shared_ptr<Writer>& w, const char* msg, ...);
+
+}  // namespace dap
+
+#endif  // dap_io_h
diff --git a/Utilities/cmcppdap/include/dap/network.h b/Utilities/cmcppdap/include/dap/network.h
new file mode 100644
index 0000000..9d14f6b
--- /dev/null
+++ b/Utilities/cmcppdap/include/dap/network.h
@@ -0,0 +1,62 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef dap_network_h
+#define dap_network_h
+
+#include <functional>
+#include <memory>
+
+namespace dap {
+class ReaderWriter;
+
+namespace net {
+
+// connect() connects to the given TCP address and port.
+// If timeoutMillis is non-zero and no connection was made before timeoutMillis
+// milliseconds, then nullptr is returned.
+std::shared_ptr<ReaderWriter> connect(const char* addr,
+                                      int port,
+                                      uint32_t timeoutMillis = 0);
+
+// Server implements a basic TCP server.
+class Server {
+  // ignoreErrors() matches the OnError signature, and does nothing.
+  static inline void ignoreErrors(const char*) {}
+
+ public:
+  using OnError = std::function<void(const char*)>;
+  using OnConnect = std::function<void(const std::shared_ptr<ReaderWriter>&)>;
+
+  virtual ~Server() = default;
+
+  // create() constructs and returns a new Server.
+  static std::unique_ptr<Server> create();
+
+  // start() begins listening for connections on the given port.
+  // callback will be called for each connection.
+  // onError will be called for any connection errors.
+  virtual bool start(int port,
+                     const OnConnect& callback,
+                     const OnError& onError = ignoreErrors) = 0;
+
+  // stop() stops listening for connections.
+  // stop() is implicitly called on destruction.
+  virtual void stop() = 0;
+};
+
+}  // namespace net
+}  // namespace dap
+
+#endif  // dap_network_h
diff --git a/Utilities/cmcppdap/include/dap/optional.h b/Utilities/cmcppdap/include/dap/optional.h
new file mode 100644
index 0000000..9a3d216
--- /dev/null
+++ b/Utilities/cmcppdap/include/dap/optional.h
@@ -0,0 +1,263 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef dap_optional_h
+#define dap_optional_h
+
+#include <assert.h>
+#include <type_traits>
+#include <utility>  // std::move, std::forward
+
+namespace dap {
+
+// optional holds an 'optional' contained value.
+// This is similar to C++17's std::optional.
+template <typename T>
+class optional {
+  template <typename U>
+  using IsConvertibleToT =
+      typename std::enable_if<std::is_convertible<U, T>::value>::type;
+
+ public:
+  using value_type = T;
+
+  // constructors
+  inline optional() = default;
+  inline optional(const optional& other);
+  inline optional(optional&& other);
+  template <typename U>
+  inline optional(const optional<U>& other);
+  template <typename U>
+  inline optional(optional<U>&& other);
+  template <typename U = value_type, typename = IsConvertibleToT<U>>
+  inline optional(U&& value);
+
+  // value() returns the contained value.
+  // If the optional does not contain a value, then value() will assert.
+  inline T& value();
+  inline const T& value() const;
+
+  // value() returns the contained value, or defaultValue if the optional does
+  // not contain a value.
+  inline const T& value(const T& defaultValue) const;
+
+  // operator bool() returns true if the optional contains a value.
+  inline explicit operator bool() const noexcept;
+
+  // has_value() returns true if the optional contains a value.
+  inline bool has_value() const;
+
+  // assignment
+  inline optional& operator=(const optional& other);
+  inline optional& operator=(optional&& other) noexcept;
+  template <typename U = T, typename = IsConvertibleToT<U>>
+  inline optional& operator=(U&& value);
+  template <typename U>
+  inline optional& operator=(const optional<U>& other);
+  template <typename U>
+  inline optional& operator=(optional<U>&& other);
+
+  // value access
+  inline const T* operator->() const;
+  inline T* operator->();
+  inline const T& operator*() const;
+  inline T& operator*();
+
+ private:
+  T val{};
+  bool set = false;
+};
+
+template <typename T>
+optional<T>::optional(const optional& other) : val(other.val), set(other.set) {}
+
+template <typename T>
+optional<T>::optional(optional&& other)
+    : val(std::move(other.val)), set(other.set) {}
+
+template <typename T>
+template <typename U>
+optional<T>::optional(const optional<U>& other) : set(other.has_value()) {
+  if (set) {
+    val = static_cast<T>(other.value());
+  }
+}
+
+template <typename T>
+template <typename U>
+optional<T>::optional(optional<U>&& other) : set(other.has_value()) {
+  if (set) {
+    val = static_cast<T>(std::move(other.value()));
+  }
+}
+
+template <typename T>
+template <typename U /*= T*/, typename>
+optional<T>::optional(U&& value) : val(std::forward<U>(value)), set(true) {}
+
+template <typename T>
+T& optional<T>::value() {
+  assert(set);
+  return val;
+}
+
+template <typename T>
+const T& optional<T>::value() const {
+  assert(set);
+  return val;
+}
+
+template <typename T>
+const T& optional<T>::value(const T& defaultValue) const {
+  if (!has_value()) {
+    return defaultValue;
+  }
+  return val;
+}
+
+template <typename T>
+optional<T>::operator bool() const noexcept {
+  return set;
+}
+
+template <typename T>
+bool optional<T>::has_value() const {
+  return set;
+}
+
+template <typename T>
+optional<T>& optional<T>::operator=(const optional& other) {
+  val = other.val;
+  set = other.set;
+  return *this;
+}
+
+template <typename T>
+optional<T>& optional<T>::operator=(optional&& other) noexcept {
+  val = std::move(other.val);
+  set = other.set;
+  return *this;
+}
+
+template <typename T>
+template <typename U /* = T */, typename>
+optional<T>& optional<T>::operator=(U&& value) {
+  val = std::forward<U>(value);
+  set = true;
+  return *this;
+}
+
+template <typename T>
+template <typename U>
+optional<T>& optional<T>::operator=(const optional<U>& other) {
+  val = other.val;
+  set = other.set;
+  return *this;
+}
+
+template <typename T>
+template <typename U>
+optional<T>& optional<T>::operator=(optional<U>&& other) {
+  val = std::move(other.val);
+  set = other.set;
+  return *this;
+}
+
+template <typename T>
+const T* optional<T>::operator->() const {
+  assert(set);
+  return &val;
+}
+
+template <typename T>
+T* optional<T>::operator->() {
+  assert(set);
+  return &val;
+}
+
+template <typename T>
+const T& optional<T>::operator*() const {
+  assert(set);
+  return val;
+}
+
+template <typename T>
+T& optional<T>::operator*() {
+  assert(set);
+  return val;
+}
+
+template <class T, class U>
+inline bool operator==(const optional<T>& lhs, const optional<U>& rhs) {
+  if (!lhs.has_value() && !rhs.has_value()) {
+    return true;
+  }
+  if (!lhs.has_value() || !rhs.has_value()) {
+    return false;
+  }
+  return lhs.value() == rhs.value();
+}
+
+template <class T, class U>
+inline bool operator!=(const optional<T>& lhs, const optional<U>& rhs) {
+  return !(lhs == rhs);
+}
+
+template <class T, class U>
+inline bool operator<(const optional<T>& lhs, const optional<U>& rhs) {
+  if (!rhs.has_value()) {
+    return false;
+  }
+  if (!lhs.has_value()) {
+    return true;
+  }
+  return lhs.value() < rhs.value();
+}
+
+template <class T, class U>
+inline bool operator<=(const optional<T>& lhs, const optional<U>& rhs) {
+  if (!lhs.has_value()) {
+    return true;
+  }
+  if (!rhs.has_value()) {
+    return false;
+  }
+  return lhs.value() <= rhs.value();
+}
+
+template <class T, class U>
+inline bool operator>(const optional<T>& lhs, const optional<U>& rhs) {
+  if (!lhs.has_value()) {
+    return false;
+  }
+  if (!rhs.has_value()) {
+    return true;
+  }
+  return lhs.value() > rhs.value();
+}
+
+template <class T, class U>
+inline bool operator>=(const optional<T>& lhs, const optional<U>& rhs) {
+  if (!rhs.has_value()) {
+    return true;
+  }
+  if (!lhs.has_value()) {
+    return false;
+  }
+  return lhs.value() >= rhs.value();
+}
+
+}  // namespace dap
+
+#endif  // dap_optional_h
diff --git a/Utilities/cmcppdap/include/dap/protocol.h b/Utilities/cmcppdap/include/dap/protocol.h
new file mode 100644
index 0000000..e4c479e
--- /dev/null
+++ b/Utilities/cmcppdap/include/dap/protocol.h
@@ -0,0 +1,2679 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Generated with protocol_gen.go -- do not edit this file.
+//   go run scripts/protocol_gen/protocol_gen.go
+//
+// DAP version 1.59.0
+
+#ifndef dap_protocol_h
+#define dap_protocol_h
+
+#include "optional.h"
+#include "typeinfo.h"
+#include "typeof.h"
+#include "variant.h"
+
+#include <string>
+#include <type_traits>
+#include <vector>
+
+namespace dap {
+
+struct Request {};
+struct Response {};
+struct Event {};
+
+// Response to `attach` request. This is just an acknowledgement, so no body
+// field is required.
+struct AttachResponse : public Response {};
+
+DAP_DECLARE_STRUCT_TYPEINFO(AttachResponse);
+
+// The `attach` request is sent from the client to the debug adapter to attach
+// to a debuggee that is already running. Since attaching is debugger/runtime
+// specific, the arguments for this request are not part of this specification.
+struct AttachRequest : public Request {
+  using Response = AttachResponse;
+  // Arbitrary data from the previous, restarted session.
+  // The data is sent as the `restart` attribute of the `terminated` event.
+  // The client should leave the data intact.
+  optional<variant<array<any>, boolean, integer, null, number, object, string>>
+      restart;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(AttachRequest);
+
+// Names of checksum algorithms that may be supported by a debug adapter.
+//
+// Must be one of the following enumeration values:
+// 'MD5', 'SHA1', 'SHA256', 'timestamp'
+using ChecksumAlgorithm = string;
+
+// The checksum of an item calculated by the specified algorithm.
+struct Checksum {
+  // The algorithm used to calculate this checksum.
+  ChecksumAlgorithm algorithm = "MD5";
+  // Value of the checksum, encoded as a hexadecimal value.
+  string checksum;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(Checksum);
+
+// A `Source` is a descriptor for source code.
+// It is returned from the debug adapter as part of a `StackFrame` and it is
+// used by clients when specifying breakpoints.
+struct Source {
+  // Additional data that a debug adapter might want to loop through the client.
+  // The client should leave the data intact and persist it across sessions. The
+  // client should not interpret the data.
+  optional<variant<array<any>, boolean, integer, null, number, object, string>>
+      adapterData;
+  // The checksums associated with this file.
+  optional<array<Checksum>> checksums;
+  // The short name of the source. Every source returned from the debug adapter
+  // has a name. When sending a source to the debug adapter this name is
+  // optional.
+  optional<string> name;
+  // The origin of this source. For example, 'internal module', 'inlined content
+  // from source map', etc.
+  optional<string> origin;
+  // The path of the source to be shown in the UI.
+  // It is only used to locate and load the content of the source if no
+  // `sourceReference` is specified (or its value is 0).
+  optional<string> path;
+  // A hint for how to present the source in the UI.
+  // A value of `deemphasize` can be used to indicate that the source is not
+  // available or that it is skipped on stepping.
+  //
+  // Must be one of the following enumeration values:
+  // 'normal', 'emphasize', 'deemphasize'
+  optional<string> presentationHint;
+  // If the value > 0 the contents of the source must be retrieved through the
+  // `source` request (even if a path is specified). Since a `sourceReference`
+  // is only valid for a session, it can not be used to persist a source. The
+  // value should be less than or equal to 2147483647 (2^31-1).
+  optional<integer> sourceReference;
+  // A list of sources that are related to this source. These may be the source
+  // that generated this source.
+  optional<array<Source>> sources;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(Source);
+
+// Information about a breakpoint created in `setBreakpoints`,
+// `setFunctionBreakpoints`, `setInstructionBreakpoints`, or
+// `setDataBreakpoints` requests.
+struct Breakpoint {
+  // Start position of the source range covered by the breakpoint. It is
+  // measured in UTF-16 code units and the client capability `columnsStartAt1`
+  // determines whether it is 0- or 1-based.
+  optional<integer> column;
+  // End position of the source range covered by the breakpoint. It is measured
+  // in UTF-16 code units and the client capability `columnsStartAt1` determines
+  // whether it is 0- or 1-based. If no end line is given, then the end column
+  // is assumed to be in the start line.
+  optional<integer> endColumn;
+  // The end line of the actual range covered by the breakpoint.
+  optional<integer> endLine;
+  // The identifier for the breakpoint. It is needed if breakpoint events are
+  // used to update or remove breakpoints.
+  optional<integer> id;
+  // A memory reference to where the breakpoint is set.
+  optional<string> instructionReference;
+  // The start line of the actual range covered by the breakpoint.
+  optional<integer> line;
+  // A message about the state of the breakpoint.
+  // This is shown to the user and can be used to explain why a breakpoint could
+  // not be verified.
+  optional<string> message;
+  // The offset from the instruction reference.
+  // This can be negative.
+  optional<integer> offset;
+  // The source where the breakpoint is located.
+  optional<Source> source;
+  // If true, the breakpoint could be set (but not necessarily at the desired
+  // location).
+  boolean verified;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(Breakpoint);
+
+// The event indicates that some information about a breakpoint has changed.
+struct BreakpointEvent : public Event {
+  // The `id` attribute is used to find the target breakpoint, the other
+  // attributes are used as the new values.
+  Breakpoint breakpoint;
+  // The reason for the event.
+  //
+  // May be one of the following enumeration values:
+  // 'changed', 'new', 'removed'
+  string reason;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(BreakpointEvent);
+
+// Properties of a breakpoint location returned from the `breakpointLocations`
+// request.
+struct BreakpointLocation {
+  // The start position of a breakpoint location. Position is measured in UTF-16
+  // code units and the client capability `columnsStartAt1` determines whether
+  // it is 0- or 1-based.
+  optional<integer> column;
+  // The end position of a breakpoint location (if the location covers a range).
+  // Position is measured in UTF-16 code units and the client capability
+  // `columnsStartAt1` determines whether it is 0- or 1-based.
+  optional<integer> endColumn;
+  // The end line of breakpoint location if the location covers a range.
+  optional<integer> endLine;
+  // Start line of breakpoint location.
+  integer line;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(BreakpointLocation);
+
+// Response to `breakpointLocations` request.
+// Contains possible locations for source breakpoints.
+struct BreakpointLocationsResponse : public Response {
+  // Sorted set of possible breakpoint locations.
+  array<BreakpointLocation> breakpoints;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(BreakpointLocationsResponse);
+
+// The `breakpointLocations` request returns all possible locations for source
+// breakpoints in a given range. Clients should only call this request if the
+// corresponding capability `supportsBreakpointLocationsRequest` is true.
+struct BreakpointLocationsRequest : public Request {
+  using Response = BreakpointLocationsResponse;
+  // Start position within `line` to search possible breakpoint locations in. It
+  // is measured in UTF-16 code units and the client capability
+  // `columnsStartAt1` determines whether it is 0- or 1-based. If no column is
+  // given, the first position in the start line is assumed.
+  optional<integer> column;
+  // End position within `endLine` to search possible breakpoint locations in.
+  // It is measured in UTF-16 code units and the client capability
+  // `columnsStartAt1` determines whether it is 0- or 1-based. If no end column
+  // is given, the last position in the end line is assumed.
+  optional<integer> endColumn;
+  // End line of range to search possible breakpoint locations in. If no end
+  // line is given, then the end line is assumed to be the start line.
+  optional<integer> endLine;
+  // Start line of range to search possible breakpoint locations in. If only the
+  // line is specified, the request returns all possible locations in that line.
+  integer line;
+  // The source location of the breakpoints; either `source.path` or
+  // `source.reference` must be specified.
+  Source source;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(BreakpointLocationsRequest);
+
+// Response to `cancel` request. This is just an acknowledgement, so no body
+// field is required.
+struct CancelResponse : public Response {};
+
+DAP_DECLARE_STRUCT_TYPEINFO(CancelResponse);
+
+// The `cancel` request is used by the client in two situations:
+// - to indicate that it is no longer interested in the result produced by a
+// specific request issued earlier
+// - to cancel a progress sequence. Clients should only call this request if the
+// corresponding capability `supportsCancelRequest` is true. This request has a
+// hint characteristic: a debug adapter can only be expected to make a 'best
+// effort' in honoring this request but there are no guarantees. The `cancel`
+// request may return an error if it could not cancel an operation but a client
+// should refrain from presenting this error to end users. The request that got
+// cancelled still needs to send a response back. This can either be a normal
+// result (`success` attribute true) or an error response (`success` attribute
+// false and the `message` set to `cancelled`). Returning partial results from a
+// cancelled request is possible but please note that a client has no generic
+// way for detecting that a response is partial or not. The progress that got
+// cancelled still needs to send a `progressEnd` event back.
+//  A client should not assume that progress just got cancelled after sending
+//  the `cancel` request.
+struct CancelRequest : public Request {
+  using Response = CancelResponse;
+  // The ID (attribute `progressId`) of the progress to cancel. If missing no
+  // progress is cancelled. Both a `requestId` and a `progressId` can be
+  // specified in one request.
+  optional<string> progressId;
+  // The ID (attribute `seq`) of the request to cancel. If missing no request is
+  // cancelled. Both a `requestId` and a `progressId` can be specified in one
+  // request.
+  optional<integer> requestId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(CancelRequest);
+
+// A `ColumnDescriptor` specifies what module attribute to show in a column of
+// the modules view, how to format it, and what the column's label should be. It
+// is only used if the underlying UI actually supports this level of
+// customization.
+struct ColumnDescriptor {
+  // Name of the attribute rendered in this column.
+  string attributeName;
+  // Format to use for the rendered values in this column. TBD how the format
+  // strings looks like.
+  optional<string> format;
+  // Header UI label of column.
+  string label;
+  // Datatype of values in this column. Defaults to `string` if not specified.
+  //
+  // Must be one of the following enumeration values:
+  // 'string', 'number', 'boolean', 'unixTimestampUTC'
+  optional<string> type;
+  // Width of this column in characters (hint only).
+  optional<integer> width;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ColumnDescriptor);
+
+// An `ExceptionBreakpointsFilter` is shown in the UI as an filter option for
+// configuring how exceptions are dealt with.
+struct ExceptionBreakpointsFilter {
+  // A help text providing information about the condition. This string is shown
+  // as the placeholder text for a text box and can be translated.
+  optional<string> conditionDescription;
+  // Initial value of the filter option. If not specified a value false is
+  // assumed.
+  optional<boolean> def;
+  // A help text providing additional information about the exception filter.
+  // This string is typically shown as a hover and can be translated.
+  optional<string> description;
+  // The internal ID of the filter option. This value is passed to the
+  // `setExceptionBreakpoints` request.
+  string filter;
+  // The name of the filter option. This is shown in the UI.
+  string label;
+  // Controls whether a condition can be specified for this filter option. If
+  // false or missing, a condition can not be set.
+  optional<boolean> supportsCondition;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ExceptionBreakpointsFilter);
+
+// Information about the capabilities of a debug adapter.
+struct Capabilities {
+  // The set of additional module information exposed by the debug adapter.
+  optional<array<ColumnDescriptor>> additionalModuleColumns;
+  // The set of characters that should trigger completion in a REPL. If not
+  // specified, the UI should assume the `.` character.
+  optional<array<string>> completionTriggerCharacters;
+  // Available exception filter options for the `setExceptionBreakpoints`
+  // request.
+  optional<array<ExceptionBreakpointsFilter>> exceptionBreakpointFilters;
+  // The debug adapter supports the `suspendDebuggee` attribute on the
+  // `disconnect` request.
+  optional<boolean> supportSuspendDebuggee;
+  // The debug adapter supports the `terminateDebuggee` attribute on the
+  // `disconnect` request.
+  optional<boolean> supportTerminateDebuggee;
+  // Checksum algorithms supported by the debug adapter.
+  optional<array<ChecksumAlgorithm>> supportedChecksumAlgorithms;
+  // The debug adapter supports the `breakpointLocations` request.
+  optional<boolean> supportsBreakpointLocationsRequest;
+  // The debug adapter supports the `cancel` request.
+  optional<boolean> supportsCancelRequest;
+  // The debug adapter supports the `clipboard` context value in the `evaluate`
+  // request.
+  optional<boolean> supportsClipboardContext;
+  // The debug adapter supports the `completions` request.
+  optional<boolean> supportsCompletionsRequest;
+  // The debug adapter supports conditional breakpoints.
+  optional<boolean> supportsConditionalBreakpoints;
+  // The debug adapter supports the `configurationDone` request.
+  optional<boolean> supportsConfigurationDoneRequest;
+  // The debug adapter supports data breakpoints.
+  optional<boolean> supportsDataBreakpoints;
+  // The debug adapter supports the delayed loading of parts of the stack, which
+  // requires that both the `startFrame` and `levels` arguments and the
+  // `totalFrames` result of the `stackTrace` request are supported.
+  optional<boolean> supportsDelayedStackTraceLoading;
+  // The debug adapter supports the `disassemble` request.
+  optional<boolean> supportsDisassembleRequest;
+  // The debug adapter supports a (side effect free) `evaluate` request for data
+  // hovers.
+  optional<boolean> supportsEvaluateForHovers;
+  // The debug adapter supports `filterOptions` as an argument on the
+  // `setExceptionBreakpoints` request.
+  optional<boolean> supportsExceptionFilterOptions;
+  // The debug adapter supports the `exceptionInfo` request.
+  optional<boolean> supportsExceptionInfoRequest;
+  // The debug adapter supports `exceptionOptions` on the
+  // `setExceptionBreakpoints` request.
+  optional<boolean> supportsExceptionOptions;
+  // The debug adapter supports function breakpoints.
+  optional<boolean> supportsFunctionBreakpoints;
+  // The debug adapter supports the `gotoTargets` request.
+  optional<boolean> supportsGotoTargetsRequest;
+  // The debug adapter supports breakpoints that break execution after a
+  // specified number of hits.
+  optional<boolean> supportsHitConditionalBreakpoints;
+  // The debug adapter supports adding breakpoints based on instruction
+  // references.
+  optional<boolean> supportsInstructionBreakpoints;
+  // The debug adapter supports the `loadedSources` request.
+  optional<boolean> supportsLoadedSourcesRequest;
+  // The debug adapter supports log points by interpreting the `logMessage`
+  // attribute of the `SourceBreakpoint`.
+  optional<boolean> supportsLogPoints;
+  // The debug adapter supports the `modules` request.
+  optional<boolean> supportsModulesRequest;
+  // The debug adapter supports the `readMemory` request.
+  optional<boolean> supportsReadMemoryRequest;
+  // The debug adapter supports restarting a frame.
+  optional<boolean> supportsRestartFrame;
+  // The debug adapter supports the `restart` request. In this case a client
+  // should not implement `restart` by terminating and relaunching the adapter
+  // but by calling the `restart` request.
+  optional<boolean> supportsRestartRequest;
+  // The debug adapter supports the `setExpression` request.
+  optional<boolean> supportsSetExpression;
+  // The debug adapter supports setting a variable to a value.
+  optional<boolean> supportsSetVariable;
+  // The debug adapter supports the `singleThread` property on the execution
+  // requests (`continue`, `next`, `stepIn`, `stepOut`, `reverseContinue`,
+  // `stepBack`).
+  optional<boolean> supportsSingleThreadExecutionRequests;
+  // The debug adapter supports stepping back via the `stepBack` and
+  // `reverseContinue` requests.
+  optional<boolean> supportsStepBack;
+  // The debug adapter supports the `stepInTargets` request.
+  optional<boolean> supportsStepInTargetsRequest;
+  // The debug adapter supports stepping granularities (argument `granularity`)
+  // for the stepping requests.
+  optional<boolean> supportsSteppingGranularity;
+  // The debug adapter supports the `terminate` request.
+  optional<boolean> supportsTerminateRequest;
+  // The debug adapter supports the `terminateThreads` request.
+  optional<boolean> supportsTerminateThreadsRequest;
+  // The debug adapter supports a `format` attribute on the `stackTrace`,
+  // `variables`, and `evaluate` requests.
+  optional<boolean> supportsValueFormattingOptions;
+  // The debug adapter supports the `writeMemory` request.
+  optional<boolean> supportsWriteMemoryRequest;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(Capabilities);
+
+// The event indicates that one or more capabilities have changed.
+// Since the capabilities are dependent on the client and its UI, it might not
+// be possible to change that at random times (or too late). Consequently this
+// event has a hint characteristic: a client can only be expected to make a
+// 'best effort' in honoring individual capabilities but there are no
+// guarantees. Only changed capabilities need to be included, all other
+// capabilities keep their values.
+struct CapabilitiesEvent : public Event {
+  // The set of updated capabilities.
+  Capabilities capabilities;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(CapabilitiesEvent);
+
+// Some predefined types for the CompletionItem. Please note that not all
+// clients have specific icons for all of them.
+//
+// Must be one of the following enumeration values:
+// 'method', 'function', 'constructor', 'field', 'variable', 'class',
+// 'interface', 'module', 'property', 'unit', 'value', 'enum', 'keyword',
+// 'snippet', 'text', 'color', 'file', 'reference', 'customcolor'
+using CompletionItemType = string;
+
+// `CompletionItems` are the suggestions returned from the `completions`
+// request.
+struct CompletionItem {
+  // A human-readable string with additional information about this item, like
+  // type or symbol information.
+  optional<string> detail;
+  // The label of this completion item. By default this is also the text that is
+  // inserted when selecting this completion.
+  string label;
+  // Length determines how many characters are overwritten by the completion
+  // text and it is measured in UTF-16 code units. If missing the value 0 is
+  // assumed which results in the completion text being inserted.
+  optional<integer> length;
+  // Determines the length of the new selection after the text has been inserted
+  // (or replaced) and it is measured in UTF-16 code units. The selection can
+  // not extend beyond the bounds of the completion text. If omitted the length
+  // is assumed to be 0.
+  optional<integer> selectionLength;
+  // Determines the start of the new selection after the text has been inserted
+  // (or replaced). `selectionStart` is measured in UTF-16 code units and must
+  // be in the range 0 and length of the completion text. If omitted the
+  // selection starts at the end of the completion text.
+  optional<integer> selectionStart;
+  // A string that should be used when comparing this item with other items. If
+  // not returned or an empty string, the `label` is used instead.
+  optional<string> sortText;
+  // Start position (within the `text` attribute of the `completions` request)
+  // where the completion text is added. The position is measured in UTF-16 code
+  // units and the client capability `columnsStartAt1` determines whether it is
+  // 0- or 1-based. If the start position is omitted the text is added at the
+  // location specified by the `column` attribute of the `completions` request.
+  optional<integer> start;
+  // If text is returned and not an empty string, then it is inserted instead of
+  // the label.
+  optional<string> text;
+  // The item's type. Typically the client uses this information to render the
+  // item in the UI with an icon.
+  optional<CompletionItemType> type;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(CompletionItem);
+
+// Response to `completions` request.
+struct CompletionsResponse : public Response {
+  // The possible completions for .
+  array<CompletionItem> targets;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(CompletionsResponse);
+
+// Returns a list of possible completions for a given caret position and text.
+// Clients should only call this request if the corresponding capability
+// `supportsCompletionsRequest` is true.
+struct CompletionsRequest : public Request {
+  using Response = CompletionsResponse;
+  // The position within `text` for which to determine the completion proposals.
+  // It is measured in UTF-16 code units and the client capability
+  // `columnsStartAt1` determines whether it is 0- or 1-based.
+  integer column;
+  // Returns completions in the scope of this stack frame. If not specified, the
+  // completions are returned for the global scope.
+  optional<integer> frameId;
+  // A line for which to determine the completion proposals. If missing the
+  // first line of the text is assumed.
+  optional<integer> line;
+  // One or more source lines. Typically this is the text users have typed into
+  // the debug console before they asked for completion.
+  string text;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(CompletionsRequest);
+
+// Response to `configurationDone` request. This is just an acknowledgement, so
+// no body field is required.
+struct ConfigurationDoneResponse : public Response {};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ConfigurationDoneResponse);
+
+// This request indicates that the client has finished initialization of the
+// debug adapter. So it is the last request in the sequence of configuration
+// requests (which was started by the `initialized` event). Clients should only
+// call this request if the corresponding capability
+// `supportsConfigurationDoneRequest` is true.
+struct ConfigurationDoneRequest : public Request {
+  using Response = ConfigurationDoneResponse;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ConfigurationDoneRequest);
+
+// Response to `continue` request.
+struct ContinueResponse : public Response {
+  // The value true (or a missing property) signals to the client that all
+  // threads have been resumed. The value false indicates that not all threads
+  // were resumed.
+  optional<boolean> allThreadsContinued;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ContinueResponse);
+
+// The request resumes execution of all threads. If the debug adapter supports
+// single thread execution (see capability
+// `supportsSingleThreadExecutionRequests`), setting the `singleThread` argument
+// to true resumes only the specified thread. If not all threads were resumed,
+// the `allThreadsContinued` attribute of the response should be set to false.
+struct ContinueRequest : public Request {
+  using Response = ContinueResponse;
+  // If this flag is true, execution is resumed only for the thread with given
+  // `threadId`.
+  optional<boolean> singleThread;
+  // Specifies the active thread. If the debug adapter supports single thread
+  // execution (see `supportsSingleThreadExecutionRequests`) and the argument
+  // `singleThread` is true, only the thread with this ID is resumed.
+  integer threadId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ContinueRequest);
+
+// The event indicates that the execution of the debuggee has continued.
+// Please note: a debug adapter is not expected to send this event in response
+// to a request that implies that execution continues, e.g. `launch` or
+// `continue`. It is only necessary to send a `continued` event if there was no
+// previous request that implied this.
+struct ContinuedEvent : public Event {
+  // If `allThreadsContinued` is true, a debug adapter can announce that all
+  // threads have continued.
+  optional<boolean> allThreadsContinued;
+  // The thread which was continued.
+  integer threadId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ContinuedEvent);
+
+// This enumeration defines all possible access types for data breakpoints.
+//
+// Must be one of the following enumeration values:
+// 'read', 'write', 'readWrite'
+using DataBreakpointAccessType = string;
+
+// Response to `dataBreakpointInfo` request.
+struct DataBreakpointInfoResponse : public Response {
+  // Attribute lists the available access types for a potential data breakpoint.
+  // A UI client could surface this information.
+  optional<array<DataBreakpointAccessType>> accessTypes;
+  // Attribute indicates that a potential data breakpoint could be persisted
+  // across sessions.
+  optional<boolean> canPersist;
+  // An identifier for the data on which a data breakpoint can be registered
+  // with the `setDataBreakpoints` request or null if no data breakpoint is
+  // available.
+  variant<string, null> dataId;
+  // UI string that describes on what data the breakpoint is set on or why a
+  // data breakpoint is not available.
+  string description;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(DataBreakpointInfoResponse);
+
+// Obtains information on a possible data breakpoint that could be set on an
+// expression or variable. Clients should only call this request if the
+// corresponding capability `supportsDataBreakpoints` is true.
+struct DataBreakpointInfoRequest : public Request {
+  using Response = DataBreakpointInfoResponse;
+  // When `name` is an expression, evaluate it in the scope of this stack frame.
+  // If not specified, the expression is evaluated in the global scope. When
+  // `variablesReference` is specified, this property has no effect.
+  optional<integer> frameId;
+  // The name of the variable's child to obtain data breakpoint information for.
+  // If `variablesReference` isn't specified, this can be an expression.
+  string name;
+  // Reference to the variable container if the data breakpoint is requested for
+  // a child of the container. The `variablesReference` must have been obtained
+  // in the current suspended state. See 'Lifetime of Object References' in the
+  // Overview section for details.
+  optional<integer> variablesReference;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(DataBreakpointInfoRequest);
+
+// Represents a single disassembled instruction.
+struct DisassembledInstruction {
+  // The address of the instruction. Treated as a hex value if prefixed with
+  // `0x`, or as a decimal value otherwise.
+  string address;
+  // The column within the line that corresponds to this instruction, if any.
+  optional<integer> column;
+  // The end column of the range that corresponds to this instruction, if any.
+  optional<integer> endColumn;
+  // The end line of the range that corresponds to this instruction, if any.
+  optional<integer> endLine;
+  // Text representing the instruction and its operands, in an
+  // implementation-defined format.
+  string instruction;
+  // Raw bytes representing the instruction and its operands, in an
+  // implementation-defined format.
+  optional<string> instructionBytes;
+  // The line within the source location that corresponds to this instruction,
+  // if any.
+  optional<integer> line;
+  // Source location that corresponds to this instruction, if any.
+  // Should always be set (if available) on the first instruction returned,
+  // but can be omitted afterwards if this instruction maps to the same source
+  // file as the previous instruction.
+  optional<Source> location;
+  // Name of the symbol that corresponds with the location of this instruction,
+  // if any.
+  optional<string> symbol;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(DisassembledInstruction);
+
+// Response to `disassemble` request.
+struct DisassembleResponse : public Response {
+  // The list of disassembled instructions.
+  array<DisassembledInstruction> instructions;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(DisassembleResponse);
+
+// Disassembles code stored at the provided location.
+// Clients should only call this request if the corresponding capability
+// `supportsDisassembleRequest` is true.
+struct DisassembleRequest : public Request {
+  using Response = DisassembleResponse;
+  // Number of instructions to disassemble starting at the specified location
+  // and offset. An adapter must return exactly this number of instructions -
+  // any unavailable instructions should be replaced with an
+  // implementation-defined 'invalid instruction' value.
+  integer instructionCount;
+  // Offset (in instructions) to be applied after the byte offset (if any)
+  // before disassembling. Can be negative.
+  optional<integer> instructionOffset;
+  // Memory reference to the base location containing the instructions to
+  // disassemble.
+  string memoryReference;
+  // Offset (in bytes) to be applied to the reference location before
+  // disassembling. Can be negative.
+  optional<integer> offset;
+  // If true, the adapter should attempt to resolve memory addresses and other
+  // values to symbolic names.
+  optional<boolean> resolveSymbols;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(DisassembleRequest);
+
+// Response to `disconnect` request. This is just an acknowledgement, so no body
+// field is required.
+struct DisconnectResponse : public Response {};
+
+DAP_DECLARE_STRUCT_TYPEINFO(DisconnectResponse);
+
+// The `disconnect` request asks the debug adapter to disconnect from the
+// debuggee (thus ending the debug session) and then to shut down itself (the
+// debug adapter). In addition, the debug adapter must terminate the debuggee if
+// it was started with the `launch` request. If an `attach` request was used to
+// connect to the debuggee, then the debug adapter must not terminate the
+// debuggee. This implicit behavior of when to terminate the debuggee can be
+// overridden with the `terminateDebuggee` argument (which is only supported by
+// a debug adapter if the corresponding capability `supportTerminateDebuggee` is
+// true).
+struct DisconnectRequest : public Request {
+  using Response = DisconnectResponse;
+  // A value of true indicates that this `disconnect` request is part of a
+  // restart sequence.
+  optional<boolean> restart;
+  // Indicates whether the debuggee should stay suspended when the debugger is
+  // disconnected. If unspecified, the debuggee should resume execution. The
+  // attribute is only honored by a debug adapter if the corresponding
+  // capability `supportSuspendDebuggee` is true.
+  optional<boolean> suspendDebuggee;
+  // Indicates whether the debuggee should be terminated when the debugger is
+  // disconnected. If unspecified, the debug adapter is free to do whatever it
+  // thinks is best. The attribute is only honored by a debug adapter if the
+  // corresponding capability `supportTerminateDebuggee` is true.
+  optional<boolean> terminateDebuggee;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(DisconnectRequest);
+
+// A structured message object. Used to return errors from requests.
+struct Message {
+  // A format string for the message. Embedded variables have the form `{name}`.
+  // If variable name starts with an underscore character, the variable does not
+  // contain user data (PII) and can be safely used for telemetry purposes.
+  string format;
+  // Unique (within a debug adapter implementation) identifier for the message.
+  // The purpose of these error IDs is to help extension authors that have the
+  // requirement that every user visible error message needs a corresponding
+  // error number, so that users or customer support can find information about
+  // the specific error more easily.
+  integer id;
+  // If true send to telemetry.
+  optional<boolean> sendTelemetry;
+  // If true show user.
+  optional<boolean> showUser;
+  // A url where additional information about this message can be found.
+  optional<string> url;
+  // A label that is presented to the user as the UI for opening the url.
+  optional<string> urlLabel;
+  // An object used as a dictionary for looking up the variables in the format
+  // string.
+  optional<object> variables;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(Message);
+
+// On error (whenever `success` is false), the body can provide more details.
+struct ErrorResponse : public Response {
+  // A structured error message.
+  optional<Message> error;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ErrorResponse);
+
+// Properties of a variable that can be used to determine how to render the
+// variable in the UI.
+struct VariablePresentationHint {
+  // Set of attributes represented as an array of strings. Before introducing
+  // additional values, try to use the listed values.
+  optional<array<string>> attributes;
+  // The kind of variable. Before introducing additional values, try to use the
+  // listed values.
+  //
+  // May be one of the following enumeration values:
+  // 'property', 'method', 'class', 'data', 'event', 'baseClass', 'innerClass',
+  // 'interface', 'mostDerivedClass', 'virtual', 'dataBreakpoint'
+  optional<string> kind;
+  // If true, clients can present the variable with a UI that supports a
+  // specific gesture to trigger its evaluation. This mechanism can be used for
+  // properties that require executing code when retrieving their value and
+  // where the code execution can be expensive and/or produce side-effects. A
+  // typical example are properties based on a getter function. Please note that
+  // in addition to the `lazy` flag, the variable's `variablesReference` is
+  // expected to refer to a variable that will provide the value through another
+  // `variable` request.
+  optional<boolean> lazy;
+  // Visibility of variable. Before introducing additional values, try to use
+  // the listed values.
+  //
+  // May be one of the following enumeration values:
+  // 'public', 'private', 'protected', 'internal', 'final'
+  optional<string> visibility;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(VariablePresentationHint);
+
+// Response to `evaluate` request.
+struct EvaluateResponse : public Response {
+  // The number of indexed child variables.
+  // The client can use this information to present the variables in a paged UI
+  // and fetch them in chunks. The value should be less than or equal to
+  // 2147483647 (2^31-1).
+  optional<integer> indexedVariables;
+  // A memory reference to a location appropriate for this result.
+  // For pointer type eval results, this is generally a reference to the memory
+  // address contained in the pointer. This attribute should be returned by a
+  // debug adapter if corresponding capability `supportsMemoryReferences` is
+  // true.
+  optional<string> memoryReference;
+  // The number of named child variables.
+  // The client can use this information to present the variables in a paged UI
+  // and fetch them in chunks. The value should be less than or equal to
+  // 2147483647 (2^31-1).
+  optional<integer> namedVariables;
+  // Properties of an evaluate result that can be used to determine how to
+  // render the result in the UI.
+  optional<VariablePresentationHint> presentationHint;
+  // The result of the evaluate request.
+  string result;
+  // The type of the evaluate result.
+  // This attribute should only be returned by a debug adapter if the
+  // corresponding capability `supportsVariableType` is true.
+  optional<string> type;
+  // If `variablesReference` is > 0, the evaluate result is structured and its
+  // children can be retrieved by passing `variablesReference` to the
+  // `variables` request as long as execution remains suspended. See 'Lifetime
+  // of Object References' in the Overview section for details.
+  integer variablesReference;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(EvaluateResponse);
+
+// Provides formatting information for a value.
+struct ValueFormat {
+  // Display the value in hex.
+  optional<boolean> hex;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ValueFormat);
+
+// Evaluates the given expression in the context of the topmost stack frame.
+// The expression has access to any variables and arguments that are in scope.
+struct EvaluateRequest : public Request {
+  using Response = EvaluateResponse;
+  // The context in which the evaluate request is used.
+  //
+  // May be one of the following enumeration values:
+  // 'watch', 'repl', 'hover', 'clipboard', 'variables'
+  optional<string> context;
+  // The expression to evaluate.
+  string expression;
+  // Specifies details on how to format the result.
+  // The attribute is only honored by a debug adapter if the corresponding
+  // capability `supportsValueFormattingOptions` is true.
+  optional<ValueFormat> format;
+  // Evaluate the expression in the scope of this stack frame. If not specified,
+  // the expression is evaluated in the global scope.
+  optional<integer> frameId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(EvaluateRequest);
+
+// This enumeration defines all possible conditions when a thrown exception
+// should result in a break. never: never breaks, always: always breaks,
+// unhandled: breaks when exception unhandled,
+// userUnhandled: breaks if the exception is not handled by user code.
+//
+// Must be one of the following enumeration values:
+// 'never', 'always', 'unhandled', 'userUnhandled'
+using ExceptionBreakMode = string;
+
+// Detailed information about an exception that has occurred.
+struct ExceptionDetails {
+  // An expression that can be evaluated in the current scope to obtain the
+  // exception object.
+  optional<string> evaluateName;
+  // Fully-qualified type name of the exception object.
+  optional<string> fullTypeName;
+  // Details of the exception contained by this exception, if any.
+  optional<array<ExceptionDetails>> innerException;
+  // Message contained in the exception.
+  optional<string> message;
+  // Stack trace at the time the exception was thrown.
+  optional<string> stackTrace;
+  // Short type name of the exception object.
+  optional<string> typeName;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ExceptionDetails);
+
+// Response to `exceptionInfo` request.
+struct ExceptionInfoResponse : public Response {
+  // Mode that caused the exception notification to be raised.
+  ExceptionBreakMode breakMode = "never";
+  // Descriptive text for the exception.
+  optional<string> description;
+  // Detailed information about the exception.
+  optional<ExceptionDetails> details;
+  // ID of the exception that was thrown.
+  string exceptionId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ExceptionInfoResponse);
+
+// Retrieves the details of the exception that caused this event to be raised.
+// Clients should only call this request if the corresponding capability
+// `supportsExceptionInfoRequest` is true.
+struct ExceptionInfoRequest : public Request {
+  using Response = ExceptionInfoResponse;
+  // Thread for which exception information should be retrieved.
+  integer threadId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ExceptionInfoRequest);
+
+// The event indicates that the debuggee has exited and returns its exit code.
+struct ExitedEvent : public Event {
+  // The exit code returned from the debuggee.
+  integer exitCode;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ExitedEvent);
+
+// Response to `goto` request. This is just an acknowledgement, so no body field
+// is required.
+struct GotoResponse : public Response {};
+
+DAP_DECLARE_STRUCT_TYPEINFO(GotoResponse);
+
+// The request sets the location where the debuggee will continue to run.
+// This makes it possible to skip the execution of code or to execute code
+// again. The code between the current location and the goto target is not
+// executed but skipped. The debug adapter first sends the response and then a
+// `stopped` event with reason `goto`. Clients should only call this request if
+// the corresponding capability `supportsGotoTargetsRequest` is true (because
+// only then goto targets exist that can be passed as arguments).
+struct GotoRequest : public Request {
+  using Response = GotoResponse;
+  // The location where the debuggee will continue to run.
+  integer targetId;
+  // Set the goto target for this thread.
+  integer threadId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(GotoRequest);
+
+// A `GotoTarget` describes a code location that can be used as a target in the
+// `goto` request. The possible goto targets can be determined via the
+// `gotoTargets` request.
+struct GotoTarget {
+  // The column of the goto target.
+  optional<integer> column;
+  // The end column of the range covered by the goto target.
+  optional<integer> endColumn;
+  // The end line of the range covered by the goto target.
+  optional<integer> endLine;
+  // Unique identifier for a goto target. This is used in the `goto` request.
+  integer id;
+  // A memory reference for the instruction pointer value represented by this
+  // target.
+  optional<string> instructionPointerReference;
+  // The name of the goto target (shown in the UI).
+  string label;
+  // The line of the goto target.
+  integer line;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(GotoTarget);
+
+// Response to `gotoTargets` request.
+struct GotoTargetsResponse : public Response {
+  // The possible goto targets of the specified location.
+  array<GotoTarget> targets;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(GotoTargetsResponse);
+
+// This request retrieves the possible goto targets for the specified source
+// location. These targets can be used in the `goto` request. Clients should
+// only call this request if the corresponding capability
+// `supportsGotoTargetsRequest` is true.
+struct GotoTargetsRequest : public Request {
+  using Response = GotoTargetsResponse;
+  // The position within `line` for which the goto targets are determined. It is
+  // measured in UTF-16 code units and the client capability `columnsStartAt1`
+  // determines whether it is 0- or 1-based.
+  optional<integer> column;
+  // The line location for which the goto targets are determined.
+  integer line;
+  // The source location for which the goto targets are determined.
+  Source source;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(GotoTargetsRequest);
+
+// Response to `initialize` request.
+struct InitializeResponse : public Response {
+  // The set of additional module information exposed by the debug adapter.
+  optional<array<ColumnDescriptor>> additionalModuleColumns;
+  // The set of characters that should trigger completion in a REPL. If not
+  // specified, the UI should assume the `.` character.
+  optional<array<string>> completionTriggerCharacters;
+  // Available exception filter options for the `setExceptionBreakpoints`
+  // request.
+  optional<array<ExceptionBreakpointsFilter>> exceptionBreakpointFilters;
+  // The debug adapter supports the `suspendDebuggee` attribute on the
+  // `disconnect` request.
+  optional<boolean> supportSuspendDebuggee;
+  // The debug adapter supports the `terminateDebuggee` attribute on the
+  // `disconnect` request.
+  optional<boolean> supportTerminateDebuggee;
+  // Checksum algorithms supported by the debug adapter.
+  optional<array<ChecksumAlgorithm>> supportedChecksumAlgorithms;
+  // The debug adapter supports the `breakpointLocations` request.
+  optional<boolean> supportsBreakpointLocationsRequest;
+  // The debug adapter supports the `cancel` request.
+  optional<boolean> supportsCancelRequest;
+  // The debug adapter supports the `clipboard` context value in the `evaluate`
+  // request.
+  optional<boolean> supportsClipboardContext;
+  // The debug adapter supports the `completions` request.
+  optional<boolean> supportsCompletionsRequest;
+  // The debug adapter supports conditional breakpoints.
+  optional<boolean> supportsConditionalBreakpoints;
+  // The debug adapter supports the `configurationDone` request.
+  optional<boolean> supportsConfigurationDoneRequest;
+  // The debug adapter supports data breakpoints.
+  optional<boolean> supportsDataBreakpoints;
+  // The debug adapter supports the delayed loading of parts of the stack, which
+  // requires that both the `startFrame` and `levels` arguments and the
+  // `totalFrames` result of the `stackTrace` request are supported.
+  optional<boolean> supportsDelayedStackTraceLoading;
+  // The debug adapter supports the `disassemble` request.
+  optional<boolean> supportsDisassembleRequest;
+  // The debug adapter supports a (side effect free) `evaluate` request for data
+  // hovers.
+  optional<boolean> supportsEvaluateForHovers;
+  // The debug adapter supports `filterOptions` as an argument on the
+  // `setExceptionBreakpoints` request.
+  optional<boolean> supportsExceptionFilterOptions;
+  // The debug adapter supports the `exceptionInfo` request.
+  optional<boolean> supportsExceptionInfoRequest;
+  // The debug adapter supports `exceptionOptions` on the
+  // `setExceptionBreakpoints` request.
+  optional<boolean> supportsExceptionOptions;
+  // The debug adapter supports function breakpoints.
+  optional<boolean> supportsFunctionBreakpoints;
+  // The debug adapter supports the `gotoTargets` request.
+  optional<boolean> supportsGotoTargetsRequest;
+  // The debug adapter supports breakpoints that break execution after a
+  // specified number of hits.
+  optional<boolean> supportsHitConditionalBreakpoints;
+  // The debug adapter supports adding breakpoints based on instruction
+  // references.
+  optional<boolean> supportsInstructionBreakpoints;
+  // The debug adapter supports the `loadedSources` request.
+  optional<boolean> supportsLoadedSourcesRequest;
+  // The debug adapter supports log points by interpreting the `logMessage`
+  // attribute of the `SourceBreakpoint`.
+  optional<boolean> supportsLogPoints;
+  // The debug adapter supports the `modules` request.
+  optional<boolean> supportsModulesRequest;
+  // The debug adapter supports the `readMemory` request.
+  optional<boolean> supportsReadMemoryRequest;
+  // The debug adapter supports restarting a frame.
+  optional<boolean> supportsRestartFrame;
+  // The debug adapter supports the `restart` request. In this case a client
+  // should not implement `restart` by terminating and relaunching the adapter
+  // but by calling the `restart` request.
+  optional<boolean> supportsRestartRequest;
+  // The debug adapter supports the `setExpression` request.
+  optional<boolean> supportsSetExpression;
+  // The debug adapter supports setting a variable to a value.
+  optional<boolean> supportsSetVariable;
+  // The debug adapter supports the `singleThread` property on the execution
+  // requests (`continue`, `next`, `stepIn`, `stepOut`, `reverseContinue`,
+  // `stepBack`).
+  optional<boolean> supportsSingleThreadExecutionRequests;
+  // The debug adapter supports stepping back via the `stepBack` and
+  // `reverseContinue` requests.
+  optional<boolean> supportsStepBack;
+  // The debug adapter supports the `stepInTargets` request.
+  optional<boolean> supportsStepInTargetsRequest;
+  // The debug adapter supports stepping granularities (argument `granularity`)
+  // for the stepping requests.
+  optional<boolean> supportsSteppingGranularity;
+  // The debug adapter supports the `terminate` request.
+  optional<boolean> supportsTerminateRequest;
+  // The debug adapter supports the `terminateThreads` request.
+  optional<boolean> supportsTerminateThreadsRequest;
+  // The debug adapter supports a `format` attribute on the `stackTrace`,
+  // `variables`, and `evaluate` requests.
+  optional<boolean> supportsValueFormattingOptions;
+  // The debug adapter supports the `writeMemory` request.
+  optional<boolean> supportsWriteMemoryRequest;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(InitializeResponse);
+
+// The `initialize` request is sent as the first request from the client to the
+// debug adapter in order to configure it with client capabilities and to
+// retrieve capabilities from the debug adapter. Until the debug adapter has
+// responded with an `initialize` response, the client must not send any
+// additional requests or events to the debug adapter. In addition the debug
+// adapter is not allowed to send any requests or events to the client until it
+// has responded with an `initialize` response. The `initialize` request may
+// only be sent once.
+struct InitializeRequest : public Request {
+  using Response = InitializeResponse;
+  // The ID of the debug adapter.
+  string adapterID;
+  // The ID of the client using this adapter.
+  optional<string> clientID;
+  // The human-readable name of the client using this adapter.
+  optional<string> clientName;
+  // If true all column numbers are 1-based (default).
+  optional<boolean> columnsStartAt1;
+  // If true all line numbers are 1-based (default).
+  optional<boolean> linesStartAt1;
+  // The ISO-639 locale of the client using this adapter, e.g. en-US or de-CH.
+  optional<string> locale;
+  // Determines in what format paths are specified. The default is `path`, which
+  // is the native format.
+  //
+  // May be one of the following enumeration values:
+  // 'path', 'uri'
+  optional<string> pathFormat;
+  // Client supports the `argsCanBeInterpretedByShell` attribute on the
+  // `runInTerminal` request.
+  optional<boolean> supportsArgsCanBeInterpretedByShell;
+  // Client supports the `invalidated` event.
+  optional<boolean> supportsInvalidatedEvent;
+  // Client supports the `memory` event.
+  optional<boolean> supportsMemoryEvent;
+  // Client supports memory references.
+  optional<boolean> supportsMemoryReferences;
+  // Client supports progress reporting.
+  optional<boolean> supportsProgressReporting;
+  // Client supports the `runInTerminal` request.
+  optional<boolean> supportsRunInTerminalRequest;
+  // Client supports the `startDebugging` request.
+  optional<boolean> supportsStartDebuggingRequest;
+  // Client supports the paging of variables.
+  optional<boolean> supportsVariablePaging;
+  // Client supports the `type` attribute for variables.
+  optional<boolean> supportsVariableType;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(InitializeRequest);
+
+// This event indicates that the debug adapter is ready to accept configuration
+// requests (e.g. `setBreakpoints`, `setExceptionBreakpoints`). A debug adapter
+// is expected to send this event when it is ready to accept configuration
+// requests (but not before the `initialize` request has finished). The sequence
+// of events/requests is as follows:
+// - adapters sends `initialized` event (after the `initialize` request has
+// returned)
+// - client sends zero or more `setBreakpoints` requests
+// - client sends one `setFunctionBreakpoints` request (if corresponding
+// capability `supportsFunctionBreakpoints` is true)
+// - client sends a `setExceptionBreakpoints` request if one or more
+// `exceptionBreakpointFilters` have been defined (or if
+// `supportsConfigurationDoneRequest` is not true)
+// - client sends other future configuration requests
+// - client sends one `configurationDone` request to indicate the end of the
+// configuration.
+struct InitializedEvent : public Event {};
+
+DAP_DECLARE_STRUCT_TYPEINFO(InitializedEvent);
+
+// Logical areas that can be invalidated by the `invalidated` event.
+using InvalidatedAreas = string;
+
+// This event signals that some state in the debug adapter has changed and
+// requires that the client needs to re-render the data snapshot previously
+// requested. Debug adapters do not have to emit this event for runtime changes
+// like stopped or thread events because in that case the client refetches the
+// new state anyway. But the event can be used for example to refresh the UI
+// after rendering formatting has changed in the debug adapter. This event
+// should only be sent if the corresponding capability
+// `supportsInvalidatedEvent` is true.
+struct InvalidatedEvent : public Event {
+  // Set of logical areas that got invalidated. This property has a hint
+  // characteristic: a client can only be expected to make a 'best effort' in
+  // honoring the areas but there are no guarantees. If this property is
+  // missing, empty, or if values are not understood, the client should assume a
+  // single value `all`.
+  optional<array<InvalidatedAreas>> areas;
+  // If specified, the client only needs to refetch data related to this stack
+  // frame (and the `threadId` is ignored).
+  optional<integer> stackFrameId;
+  // If specified, the client only needs to refetch data related to this thread.
+  optional<integer> threadId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(InvalidatedEvent);
+
+// Response to `launch` request. This is just an acknowledgement, so no body
+// field is required.
+struct LaunchResponse : public Response {};
+
+DAP_DECLARE_STRUCT_TYPEINFO(LaunchResponse);
+
+// This launch request is sent from the client to the debug adapter to start the
+// debuggee with or without debugging (if `noDebug` is true). Since launching is
+// debugger/runtime specific, the arguments for this request are not part of
+// this specification.
+struct LaunchRequest : public Request {
+  using Response = LaunchResponse;
+  // Arbitrary data from the previous, restarted session.
+  // The data is sent as the `restart` attribute of the `terminated` event.
+  // The client should leave the data intact.
+  optional<variant<array<any>, boolean, integer, null, number, object, string>>
+      restart;
+  // If true, the launch request should launch the program without enabling
+  // debugging.
+  optional<boolean> noDebug;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(LaunchRequest);
+
+// The event indicates that some source has been added, changed, or removed from
+// the set of all loaded sources.
+struct LoadedSourceEvent : public Event {
+  // The reason for the event.
+  //
+  // Must be one of the following enumeration values:
+  // 'new', 'changed', 'removed'
+  string reason = "new";
+  // The new, changed, or removed source.
+  Source source;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(LoadedSourceEvent);
+
+// Response to `loadedSources` request.
+struct LoadedSourcesResponse : public Response {
+  // Set of loaded sources.
+  array<Source> sources;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(LoadedSourcesResponse);
+
+// Retrieves the set of all sources currently loaded by the debugged process.
+// Clients should only call this request if the corresponding capability
+// `supportsLoadedSourcesRequest` is true.
+struct LoadedSourcesRequest : public Request {
+  using Response = LoadedSourcesResponse;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(LoadedSourcesRequest);
+
+// This event indicates that some memory range has been updated. It should only
+// be sent if the corresponding capability `supportsMemoryEvent` is true.
+// Clients typically react to the event by re-issuing a `readMemory` request if
+// they show the memory identified by the `memoryReference` and if the updated
+// memory range overlaps the displayed range. Clients should not make
+// assumptions how individual memory references relate to each other, so they
+// should not assume that they are part of a single continuous address range and
+// might overlap. Debug adapters can use this event to indicate that the
+// contents of a memory range has changed due to some other request like
+// `setVariable` or `setExpression`. Debug adapters are not expected to emit
+// this event for each and every memory change of a running program, because
+// that information is typically not available from debuggers and it would flood
+// clients with too many events.
+struct MemoryEvent : public Event {
+  // Number of bytes updated.
+  integer count;
+  // Memory reference of a memory range that has been updated.
+  string memoryReference;
+  // Starting offset in bytes where memory has been updated. Can be negative.
+  integer offset;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(MemoryEvent);
+
+// A Module object represents a row in the modules view.
+// The `id` attribute identifies a module in the modules view and is used in a
+// `module` event for identifying a module for adding, updating or deleting. The
+// `name` attribute is used to minimally render the module in the UI.
+//
+// Additional attributes can be added to the module. They show up in the module
+// view if they have a corresponding `ColumnDescriptor`.
+//
+// To avoid an unnecessary proliferation of additional attributes with similar
+// semantics but different names, we recommend to re-use attributes from the
+// 'recommended' list below first, and only introduce new attributes if nothing
+// appropriate could be found.
+struct Module {
+  // Address range covered by this module.
+  optional<string> addressRange;
+  // Module created or modified, encoded as a RFC 3339 timestamp.
+  optional<string> dateTimeStamp;
+  // Unique identifier for the module.
+  variant<integer, string> id;
+  // True if the module is optimized.
+  optional<boolean> isOptimized;
+  // True if the module is considered 'user code' by a debugger that supports
+  // 'Just My Code'.
+  optional<boolean> isUserCode;
+  // A name of the module.
+  string name;
+  // Logical full path to the module. The exact definition is implementation
+  // defined, but usually this would be a full path to the on-disk file for the
+  // module.
+  optional<string> path;
+  // Logical full path to the symbol file. The exact definition is
+  // implementation defined.
+  optional<string> symbolFilePath;
+  // User-understandable description of if symbols were found for the module
+  // (ex: 'Symbols Loaded', 'Symbols not found', etc.)
+  optional<string> symbolStatus;
+  // Version of Module.
+  optional<string> version;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(Module);
+
+// The event indicates that some information about a module has changed.
+struct ModuleEvent : public Event {
+  // The new, changed, or removed module. In case of `removed` only the module
+  // id is used.
+  Module module;
+  // The reason for the event.
+  //
+  // Must be one of the following enumeration values:
+  // 'new', 'changed', 'removed'
+  string reason = "new";
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ModuleEvent);
+
+// Response to `modules` request.
+struct ModulesResponse : public Response {
+  // All modules or range of modules.
+  array<Module> modules;
+  // The total number of modules available.
+  optional<integer> totalModules;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ModulesResponse);
+
+// Modules can be retrieved from the debug adapter with this request which can
+// either return all modules or a range of modules to support paging. Clients
+// should only call this request if the corresponding capability
+// `supportsModulesRequest` is true.
+struct ModulesRequest : public Request {
+  using Response = ModulesResponse;
+  // The number of modules to return. If `moduleCount` is not specified or 0,
+  // all modules are returned.
+  optional<integer> moduleCount;
+  // The index of the first module to return; if omitted modules start at 0.
+  optional<integer> startModule;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ModulesRequest);
+
+// Response to `next` request. This is just an acknowledgement, so no body field
+// is required.
+struct NextResponse : public Response {};
+
+DAP_DECLARE_STRUCT_TYPEINFO(NextResponse);
+
+// The granularity of one 'step' in the stepping requests `next`, `stepIn`,
+// `stepOut`, and `stepBack`.
+//
+// Must be one of the following enumeration values:
+// 'statement', 'line', 'instruction'
+using SteppingGranularity = string;
+
+// The request executes one step (in the given granularity) for the specified
+// thread and allows all other threads to run freely by resuming them. If the
+// debug adapter supports single thread execution (see capability
+// `supportsSingleThreadExecutionRequests`), setting the `singleThread` argument
+// to true prevents other suspended threads from resuming. The debug adapter
+// first sends the response and then a `stopped` event (with reason `step`)
+// after the step has completed.
+struct NextRequest : public Request {
+  using Response = NextResponse;
+  // Stepping granularity. If no granularity is specified, a granularity of
+  // `statement` is assumed.
+  optional<SteppingGranularity> granularity;
+  // If this flag is true, all other suspended threads are not resumed.
+  optional<boolean> singleThread;
+  // Specifies the thread for which to resume execution for one step (of the
+  // given granularity).
+  integer threadId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(NextRequest);
+
+// The event indicates that the target has produced some output.
+struct OutputEvent : public Event {
+  // The output category. If not specified or if the category is not understood
+  // by the client, `console` is assumed.
+  //
+  // May be one of the following enumeration values:
+  // 'console', 'important', 'stdout', 'stderr', 'telemetry'
+  optional<string> category;
+  // The position in `line` where the output was produced. It is measured in
+  // UTF-16 code units and the client capability `columnsStartAt1` determines
+  // whether it is 0- or 1-based.
+  optional<integer> column;
+  // Additional data to report. For the `telemetry` category the data is sent to
+  // telemetry, for the other categories the data is shown in JSON format.
+  optional<variant<array<any>, boolean, integer, null, number, object, string>>
+      data;
+  // Support for keeping an output log organized by grouping related messages.
+  //
+  // Must be one of the following enumeration values:
+  // 'start', 'startCollapsed', 'end'
+  optional<string> group;
+  // The source location's line where the output was produced.
+  optional<integer> line;
+  // The output to report.
+  string output;
+  // The source location where the output was produced.
+  optional<Source> source;
+  // If an attribute `variablesReference` exists and its value is > 0, the
+  // output contains objects which can be retrieved by passing
+  // `variablesReference` to the `variables` request as long as execution
+  // remains suspended. See 'Lifetime of Object References' in the Overview
+  // section for details.
+  optional<integer> variablesReference;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(OutputEvent);
+
+// Response to `pause` request. This is just an acknowledgement, so no body
+// field is required.
+struct PauseResponse : public Response {};
+
+DAP_DECLARE_STRUCT_TYPEINFO(PauseResponse);
+
+// The request suspends the debuggee.
+// The debug adapter first sends the response and then a `stopped` event (with
+// reason `pause`) after the thread has been paused successfully.
+struct PauseRequest : public Request {
+  using Response = PauseResponse;
+  // Pause execution for this thread.
+  integer threadId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(PauseRequest);
+
+// The event indicates that the debugger has begun debugging a new process.
+// Either one that it has launched, or one that it has attached to.
+struct ProcessEvent : public Event {
+  // If true, the process is running on the same computer as the debug adapter.
+  optional<boolean> isLocalProcess;
+  // The logical name of the process. This is usually the full path to process's
+  // executable file. Example: /home/example/myproj/program.js.
+  string name;
+  // The size of a pointer or address for this process, in bits. This value may
+  // be used by clients when formatting addresses for display.
+  optional<integer> pointerSize;
+  // Describes how the debug engine started debugging this process.
+  //
+  // Must be one of the following enumeration values:
+  // 'launch', 'attach', 'attachForSuspendedLaunch'
+  optional<string> startMethod;
+  // The system process id of the debugged process. This property is missing for
+  // non-system processes.
+  optional<integer> systemProcessId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ProcessEvent);
+
+// The event signals the end of the progress reporting with a final message.
+// This event should only be sent if the corresponding capability
+// `supportsProgressReporting` is true.
+struct ProgressEndEvent : public Event {
+  // More detailed progress message. If omitted, the previous message (if any)
+  // is used.
+  optional<string> message;
+  // The ID that was introduced in the initial `ProgressStartEvent`.
+  string progressId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ProgressEndEvent);
+
+// The event signals that a long running operation is about to start and
+// provides additional information for the client to set up a corresponding
+// progress and cancellation UI. The client is free to delay the showing of the
+// UI in order to reduce flicker. This event should only be sent if the
+// corresponding capability `supportsProgressReporting` is true.
+struct ProgressStartEvent : public Event {
+  // If true, the request that reports progress may be cancelled with a `cancel`
+  // request. So this property basically controls whether the client should use
+  // UX that supports cancellation. Clients that don't support cancellation are
+  // allowed to ignore the setting.
+  optional<boolean> cancellable;
+  // More detailed progress message.
+  optional<string> message;
+  // Progress percentage to display (value range: 0 to 100). If omitted no
+  // percentage is shown.
+  optional<number> percentage;
+  // An ID that can be used in subsequent `progressUpdate` and `progressEnd`
+  // events to make them refer to the same progress reporting. IDs must be
+  // unique within a debug session.
+  string progressId;
+  // The request ID that this progress report is related to. If specified a
+  // debug adapter is expected to emit progress events for the long running
+  // request until the request has been either completed or cancelled. If the
+  // request ID is omitted, the progress report is assumed to be related to some
+  // general activity of the debug adapter.
+  optional<integer> requestId;
+  // Short title of the progress reporting. Shown in the UI to describe the long
+  // running operation.
+  string title;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ProgressStartEvent);
+
+// The event signals that the progress reporting needs to be updated with a new
+// message and/or percentage. The client does not have to update the UI
+// immediately, but the clients needs to keep track of the message and/or
+// percentage values. This event should only be sent if the corresponding
+// capability `supportsProgressReporting` is true.
+struct ProgressUpdateEvent : public Event {
+  // More detailed progress message. If omitted, the previous message (if any)
+  // is used.
+  optional<string> message;
+  // Progress percentage to display (value range: 0 to 100). If omitted no
+  // percentage is shown.
+  optional<number> percentage;
+  // The ID that was introduced in the initial `progressStart` event.
+  string progressId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ProgressUpdateEvent);
+
+// Response to `readMemory` request.
+struct ReadMemoryResponse : public Response {
+  // The address of the first byte of data returned.
+  // Treated as a hex value if prefixed with `0x`, or as a decimal value
+  // otherwise.
+  string address;
+  // The bytes read from memory, encoded using base64. If the decoded length of
+  // `data` is less than the requested `count` in the original `readMemory`
+  // request, and `unreadableBytes` is zero or omitted, then the client should
+  // assume it's reached the end of readable memory.
+  optional<string> data;
+  // The number of unreadable bytes encountered after the last successfully read
+  // byte. This can be used to determine the number of bytes that should be
+  // skipped before a subsequent `readMemory` request succeeds.
+  optional<integer> unreadableBytes;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ReadMemoryResponse);
+
+// Reads bytes from memory at the provided location.
+// Clients should only call this request if the corresponding capability
+// `supportsReadMemoryRequest` is true.
+struct ReadMemoryRequest : public Request {
+  using Response = ReadMemoryResponse;
+  // Number of bytes to read at the specified location and offset.
+  integer count;
+  // Memory reference to the base location from which data should be read.
+  string memoryReference;
+  // Offset (in bytes) to be applied to the reference location before reading
+  // data. Can be negative.
+  optional<integer> offset;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ReadMemoryRequest);
+
+// Response to `restartFrame` request. This is just an acknowledgement, so no
+// body field is required.
+struct RestartFrameResponse : public Response {};
+
+DAP_DECLARE_STRUCT_TYPEINFO(RestartFrameResponse);
+
+// The request restarts execution of the specified stack frame.
+// The debug adapter first sends the response and then a `stopped` event (with
+// reason `restart`) after the restart has completed. Clients should only call
+// this request if the corresponding capability `supportsRestartFrame` is true.
+struct RestartFrameRequest : public Request {
+  using Response = RestartFrameResponse;
+  // Restart the stack frame identified by `frameId`. The `frameId` must have
+  // been obtained in the current suspended state. See 'Lifetime of Object
+  // References' in the Overview section for details.
+  integer frameId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(RestartFrameRequest);
+
+// Response to `restart` request. This is just an acknowledgement, so no body
+// field is required.
+struct RestartResponse : public Response {};
+
+DAP_DECLARE_STRUCT_TYPEINFO(RestartResponse);
+
+// Restarts a debug session. Clients should only call this request if the
+// corresponding capability `supportsRestartRequest` is true. If the capability
+// is missing or has the value false, a typical client emulates `restart` by
+// terminating the debug adapter first and then launching it anew.
+struct RestartRequest : public Request {
+  using Response = RestartResponse;
+  // The latest version of the `launch` or `attach` configuration.
+  optional<object> arguments;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(RestartRequest);
+
+// Response to `reverseContinue` request. This is just an acknowledgement, so no
+// body field is required.
+struct ReverseContinueResponse : public Response {};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ReverseContinueResponse);
+
+// The request resumes backward execution of all threads. If the debug adapter
+// supports single thread execution (see capability
+// `supportsSingleThreadExecutionRequests`), setting the `singleThread` argument
+// to true resumes only the specified thread. If not all threads were resumed,
+// the `allThreadsContinued` attribute of the response should be set to false.
+// Clients should only call this request if the corresponding capability
+// `supportsStepBack` is true.
+struct ReverseContinueRequest : public Request {
+  using Response = ReverseContinueResponse;
+  // If this flag is true, backward execution is resumed only for the thread
+  // with given `threadId`.
+  optional<boolean> singleThread;
+  // Specifies the active thread. If the debug adapter supports single thread
+  // execution (see `supportsSingleThreadExecutionRequests`) and the
+  // `singleThread` argument is true, only the thread with this ID is resumed.
+  integer threadId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ReverseContinueRequest);
+
+// Response to `runInTerminal` request.
+struct RunInTerminalResponse : public Response {
+  // The process ID. The value should be less than or equal to 2147483647
+  // (2^31-1).
+  optional<integer> processId;
+  // The process ID of the terminal shell. The value should be less than or
+  // equal to 2147483647 (2^31-1).
+  optional<integer> shellProcessId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(RunInTerminalResponse);
+
+// This request is sent from the debug adapter to the client to run a command in
+// a terminal. This is typically used to launch the debuggee in a terminal
+// provided by the client. This request should only be called if the
+// corresponding client capability `supportsRunInTerminalRequest` is true.
+// Client implementations of `runInTerminal` are free to run the command however
+// they choose including issuing the command to a command line interpreter (aka
+// 'shell'). Argument strings passed to the `runInTerminal` request must arrive
+// verbatim in the command to be run. As a consequence, clients which use a
+// shell are responsible for escaping any special shell characters in the
+// argument strings to prevent them from being interpreted (and modified) by the
+// shell. Some users may wish to take advantage of shell processing in the
+// argument strings. For clients which implement `runInTerminal` using an
+// intermediary shell, the `argsCanBeInterpretedByShell` property can be set to
+// true. In this case the client is requested not to escape any special shell
+// characters in the argument strings.
+struct RunInTerminalRequest : public Request {
+  using Response = RunInTerminalResponse;
+  // List of arguments. The first argument is the command to run.
+  array<string> args;
+  // This property should only be set if the corresponding capability
+  // `supportsArgsCanBeInterpretedByShell` is true. If the client uses an
+  // intermediary shell to launch the application, then the client must not
+  // attempt to escape characters with special meanings for the shell. The user
+  // is fully responsible for escaping as needed and that arguments using
+  // special characters may not be portable across shells.
+  optional<boolean> argsCanBeInterpretedByShell;
+  // Working directory for the command. For non-empty, valid paths this
+  // typically results in execution of a change directory command.
+  string cwd;
+  // Environment key-value pairs that are added to or removed from the default
+  // environment.
+  optional<object> env;
+  // What kind of terminal to launch. Defaults to `integrated` if not specified.
+  //
+  // Must be one of the following enumeration values:
+  // 'integrated', 'external'
+  optional<string> kind;
+  // Title of the terminal.
+  optional<string> title;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(RunInTerminalRequest);
+
+// A `Scope` is a named container for variables. Optionally a scope can map to a
+// source or a range within a source.
+struct Scope {
+  // Start position of the range covered by the scope. It is measured in UTF-16
+  // code units and the client capability `columnsStartAt1` determines whether
+  // it is 0- or 1-based.
+  optional<integer> column;
+  // End position of the range covered by the scope. It is measured in UTF-16
+  // code units and the client capability `columnsStartAt1` determines whether
+  // it is 0- or 1-based.
+  optional<integer> endColumn;
+  // The end line of the range covered by this scope.
+  optional<integer> endLine;
+  // If true, the number of variables in this scope is large or expensive to
+  // retrieve.
+  boolean expensive;
+  // The number of indexed variables in this scope.
+  // The client can use this information to present the variables in a paged UI
+  // and fetch them in chunks.
+  optional<integer> indexedVariables;
+  // The start line of the range covered by this scope.
+  optional<integer> line;
+  // Name of the scope such as 'Arguments', 'Locals', or 'Registers'. This
+  // string is shown in the UI as is and can be translated.
+  string name;
+  // The number of named variables in this scope.
+  // The client can use this information to present the variables in a paged UI
+  // and fetch them in chunks.
+  optional<integer> namedVariables;
+  // A hint for how to present this scope in the UI. If this attribute is
+  // missing, the scope is shown with a generic UI.
+  //
+  // May be one of the following enumeration values:
+  // 'arguments', 'locals', 'registers'
+  optional<string> presentationHint;
+  // The source for this scope.
+  optional<Source> source;
+  // The variables of this scope can be retrieved by passing the value of
+  // `variablesReference` to the `variables` request as long as execution
+  // remains suspended. See 'Lifetime of Object References' in the Overview
+  // section for details.
+  integer variablesReference;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(Scope);
+
+// Response to `scopes` request.
+struct ScopesResponse : public Response {
+  // The scopes of the stack frame. If the array has length zero, there are no
+  // scopes available.
+  array<Scope> scopes;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ScopesResponse);
+
+// The request returns the variable scopes for a given stack frame ID.
+struct ScopesRequest : public Request {
+  using Response = ScopesResponse;
+  // Retrieve the scopes for the stack frame identified by `frameId`. The
+  // `frameId` must have been obtained in the current suspended state. See
+  // 'Lifetime of Object References' in the Overview section for details.
+  integer frameId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ScopesRequest);
+
+// Response to `setBreakpoints` request.
+// Returned is information about each breakpoint created by this request.
+// This includes the actual code location and whether the breakpoint could be
+// verified. The breakpoints returned are in the same order as the elements of
+// the `breakpoints` (or the deprecated `lines`) array in the arguments.
+struct SetBreakpointsResponse : public Response {
+  // Information about the breakpoints.
+  // The array elements are in the same order as the elements of the
+  // `breakpoints` (or the deprecated `lines`) array in the arguments.
+  array<Breakpoint> breakpoints;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(SetBreakpointsResponse);
+
+// Properties of a breakpoint or logpoint passed to the `setBreakpoints`
+// request.
+struct SourceBreakpoint {
+  // Start position within source line of the breakpoint or logpoint. It is
+  // measured in UTF-16 code units and the client capability `columnsStartAt1`
+  // determines whether it is 0- or 1-based.
+  optional<integer> column;
+  // The expression for conditional breakpoints.
+  // It is only honored by a debug adapter if the corresponding capability
+  // `supportsConditionalBreakpoints` is true.
+  optional<string> condition;
+  // The expression that controls how many hits of the breakpoint are ignored.
+  // The debug adapter is expected to interpret the expression as needed.
+  // The attribute is only honored by a debug adapter if the corresponding
+  // capability `supportsHitConditionalBreakpoints` is true. If both this
+  // property and `condition` are specified, `hitCondition` should be evaluated
+  // only if the `condition` is met, and the debug adapter should stop only if
+  // both conditions are met.
+  optional<string> hitCondition;
+  // The source line of the breakpoint or logpoint.
+  integer line;
+  // If this attribute exists and is non-empty, the debug adapter must not
+  // 'break' (stop) but log the message instead. Expressions within `{}` are
+  // interpolated. The attribute is only honored by a debug adapter if the
+  // corresponding capability `supportsLogPoints` is true. If either
+  // `hitCondition` or `condition` is specified, then the message should only be
+  // logged if those conditions are met.
+  optional<string> logMessage;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(SourceBreakpoint);
+
+// Sets multiple breakpoints for a single source and clears all previous
+// breakpoints in that source. To clear all breakpoint for a source, specify an
+// empty array. When a breakpoint is hit, a `stopped` event (with reason
+// `breakpoint`) is generated.
+struct SetBreakpointsRequest : public Request {
+  using Response = SetBreakpointsResponse;
+  // The code locations of the breakpoints.
+  optional<array<SourceBreakpoint>> breakpoints;
+  // Deprecated: The code locations of the breakpoints.
+  optional<array<integer>> lines;
+  // The source location of the breakpoints; either `source.path` or
+  // `source.sourceReference` must be specified.
+  Source source;
+  // A value of true indicates that the underlying source has been modified
+  // which results in new breakpoint locations.
+  optional<boolean> sourceModified;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(SetBreakpointsRequest);
+
+// Response to `setDataBreakpoints` request.
+// Returned is information about each breakpoint created by this request.
+struct SetDataBreakpointsResponse : public Response {
+  // Information about the data breakpoints. The array elements correspond to
+  // the elements of the input argument `breakpoints` array.
+  array<Breakpoint> breakpoints;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(SetDataBreakpointsResponse);
+
+// Properties of a data breakpoint passed to the `setDataBreakpoints` request.
+struct DataBreakpoint {
+  // The access type of the data.
+  optional<DataBreakpointAccessType> accessType;
+  // An expression for conditional breakpoints.
+  optional<string> condition;
+  // An id representing the data. This id is returned from the
+  // `dataBreakpointInfo` request.
+  string dataId;
+  // An expression that controls how many hits of the breakpoint are ignored.
+  // The debug adapter is expected to interpret the expression as needed.
+  optional<string> hitCondition;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(DataBreakpoint);
+
+// Replaces all existing data breakpoints with new data breakpoints.
+// To clear all data breakpoints, specify an empty array.
+// When a data breakpoint is hit, a `stopped` event (with reason `data
+// breakpoint`) is generated. Clients should only call this request if the
+// corresponding capability `supportsDataBreakpoints` is true.
+struct SetDataBreakpointsRequest : public Request {
+  using Response = SetDataBreakpointsResponse;
+  // The contents of this array replaces all existing data breakpoints. An empty
+  // array clears all data breakpoints.
+  array<DataBreakpoint> breakpoints;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(SetDataBreakpointsRequest);
+
+// Response to `setExceptionBreakpoints` request.
+// The response contains an array of `Breakpoint` objects with information about
+// each exception breakpoint or filter. The `Breakpoint` objects are in the same
+// order as the elements of the `filters`, `filterOptions`, `exceptionOptions`
+// arrays given as arguments. If both `filters` and `filterOptions` are given,
+// the returned array must start with `filters` information first, followed by
+// `filterOptions` information. The `verified` property of a `Breakpoint` object
+// signals whether the exception breakpoint or filter could be successfully
+// created and whether the condition or hit count expressions are valid. In case
+// of an error the `message` property explains the problem. The `id` property
+// can be used to introduce a unique ID for the exception breakpoint or filter
+// so that it can be updated subsequently by sending breakpoint events. For
+// backward compatibility both the `breakpoints` array and the enclosing `body`
+// are optional. If these elements are missing a client is not able to show
+// problems for individual exception breakpoints or filters.
+struct SetExceptionBreakpointsResponse : public Response {
+  // Information about the exception breakpoints or filters.
+  // The breakpoints returned are in the same order as the elements of the
+  // `filters`, `filterOptions`, `exceptionOptions` arrays in the arguments. If
+  // both `filters` and `filterOptions` are given, the returned array must start
+  // with `filters` information first, followed by `filterOptions` information.
+  optional<array<Breakpoint>> breakpoints;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(SetExceptionBreakpointsResponse);
+
+// An `ExceptionPathSegment` represents a segment in a path that is used to
+// match leafs or nodes in a tree of exceptions. If a segment consists of more
+// than one name, it matches the names provided if `negate` is false or missing,
+// or it matches anything except the names provided if `negate` is true.
+struct ExceptionPathSegment {
+  // Depending on the value of `negate` the names that should match or not
+  // match.
+  array<string> names;
+  // If false or missing this segment matches the names provided, otherwise it
+  // matches anything except the names provided.
+  optional<boolean> negate;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ExceptionPathSegment);
+
+// An `ExceptionOptions` assigns configuration options to a set of exceptions.
+struct ExceptionOptions {
+  // Condition when a thrown exception should result in a break.
+  ExceptionBreakMode breakMode = "never";
+  // A path that selects a single or multiple exceptions in a tree. If `path` is
+  // missing, the whole tree is selected. By convention the first segment of the
+  // path is a category that is used to group exceptions in the UI.
+  optional<array<ExceptionPathSegment>> path;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ExceptionOptions);
+
+// An `ExceptionFilterOptions` is used to specify an exception filter together
+// with a condition for the `setExceptionBreakpoints` request.
+struct ExceptionFilterOptions {
+  // An expression for conditional exceptions.
+  // The exception breaks into the debugger if the result of the condition is
+  // true.
+  optional<string> condition;
+  // ID of an exception filter returned by the `exceptionBreakpointFilters`
+  // capability.
+  string filterId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ExceptionFilterOptions);
+
+// The request configures the debugger's response to thrown exceptions.
+// If an exception is configured to break, a `stopped` event is fired (with
+// reason `exception`). Clients should only call this request if the
+// corresponding capability `exceptionBreakpointFilters` returns one or more
+// filters.
+struct SetExceptionBreakpointsRequest : public Request {
+  using Response = SetExceptionBreakpointsResponse;
+  // Configuration options for selected exceptions.
+  // The attribute is only honored by a debug adapter if the corresponding
+  // capability `supportsExceptionOptions` is true.
+  optional<array<ExceptionOptions>> exceptionOptions;
+  // Set of exception filters and their options. The set of all possible
+  // exception filters is defined by the `exceptionBreakpointFilters`
+  // capability. This attribute is only honored by a debug adapter if the
+  // corresponding capability `supportsExceptionFilterOptions` is true. The
+  // `filter` and `filterOptions` sets are additive.
+  optional<array<ExceptionFilterOptions>> filterOptions;
+  // Set of exception filters specified by their ID. The set of all possible
+  // exception filters is defined by the `exceptionBreakpointFilters`
+  // capability. The `filter` and `filterOptions` sets are additive.
+  array<string> filters;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(SetExceptionBreakpointsRequest);
+
+// Response to `setExpression` request.
+struct SetExpressionResponse : public Response {
+  // The number of indexed child variables.
+  // The client can use this information to present the variables in a paged UI
+  // and fetch them in chunks. The value should be less than or equal to
+  // 2147483647 (2^31-1).
+  optional<integer> indexedVariables;
+  // The number of named child variables.
+  // The client can use this information to present the variables in a paged UI
+  // and fetch them in chunks. The value should be less than or equal to
+  // 2147483647 (2^31-1).
+  optional<integer> namedVariables;
+  // Properties of a value that can be used to determine how to render the
+  // result in the UI.
+  optional<VariablePresentationHint> presentationHint;
+  // The type of the value.
+  // This attribute should only be returned by a debug adapter if the
+  // corresponding capability `supportsVariableType` is true.
+  optional<string> type;
+  // The new value of the expression.
+  string value;
+  // If `variablesReference` is > 0, the evaluate result is structured and its
+  // children can be retrieved by passing `variablesReference` to the
+  // `variables` request as long as execution remains suspended. See 'Lifetime
+  // of Object References' in the Overview section for details.
+  optional<integer> variablesReference;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(SetExpressionResponse);
+
+// Evaluates the given `value` expression and assigns it to the `expression`
+// which must be a modifiable l-value. The expressions have access to any
+// variables and arguments that are in scope of the specified frame. Clients
+// should only call this request if the corresponding capability
+// `supportsSetExpression` is true. If a debug adapter implements both
+// `setExpression` and `setVariable`, a client uses `setExpression` if the
+// variable has an `evaluateName` property.
+struct SetExpressionRequest : public Request {
+  using Response = SetExpressionResponse;
+  // The l-value expression to assign to.
+  string expression;
+  // Specifies how the resulting value should be formatted.
+  optional<ValueFormat> format;
+  // Evaluate the expressions in the scope of this stack frame. If not
+  // specified, the expressions are evaluated in the global scope.
+  optional<integer> frameId;
+  // The value expression to assign to the l-value expression.
+  string value;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(SetExpressionRequest);
+
+// Response to `setFunctionBreakpoints` request.
+// Returned is information about each breakpoint created by this request.
+struct SetFunctionBreakpointsResponse : public Response {
+  // Information about the breakpoints. The array elements correspond to the
+  // elements of the `breakpoints` array.
+  array<Breakpoint> breakpoints;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(SetFunctionBreakpointsResponse);
+
+// Properties of a breakpoint passed to the `setFunctionBreakpoints` request.
+struct FunctionBreakpoint {
+  // An expression for conditional breakpoints.
+  // It is only honored by a debug adapter if the corresponding capability
+  // `supportsConditionalBreakpoints` is true.
+  optional<string> condition;
+  // An expression that controls how many hits of the breakpoint are ignored.
+  // The debug adapter is expected to interpret the expression as needed.
+  // The attribute is only honored by a debug adapter if the corresponding
+  // capability `supportsHitConditionalBreakpoints` is true.
+  optional<string> hitCondition;
+  // The name of the function.
+  string name;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(FunctionBreakpoint);
+
+// Replaces all existing function breakpoints with new function breakpoints.
+// To clear all function breakpoints, specify an empty array.
+// When a function breakpoint is hit, a `stopped` event (with reason `function
+// breakpoint`) is generated. Clients should only call this request if the
+// corresponding capability `supportsFunctionBreakpoints` is true.
+struct SetFunctionBreakpointsRequest : public Request {
+  using Response = SetFunctionBreakpointsResponse;
+  // The function names of the breakpoints.
+  array<FunctionBreakpoint> breakpoints;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(SetFunctionBreakpointsRequest);
+
+// Response to `setInstructionBreakpoints` request
+struct SetInstructionBreakpointsResponse : public Response {
+  // Information about the breakpoints. The array elements correspond to the
+  // elements of the `breakpoints` array.
+  array<Breakpoint> breakpoints;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(SetInstructionBreakpointsResponse);
+
+// Properties of a breakpoint passed to the `setInstructionBreakpoints` request
+struct InstructionBreakpoint {
+  // An expression for conditional breakpoints.
+  // It is only honored by a debug adapter if the corresponding capability
+  // `supportsConditionalBreakpoints` is true.
+  optional<string> condition;
+  // An expression that controls how many hits of the breakpoint are ignored.
+  // The debug adapter is expected to interpret the expression as needed.
+  // The attribute is only honored by a debug adapter if the corresponding
+  // capability `supportsHitConditionalBreakpoints` is true.
+  optional<string> hitCondition;
+  // The instruction reference of the breakpoint.
+  // This should be a memory or instruction pointer reference from an
+  // `EvaluateResponse`, `Variable`, `StackFrame`, `GotoTarget`, or
+  // `Breakpoint`.
+  string instructionReference;
+  // The offset from the instruction reference.
+  // This can be negative.
+  optional<integer> offset;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(InstructionBreakpoint);
+
+// Replaces all existing instruction breakpoints. Typically, instruction
+// breakpoints would be set from a disassembly window. To clear all instruction
+// breakpoints, specify an empty array. When an instruction breakpoint is hit, a
+// `stopped` event (with reason `instruction breakpoint`) is generated. Clients
+// should only call this request if the corresponding capability
+// `supportsInstructionBreakpoints` is true.
+struct SetInstructionBreakpointsRequest : public Request {
+  using Response = SetInstructionBreakpointsResponse;
+  // The instruction references of the breakpoints
+  array<InstructionBreakpoint> breakpoints;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(SetInstructionBreakpointsRequest);
+
+// Response to `setVariable` request.
+struct SetVariableResponse : public Response {
+  // The number of indexed child variables.
+  // The client can use this information to present the variables in a paged UI
+  // and fetch them in chunks. The value should be less than or equal to
+  // 2147483647 (2^31-1).
+  optional<integer> indexedVariables;
+  // The number of named child variables.
+  // The client can use this information to present the variables in a paged UI
+  // and fetch them in chunks. The value should be less than or equal to
+  // 2147483647 (2^31-1).
+  optional<integer> namedVariables;
+  // The type of the new value. Typically shown in the UI when hovering over the
+  // value.
+  optional<string> type;
+  // The new value of the variable.
+  string value;
+  // If `variablesReference` is > 0, the new value is structured and its
+  // children can be retrieved by passing `variablesReference` to the
+  // `variables` request as long as execution remains suspended. See 'Lifetime
+  // of Object References' in the Overview section for details.
+  optional<integer> variablesReference;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(SetVariableResponse);
+
+// Set the variable with the given name in the variable container to a new
+// value. Clients should only call this request if the corresponding capability
+// `supportsSetVariable` is true. If a debug adapter implements both
+// `setVariable` and `setExpression`, a client will only use `setExpression` if
+// the variable has an `evaluateName` property.
+struct SetVariableRequest : public Request {
+  using Response = SetVariableResponse;
+  // Specifies details on how to format the response value.
+  optional<ValueFormat> format;
+  // The name of the variable in the container.
+  string name;
+  // The value of the variable.
+  string value;
+  // The reference of the variable container. The `variablesReference` must have
+  // been obtained in the current suspended state. See 'Lifetime of Object
+  // References' in the Overview section for details.
+  integer variablesReference;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(SetVariableRequest);
+
+// Response to `source` request.
+struct SourceResponse : public Response {
+  // Content of the source reference.
+  string content;
+  // Content type (MIME type) of the source.
+  optional<string> mimeType;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(SourceResponse);
+
+// The request retrieves the source code for a given source reference.
+struct SourceRequest : public Request {
+  using Response = SourceResponse;
+  // Specifies the source content to load. Either `source.path` or
+  // `source.sourceReference` must be specified.
+  optional<Source> source;
+  // The reference to the source. This is the same as `source.sourceReference`.
+  // This is provided for backward compatibility since old clients do not
+  // understand the `source` attribute.
+  integer sourceReference;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(SourceRequest);
+
+// A Stackframe contains the source location.
+struct StackFrame {
+  // Indicates whether this frame can be restarted with the `restart` request.
+  // Clients should only use this if the debug adapter supports the `restart`
+  // request and the corresponding capability `supportsRestartRequest` is true.
+  // If a debug adapter has this capability, then `canRestart` defaults to
+  // `true` if the property is absent.
+  optional<boolean> canRestart;
+  // Start position of the range covered by the stack frame. It is measured in
+  // UTF-16 code units and the client capability `columnsStartAt1` determines
+  // whether it is 0- or 1-based. If attribute `source` is missing or doesn't
+  // exist, `column` is 0 and should be ignored by the client.
+  integer column;
+  // End position of the range covered by the stack frame. It is measured in
+  // UTF-16 code units and the client capability `columnsStartAt1` determines
+  // whether it is 0- or 1-based.
+  optional<integer> endColumn;
+  // The end line of the range covered by the stack frame.
+  optional<integer> endLine;
+  // An identifier for the stack frame. It must be unique across all threads.
+  // This id can be used to retrieve the scopes of the frame with the `scopes`
+  // request or to restart the execution of a stack frame.
+  integer id;
+  // A memory reference for the current instruction pointer in this frame.
+  optional<string> instructionPointerReference;
+  // The line within the source of the frame. If the source attribute is missing
+  // or doesn't exist, `line` is 0 and should be ignored by the client.
+  integer line;
+  // The module associated with this frame, if any.
+  optional<variant<integer, string>> moduleId;
+  // The name of the stack frame, typically a method name.
+  string name;
+  // A hint for how to present this frame in the UI.
+  // A value of `label` can be used to indicate that the frame is an artificial
+  // frame that is used as a visual label or separator. A value of `subtle` can
+  // be used to change the appearance of a frame in a 'subtle' way.
+  //
+  // Must be one of the following enumeration values:
+  // 'normal', 'label', 'subtle'
+  optional<string> presentationHint;
+  // The source of the frame.
+  optional<Source> source;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(StackFrame);
+
+// Response to `stackTrace` request.
+struct StackTraceResponse : public Response {
+  // The frames of the stack frame. If the array has length zero, there are no
+  // stack frames available. This means that there is no location information
+  // available.
+  array<StackFrame> stackFrames;
+  // The total number of frames available in the stack. If omitted or if
+  // `totalFrames` is larger than the available frames, a client is expected to
+  // request frames until a request returns less frames than requested (which
+  // indicates the end of the stack). Returning monotonically increasing
+  // `totalFrames` values for subsequent requests can be used to enforce paging
+  // in the client.
+  optional<integer> totalFrames;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(StackTraceResponse);
+
+// Provides formatting information for a stack frame.
+struct StackFrameFormat : public ValueFormat {
+  // Includes all stack frames, including those the debug adapter might
+  // otherwise hide.
+  optional<boolean> includeAll;
+  // Displays the line number of the stack frame.
+  optional<boolean> line;
+  // Displays the module of the stack frame.
+  optional<boolean> module;
+  // Displays the names of parameters for the stack frame.
+  optional<boolean> parameterNames;
+  // Displays the types of parameters for the stack frame.
+  optional<boolean> parameterTypes;
+  // Displays the values of parameters for the stack frame.
+  optional<boolean> parameterValues;
+  // Displays parameters for the stack frame.
+  optional<boolean> parameters;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(StackFrameFormat);
+
+// The request returns a stacktrace from the current execution state of a given
+// thread. A client can request all stack frames by omitting the startFrame and
+// levels arguments. For performance-conscious clients and if the corresponding
+// capability `supportsDelayedStackTraceLoading` is true, stack frames can be
+// retrieved in a piecemeal way with the `startFrame` and `levels` arguments.
+// The response of the `stackTrace` request may contain a `totalFrames` property
+// that hints at the total number of frames in the stack. If a client needs this
+// total number upfront, it can issue a request for a single (first) frame and
+// depending on the value of `totalFrames` decide how to proceed. In any case a
+// client should be prepared to receive fewer frames than requested, which is an
+// indication that the end of the stack has been reached.
+struct StackTraceRequest : public Request {
+  using Response = StackTraceResponse;
+  // Specifies details on how to format the stack frames.
+  // The attribute is only honored by a debug adapter if the corresponding
+  // capability `supportsValueFormattingOptions` is true.
+  optional<StackFrameFormat> format;
+  // The maximum number of frames to return. If levels is not specified or 0,
+  // all frames are returned.
+  optional<integer> levels;
+  // The index of the first frame to return; if omitted frames start at 0.
+  optional<integer> startFrame;
+  // Retrieve the stacktrace for this thread.
+  integer threadId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(StackTraceRequest);
+
+// Response to `startDebugging` request. This is just an acknowledgement, so no
+// body field is required.
+struct StartDebuggingResponse : public Response {};
+
+DAP_DECLARE_STRUCT_TYPEINFO(StartDebuggingResponse);
+
+// This request is sent from the debug adapter to the client to start a new
+// debug session of the same type as the caller. This request should only be
+// sent if the corresponding client capability `supportsStartDebuggingRequest`
+// is true. A client implementation of `startDebugging` should start a new debug
+// session (of the same type as the caller) in the same way that the caller's
+// session was started. If the client supports hierarchical debug sessions, the
+// newly created session can be treated as a child of the caller session.
+struct StartDebuggingRequest : public Request {
+  using Response = StartDebuggingResponse;
+  // Arguments passed to the new debug session. The arguments must only contain
+  // properties understood by the `launch` or `attach` requests of the debug
+  // adapter and they must not contain any client-specific properties (e.g.
+  // `type`) or client-specific features (e.g. substitutable 'variables').
+  object configuration;
+  // Indicates whether the new debug session should be started with a `launch`
+  // or `attach` request.
+  //
+  // Must be one of the following enumeration values:
+  // 'launch', 'attach'
+  string request = "launch";
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(StartDebuggingRequest);
+
+// Response to `stepBack` request. This is just an acknowledgement, so no body
+// field is required.
+struct StepBackResponse : public Response {};
+
+DAP_DECLARE_STRUCT_TYPEINFO(StepBackResponse);
+
+// The request executes one backward step (in the given granularity) for the
+// specified thread and allows all other threads to run backward freely by
+// resuming them. If the debug adapter supports single thread execution (see
+// capability `supportsSingleThreadExecutionRequests`), setting the
+// `singleThread` argument to true prevents other suspended threads from
+// resuming. The debug adapter first sends the response and then a `stopped`
+// event (with reason `step`) after the step has completed. Clients should only
+// call this request if the corresponding capability `supportsStepBack` is true.
+struct StepBackRequest : public Request {
+  using Response = StepBackResponse;
+  // Stepping granularity to step. If no granularity is specified, a granularity
+  // of `statement` is assumed.
+  optional<SteppingGranularity> granularity;
+  // If this flag is true, all other suspended threads are not resumed.
+  optional<boolean> singleThread;
+  // Specifies the thread for which to resume execution for one step backwards
+  // (of the given granularity).
+  integer threadId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(StepBackRequest);
+
+// Response to `stepIn` request. This is just an acknowledgement, so no body
+// field is required.
+struct StepInResponse : public Response {};
+
+DAP_DECLARE_STRUCT_TYPEINFO(StepInResponse);
+
+// The request resumes the given thread to step into a function/method and
+// allows all other threads to run freely by resuming them. If the debug adapter
+// supports single thread execution (see capability
+// `supportsSingleThreadExecutionRequests`), setting the `singleThread` argument
+// to true prevents other suspended threads from resuming. If the request cannot
+// step into a target, `stepIn` behaves like the `next` request. The debug
+// adapter first sends the response and then a `stopped` event (with reason
+// `step`) after the step has completed. If there are multiple function/method
+// calls (or other targets) on the source line, the argument `targetId` can be
+// used to control into which target the `stepIn` should occur. The list of
+// possible targets for a given source line can be retrieved via the
+// `stepInTargets` request.
+struct StepInRequest : public Request {
+  using Response = StepInResponse;
+  // Stepping granularity. If no granularity is specified, a granularity of
+  // `statement` is assumed.
+  optional<SteppingGranularity> granularity;
+  // If this flag is true, all other suspended threads are not resumed.
+  optional<boolean> singleThread;
+  // Id of the target to step into.
+  optional<integer> targetId;
+  // Specifies the thread for which to resume execution for one step-into (of
+  // the given granularity).
+  integer threadId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(StepInRequest);
+
+// A `StepInTarget` can be used in the `stepIn` request and determines into
+// which single target the `stepIn` request should step.
+struct StepInTarget {
+  // Start position of the range covered by the step in target. It is measured
+  // in UTF-16 code units and the client capability `columnsStartAt1` determines
+  // whether it is 0- or 1-based.
+  optional<integer> column;
+  // End position of the range covered by the step in target. It is measured in
+  // UTF-16 code units and the client capability `columnsStartAt1` determines
+  // whether it is 0- or 1-based.
+  optional<integer> endColumn;
+  // The end line of the range covered by the step-in target.
+  optional<integer> endLine;
+  // Unique identifier for a step-in target.
+  integer id;
+  // The name of the step-in target (shown in the UI).
+  string label;
+  // The line of the step-in target.
+  optional<integer> line;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(StepInTarget);
+
+// Response to `stepInTargets` request.
+struct StepInTargetsResponse : public Response {
+  // The possible step-in targets of the specified source location.
+  array<StepInTarget> targets;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(StepInTargetsResponse);
+
+// This request retrieves the possible step-in targets for the specified stack
+// frame. These targets can be used in the `stepIn` request. Clients should only
+// call this request if the corresponding capability
+// `supportsStepInTargetsRequest` is true.
+struct StepInTargetsRequest : public Request {
+  using Response = StepInTargetsResponse;
+  // The stack frame for which to retrieve the possible step-in targets.
+  integer frameId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(StepInTargetsRequest);
+
+// Response to `stepOut` request. This is just an acknowledgement, so no body
+// field is required.
+struct StepOutResponse : public Response {};
+
+DAP_DECLARE_STRUCT_TYPEINFO(StepOutResponse);
+
+// The request resumes the given thread to step out (return) from a
+// function/method and allows all other threads to run freely by resuming them.
+// If the debug adapter supports single thread execution (see capability
+// `supportsSingleThreadExecutionRequests`), setting the `singleThread` argument
+// to true prevents other suspended threads from resuming. The debug adapter
+// first sends the response and then a `stopped` event (with reason `step`)
+// after the step has completed.
+struct StepOutRequest : public Request {
+  using Response = StepOutResponse;
+  // Stepping granularity. If no granularity is specified, a granularity of
+  // `statement` is assumed.
+  optional<SteppingGranularity> granularity;
+  // If this flag is true, all other suspended threads are not resumed.
+  optional<boolean> singleThread;
+  // Specifies the thread for which to resume execution for one step-out (of the
+  // given granularity).
+  integer threadId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(StepOutRequest);
+
+// The event indicates that the execution of the debuggee has stopped due to
+// some condition. This can be caused by a breakpoint previously set, a stepping
+// request has completed, by executing a debugger statement etc.
+struct StoppedEvent : public Event {
+  // If `allThreadsStopped` is true, a debug adapter can announce that all
+  // threads have stopped.
+  // - The client should use this information to enable that all threads can be
+  // expanded to access their stacktraces.
+  // - If the attribute is missing or false, only the thread with the given
+  // `threadId` can be expanded.
+  optional<boolean> allThreadsStopped;
+  // The full reason for the event, e.g. 'Paused on exception'. This string is
+  // shown in the UI as is and can be translated.
+  optional<string> description;
+  // Ids of the breakpoints that triggered the event. In most cases there is
+  // only a single breakpoint but here are some examples for multiple
+  // breakpoints:
+  // - Different types of breakpoints map to the same location.
+  // - Multiple source breakpoints get collapsed to the same instruction by the
+  // compiler/runtime.
+  // - Multiple function breakpoints with different function names map to the
+  // same location.
+  optional<array<integer>> hitBreakpointIds;
+  // A value of true hints to the client that this event should not change the
+  // focus.
+  optional<boolean> preserveFocusHint;
+  // The reason for the event.
+  // For backward compatibility this string is shown in the UI if the
+  // `description` attribute is missing (but it must not be translated).
+  //
+  // May be one of the following enumeration values:
+  // 'step', 'breakpoint', 'exception', 'pause', 'entry', 'goto', 'function
+  // breakpoint', 'data breakpoint', 'instruction breakpoint'
+  string reason;
+  // Additional information. E.g. if reason is `exception`, text contains the
+  // exception name. This string is shown in the UI.
+  optional<string> text;
+  // The thread which was stopped.
+  optional<integer> threadId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(StoppedEvent);
+
+// Response to `terminate` request. This is just an acknowledgement, so no body
+// field is required.
+struct TerminateResponse : public Response {};
+
+DAP_DECLARE_STRUCT_TYPEINFO(TerminateResponse);
+
+// The `terminate` request is sent from the client to the debug adapter in order
+// to shut down the debuggee gracefully. Clients should only call this request
+// if the capability `supportsTerminateRequest` is true. Typically a debug
+// adapter implements `terminate` by sending a software signal which the
+// debuggee intercepts in order to clean things up properly before terminating
+// itself. Please note that this request does not directly affect the state of
+// the debug session: if the debuggee decides to veto the graceful shutdown for
+// any reason by not terminating itself, then the debug session just continues.
+// Clients can surface the `terminate` request as an explicit command or they
+// can integrate it into a two stage Stop command that first sends `terminate`
+// to request a graceful shutdown, and if that fails uses `disconnect` for a
+// forceful shutdown.
+struct TerminateRequest : public Request {
+  using Response = TerminateResponse;
+  // A value of true indicates that this `terminate` request is part of a
+  // restart sequence.
+  optional<boolean> restart;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(TerminateRequest);
+
+// Response to `terminateThreads` request. This is just an acknowledgement, no
+// body field is required.
+struct TerminateThreadsResponse : public Response {};
+
+DAP_DECLARE_STRUCT_TYPEINFO(TerminateThreadsResponse);
+
+// The request terminates the threads with the given ids.
+// Clients should only call this request if the corresponding capability
+// `supportsTerminateThreadsRequest` is true.
+struct TerminateThreadsRequest : public Request {
+  using Response = TerminateThreadsResponse;
+  // Ids of threads to be terminated.
+  optional<array<integer>> threadIds;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(TerminateThreadsRequest);
+
+// The event indicates that debugging of the debuggee has terminated. This does
+// **not** mean that the debuggee itself has exited.
+struct TerminatedEvent : public Event {
+  // A debug adapter may set `restart` to true (or to an arbitrary object) to
+  // request that the client restarts the session. The value is not interpreted
+  // by the client and passed unmodified as an attribute `__restart` to the
+  // `launch` and `attach` requests.
+  optional<variant<array<any>, boolean, integer, null, number, object, string>>
+      restart;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(TerminatedEvent);
+
+// The event indicates that a thread has started or exited.
+struct ThreadEvent : public Event {
+  // The reason for the event.
+  //
+  // May be one of the following enumeration values:
+  // 'started', 'exited'
+  string reason;
+  // The identifier of the thread.
+  integer threadId;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ThreadEvent);
+
+// A Thread
+struct Thread {
+  // Unique identifier for the thread.
+  integer id;
+  // The name of the thread.
+  string name;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(Thread);
+
+// Response to `threads` request.
+struct ThreadsResponse : public Response {
+  // All threads.
+  array<Thread> threads;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ThreadsResponse);
+
+// The request retrieves a list of all threads.
+struct ThreadsRequest : public Request {
+  using Response = ThreadsResponse;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(ThreadsRequest);
+
+// A Variable is a name/value pair.
+// The `type` attribute is shown if space permits or when hovering over the
+// variable's name. The `kind` attribute is used to render additional properties
+// of the variable, e.g. different icons can be used to indicate that a variable
+// is public or private. If the value is structured (has children), a handle is
+// provided to retrieve the children with the `variables` request. If the number
+// of named or indexed children is large, the numbers should be returned via the
+// `namedVariables` and `indexedVariables` attributes. The client can use this
+// information to present the children in a paged UI and fetch them in chunks.
+struct Variable {
+  // The evaluatable name of this variable which can be passed to the `evaluate`
+  // request to fetch the variable's value.
+  optional<string> evaluateName;
+  // The number of indexed child variables.
+  // The client can use this information to present the children in a paged UI
+  // and fetch them in chunks.
+  optional<integer> indexedVariables;
+  // The memory reference for the variable if the variable represents executable
+  // code, such as a function pointer. This attribute is only required if the
+  // corresponding capability `supportsMemoryReferences` is true.
+  optional<string> memoryReference;
+  // The variable's name.
+  string name;
+  // The number of named child variables.
+  // The client can use this information to present the children in a paged UI
+  // and fetch them in chunks.
+  optional<integer> namedVariables;
+  // Properties of a variable that can be used to determine how to render the
+  // variable in the UI.
+  optional<VariablePresentationHint> presentationHint;
+  // The type of the variable's value. Typically shown in the UI when hovering
+  // over the value. This attribute should only be returned by a debug adapter
+  // if the corresponding capability `supportsVariableType` is true.
+  optional<string> type;
+  // The variable's value.
+  // This can be a multi-line text, e.g. for a function the body of a function.
+  // For structured variables (which do not have a simple value), it is
+  // recommended to provide a one-line representation of the structured object.
+  // This helps to identify the structured object in the collapsed state when
+  // its children are not yet visible. An empty string can be used if no value
+  // should be shown in the UI.
+  string value;
+  // If `variablesReference` is > 0, the variable is structured and its children
+  // can be retrieved by passing `variablesReference` to the `variables` request
+  // as long as execution remains suspended. See 'Lifetime of Object References'
+  // in the Overview section for details.
+  integer variablesReference;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(Variable);
+
+// Response to `variables` request.
+struct VariablesResponse : public Response {
+  // All (or a range) of variables for the given variable reference.
+  array<Variable> variables;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(VariablesResponse);
+
+// Retrieves all child variables for the given variable reference.
+// A filter can be used to limit the fetched children to either named or indexed
+// children.
+struct VariablesRequest : public Request {
+  using Response = VariablesResponse;
+  // The number of variables to return. If count is missing or 0, all variables
+  // are returned.
+  optional<integer> count;
+  // Filter to limit the child variables to either named or indexed. If omitted,
+  // both types are fetched.
+  //
+  // Must be one of the following enumeration values:
+  // 'indexed', 'named'
+  optional<string> filter;
+  // Specifies details on how to format the Variable values.
+  // The attribute is only honored by a debug adapter if the corresponding
+  // capability `supportsValueFormattingOptions` is true.
+  optional<ValueFormat> format;
+  // The index of the first variable to return; if omitted children start at 0.
+  optional<integer> start;
+  // The variable for which to retrieve its children. The `variablesReference`
+  // must have been obtained in the current suspended state. See 'Lifetime of
+  // Object References' in the Overview section for details.
+  integer variablesReference;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(VariablesRequest);
+
+// Response to `writeMemory` request.
+struct WriteMemoryResponse : public Response {
+  // Property that should be returned when `allowPartial` is true to indicate
+  // the number of bytes starting from address that were successfully written.
+  optional<integer> bytesWritten;
+  // Property that should be returned when `allowPartial` is true to indicate
+  // the offset of the first byte of data successfully written. Can be negative.
+  optional<integer> offset;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(WriteMemoryResponse);
+
+// Writes bytes to memory at the provided location.
+// Clients should only call this request if the corresponding capability
+// `supportsWriteMemoryRequest` is true.
+struct WriteMemoryRequest : public Request {
+  using Response = WriteMemoryResponse;
+  // Property to control partial writes. If true, the debug adapter should
+  // attempt to write memory even if the entire memory region is not writable.
+  // In such a case the debug adapter should stop after hitting the first byte
+  // of memory that cannot be written and return the number of bytes written in
+  // the response via the `offset` and `bytesWritten` properties. If false or
+  // missing, a debug adapter should attempt to verify the region is writable
+  // before writing, and fail the response if it is not.
+  optional<boolean> allowPartial;
+  // Bytes to write, encoded using base64.
+  string data;
+  // Memory reference to the base location to which data should be written.
+  string memoryReference;
+  // Offset (in bytes) to be applied to the reference location before writing
+  // data. Can be negative.
+  optional<integer> offset;
+};
+
+DAP_DECLARE_STRUCT_TYPEINFO(WriteMemoryRequest);
+
+}  // namespace dap
+
+#endif  // dap_protocol_h
diff --git a/Utilities/cmcppdap/include/dap/serialization.h b/Utilities/cmcppdap/include/dap/serialization.h
new file mode 100644
index 0000000..c7d4c5e
--- /dev/null
+++ b/Utilities/cmcppdap/include/dap/serialization.h
@@ -0,0 +1,253 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef dap_serialization_h
+#define dap_serialization_h
+
+#include "typeof.h"
+#include "types.h"
+
+#include <cstddef>  // ptrdiff_t
+#include <type_traits>
+
+namespace dap {
+
+// Field describes a single field of a struct.
+struct Field {
+  std::string name;      // name of the field
+  ptrdiff_t offset;      // offset of the field to the base of the struct
+  const TypeInfo* type;  // type of the field
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// Deserializer
+////////////////////////////////////////////////////////////////////////////////
+
+// Deserializer is the interface used to decode data from structured storage.
+// Methods that return a bool use this to indicate success.
+class Deserializer {
+ public:
+  virtual ~Deserializer() = default;
+
+  // deserialization methods for simple data types.
+  // If the stored object is not of the correct type, then these function will
+  // return false.
+  virtual bool deserialize(boolean*) const = 0;
+  virtual bool deserialize(integer*) const = 0;
+  virtual bool deserialize(number*) const = 0;
+  virtual bool deserialize(string*) const = 0;
+  virtual bool deserialize(object*) const = 0;
+  virtual bool deserialize(any*) const = 0;
+
+  // count() returns the number of elements in the array object referenced by
+  // this Deserializer.
+  virtual size_t count() const = 0;
+
+  // array() calls the provided std::function for deserializing each array
+  // element in the array object referenced by this Deserializer.
+  virtual bool array(const std::function<bool(Deserializer*)>&) const = 0;
+
+  // field() calls the provided std::function for deserializing the field with
+  // the given name from the struct object referenced by this Deserializer.
+  virtual bool field(const std::string& name,
+                     const std::function<bool(Deserializer*)>&) const = 0;
+
+  // deserialize() delegates to TypeOf<T>::type()->deserialize().
+  template <typename T,
+            typename = std::enable_if<TypeOf<T>::has_custom_serialization>>
+  inline bool deserialize(T*) const;
+
+  // deserialize() decodes an array.
+  template <typename T>
+  inline bool deserialize(dap::array<T>*) const;
+
+  // deserialize() decodes an optional.
+  template <typename T>
+  inline bool deserialize(dap::optional<T>*) const;
+
+  // deserialize() decodes an variant.
+  template <typename T0, typename... Types>
+  inline bool deserialize(dap::variant<T0, Types...>*) const;
+
+  // deserialize() decodes the struct field f with the given name.
+  template <typename T>
+  inline bool field(const std::string& name, T* f) const;
+};
+
+template <typename T, typename>
+bool Deserializer::deserialize(T* ptr) const {
+  return TypeOf<T>::type()->deserialize(this, ptr);
+}
+
+template <typename T>
+bool Deserializer::deserialize(dap::array<T>* vec) const {
+  auto n = count();
+  vec->resize(n);
+  size_t i = 0;
+  if (!array([&](Deserializer* d) { return d->deserialize(&(*vec)[i++]); })) {
+    return false;
+  }
+  return true;
+}
+
+template <typename T>
+bool Deserializer::deserialize(dap::optional<T>* opt) const {
+  T v;
+  if (deserialize(&v)) {
+    *opt = v;
+  }
+  return true;
+}
+
+template <typename T0, typename... Types>
+bool Deserializer::deserialize(dap::variant<T0, Types...>* var) const {
+  return deserialize(&var->value);
+}
+
+template <typename T>
+bool Deserializer::field(const std::string& name, T* v) const {
+  return this->field(name,
+                     [&](const Deserializer* d) { return d->deserialize(v); });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Serializer
+////////////////////////////////////////////////////////////////////////////////
+class FieldSerializer;
+
+// Serializer is the interface used to encode data to structured storage.
+// A Serializer is associated with a single storage object, whos type and value
+// is assigned by a call to serialize().
+// If serialize() is called multiple times on the same Serializer instance,
+// the last type and value is stored.
+// Methods that return a bool use this to indicate success.
+class Serializer {
+ public:
+  virtual ~Serializer() = default;
+
+  // serialization methods for simple data types.
+  virtual bool serialize(boolean) = 0;
+  virtual bool serialize(integer) = 0;
+  virtual bool serialize(number) = 0;
+  virtual bool serialize(const string&) = 0;
+  virtual bool serialize(const dap::object&) = 0;
+  virtual bool serialize(const any&) = 0;
+
+  // array() encodes count array elements to the array object referenced by this
+  // Serializer. The std::function will be called count times, each time with a
+  // Serializer that should be used to encode the n'th array element's data.
+  virtual bool array(size_t count, const std::function<bool(Serializer*)>&) = 0;
+
+  // object() begins encoding the object referenced by this Serializer.
+  // The std::function will be called with a FieldSerializer to serialize the
+  // object's fields.
+  virtual bool object(const std::function<bool(dap::FieldSerializer*)>&) = 0;
+
+  // remove() deletes the object referenced by this Serializer.
+  // remove() can be used to serialize optionals with no value assigned.
+  virtual void remove() = 0;
+
+  // serialize() delegates to TypeOf<T>::type()->serialize().
+  template <typename T,
+            typename = std::enable_if<TypeOf<T>::has_custom_serialization>>
+  inline bool serialize(const T&);
+
+  // serialize() encodes the given array.
+  template <typename T>
+  inline bool serialize(const dap::array<T>&);
+
+  // serialize() encodes the given optional.
+  template <typename T>
+  inline bool serialize(const dap::optional<T>& v);
+
+  // serialize() encodes the given variant.
+  template <typename T0, typename... Types>
+  inline bool serialize(const dap::variant<T0, Types...>&);
+
+  // deserialize() encodes the given string.
+  inline bool serialize(const char* v);
+ protected:
+  static inline const TypeInfo* get_any_type(const any&);
+  static inline const void* get_any_val(const any&);
+};
+
+inline const TypeInfo* Serializer::get_any_type(const any& a){
+  return a.type;
+}
+const void* Serializer::get_any_val(const any& a) {
+  return a.value;
+}
+
+template <typename T, typename>
+bool Serializer::serialize(const T& object) {
+  return TypeOf<T>::type()->serialize(this, &object);
+}
+
+template <typename T>
+bool Serializer::serialize(const dap::array<T>& vec) {
+  auto it = vec.begin();
+  return array(vec.size(), [&](Serializer* s) { return s->serialize(*it++); });
+}
+
+template <typename T>
+bool Serializer::serialize(const dap::optional<T>& opt) {
+  if (!opt.has_value()) {
+    remove();
+    return true;
+  }
+  return serialize(opt.value());
+}
+
+template <typename T0, typename... Types>
+bool Serializer::serialize(const dap::variant<T0, Types...>& var) {
+  return serialize(var.value);
+}
+
+bool Serializer::serialize(const char* v) {
+  return serialize(std::string(v));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// FieldSerializer
+////////////////////////////////////////////////////////////////////////////////
+
+// FieldSerializer is the interface used to serialize fields of an object.
+class FieldSerializer {
+ public:
+  using SerializeFunc = std::function<bool(Serializer*)>;
+  template <typename T>
+  using IsSerializeFunc = std::is_convertible<T, SerializeFunc>;
+
+  virtual ~FieldSerializer() = default;
+
+  // field() encodes a field to the struct object referenced by this Serializer.
+  // The SerializeFunc will be called with a Serializer used to encode the
+  // field's data.
+  virtual bool field(const std::string& name, const SerializeFunc&) = 0;
+
+  // field() encodes the field with the given name and value.
+  template <
+      typename T,
+      typename = typename std::enable_if<!IsSerializeFunc<T>::value>::type>
+  inline bool field(const std::string& name, const T& v);
+};
+
+template <typename T, typename>
+bool FieldSerializer::field(const std::string& name, const T& v) {
+  return this->field(name, [&](Serializer* s) { return s->serialize(v); });
+}
+
+}  // namespace dap
+
+#endif  // dap_serialization_h
diff --git a/Utilities/cmcppdap/include/dap/session.h b/Utilities/cmcppdap/include/dap/session.h
new file mode 100644
index 0000000..3933886
--- /dev/null
+++ b/Utilities/cmcppdap/include/dap/session.h
@@ -0,0 +1,449 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef dap_session_h
+#define dap_session_h
+
+#include "future.h"
+#include "io.h"
+#include "traits.h"
+#include "typeinfo.h"
+#include "typeof.h"
+
+#include <functional>
+
+namespace dap {
+
+// Forward declarations
+struct Request;
+struct Response;
+struct Event;
+
+////////////////////////////////////////////////////////////////////////////////
+// Error
+////////////////////////////////////////////////////////////////////////////////
+
+// Error represents an error message in response to a DAP request.
+struct Error {
+  Error() = default;
+  Error(const std::string& error);
+  Error(const char* msg, ...);
+
+  // operator bool() returns true if there is an error.
+  inline operator bool() const { return message.size() > 0; }
+
+  std::string message;  // empty represents success.
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// ResponseOrError<T>
+////////////////////////////////////////////////////////////////////////////////
+
+// ResponseOrError holds either the response to a DAP request or an error
+// message.
+template <typename T>
+struct ResponseOrError {
+  using Request = T;
+
+  inline ResponseOrError() = default;
+  inline ResponseOrError(const T& response);
+  inline ResponseOrError(T&& response);
+  inline ResponseOrError(const Error& error);
+  inline ResponseOrError(Error&& error);
+  inline ResponseOrError(const ResponseOrError& other);
+  inline ResponseOrError(ResponseOrError&& other);
+
+  inline ResponseOrError& operator=(const ResponseOrError& other);
+  inline ResponseOrError& operator=(ResponseOrError&& other);
+
+  T response;
+  Error error;  // empty represents success.
+};
+
+template <typename T>
+ResponseOrError<T>::ResponseOrError(const T& resp) : response(resp) {}
+template <typename T>
+ResponseOrError<T>::ResponseOrError(T&& resp) : response(std::move(resp)) {}
+template <typename T>
+ResponseOrError<T>::ResponseOrError(const Error& err) : error(err) {}
+template <typename T>
+ResponseOrError<T>::ResponseOrError(Error&& err) : error(std::move(err)) {}
+template <typename T>
+ResponseOrError<T>::ResponseOrError(const ResponseOrError& other)
+    : response(other.response), error(other.error) {}
+template <typename T>
+ResponseOrError<T>::ResponseOrError(ResponseOrError&& other)
+    : response(std::move(other.response)), error(std::move(other.error)) {}
+template <typename T>
+ResponseOrError<T>& ResponseOrError<T>::operator=(
+    const ResponseOrError& other) {
+  response = other.response;
+  error = other.error;
+  return *this;
+}
+template <typename T>
+ResponseOrError<T>& ResponseOrError<T>::operator=(ResponseOrError&& other) {
+  response = std::move(other.response);
+  error = std::move(other.error);
+  return *this;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Session
+////////////////////////////////////////////////////////////////////////////////
+
+// Session implements a DAP client or server endpoint.
+// The general usage is as follows:
+// (1) Create a session with Session::create().
+// (2) Register request and event handlers with registerHandler().
+// (3) Optionally register a protocol error handler with onError().
+// (3) Bind the session to the remote endpoint with bind().
+// (4) Send requests or events with send().
+class Session {
+  template <typename F, int N>
+  using ParamType = traits::ParameterType<F, N>;
+
+  template <typename T>
+  using IsRequest = traits::EnableIfIsType<dap::Request, T>;
+
+  template <typename T>
+  using IsEvent = traits::EnableIfIsType<dap::Event, T>;
+
+  template <typename F>
+  using IsRequestHandlerWithoutCallback = traits::EnableIf<
+      traits::CompatibleWith<F, std::function<void(dap::Request)>>::value>;
+
+  template <typename F, typename CallbackType>
+  using IsRequestHandlerWithCallback = traits::EnableIf<traits::CompatibleWith<
+      F,
+      std::function<void(dap::Request, std::function<void(CallbackType)>)>>::
+                                                            value>;
+
+ public:
+  virtual ~Session();
+
+  // ErrorHandler is the type of callback function used for reporting protocol
+  // errors.
+  using ErrorHandler = std::function<void(const char*)>;
+
+  // ClosedHandler is the type of callback function used to signal that a
+  // connected endpoint has closed.
+  using ClosedHandler = std::function<void()>;
+
+  // create() constructs and returns a new Session.
+  static std::unique_ptr<Session> create();
+
+  // onError() registers a error handler that will be called whenever a protocol
+  // error is encountered.
+  // Only one error handler can be bound at any given time, and later calls
+  // will replace the existing error handler.
+  virtual void onError(const ErrorHandler&) = 0;
+
+  // registerHandler() registers a request handler for a specific request type.
+  // The function F must have one of the following signatures:
+  //   ResponseOrError<ResponseType>(const RequestType&)
+  //   ResponseType(const RequestType&)
+  //   Error(const RequestType&)
+  template <typename F, typename RequestType = ParamType<F, 0>>
+  inline IsRequestHandlerWithoutCallback<F> registerHandler(F&& handler);
+
+  // registerHandler() registers a request handler for a specific request type.
+  // The handler has a response callback function for the second argument of the
+  // handler function. This callback may be called after the handler has
+  // returned.
+  // The function F must have the following signature:
+  //   void(const RequestType& request,
+  //        std::function<void(ResponseType)> response)
+  template <typename F,
+            typename RequestType = ParamType<F, 0>,
+            typename ResponseType = typename RequestType::Response>
+  inline IsRequestHandlerWithCallback<F, ResponseType> registerHandler(
+      F&& handler);
+
+  // registerHandler() registers a request handler for a specific request type.
+  // The handler has a response callback function for the second argument of the
+  // handler function. This callback may be called after the handler has
+  // returned.
+  // The function F must have the following signature:
+  //   void(const RequestType& request,
+  //        std::function<void(ResponseOrError<ResponseType>)> response)
+  template <typename F,
+            typename RequestType = ParamType<F, 0>,
+            typename ResponseType = typename RequestType::Response>
+  inline IsRequestHandlerWithCallback<F, ResponseOrError<ResponseType>>
+  registerHandler(F&& handler);
+
+  // registerHandler() registers a event handler for a specific event type.
+  // The function F must have the following signature:
+  //   void(const EventType&)
+  template <typename F, typename EventType = ParamType<F, 0>>
+  inline IsEvent<EventType> registerHandler(F&& handler);
+
+  // registerSentHandler() registers the function F to be called when a response
+  // of the specific type has been sent.
+  // The function F must have the following signature:
+  //   void(const ResponseOrError<ResponseType>&)
+  template <typename F,
+            typename ResponseType = typename ParamType<F, 0>::Request>
+  inline void registerSentHandler(F&& handler);
+
+  // send() sends the request to the connected endpoint and returns a
+  // future that is assigned the request response or error.
+  template <typename T, typename = IsRequest<T>>
+  future<ResponseOrError<typename T::Response>> send(const T& request);
+
+  // send() sends the event to the connected endpoint.
+  template <typename T, typename = IsEvent<T>>
+  void send(const T& event);
+
+  // bind() connects this Session to an endpoint using connect(), and then
+  // starts processing incoming messages with startProcessingMessages().
+  // onClose is the optional callback which will be called when the session
+  // endpoint has been closed.
+  inline void bind(const std::shared_ptr<Reader>& reader,
+                   const std::shared_ptr<Writer>& writer,
+                   const ClosedHandler& onClose);
+  inline void bind(const std::shared_ptr<ReaderWriter>& readerWriter,
+                   const ClosedHandler& onClose);
+
+  //////////////////////////////////////////////////////////////////////////////
+  // Note:
+  // Methods and members below this point are for advanced usage, and are more
+  // likely to change signature than the methods above.
+  // The methods above this point should be sufficient for most use cases.
+  //////////////////////////////////////////////////////////////////////////////
+
+  // connect() connects this Session to an endpoint.
+  // connect() can only be called once. Repeated calls will raise an error, but
+  // otherwise will do nothing.
+  // Note: This method is used for explicit control over message handling.
+  //       Most users will use bind() instead of calling this method directly.
+  virtual void connect(const std::shared_ptr<Reader>&,
+                       const std::shared_ptr<Writer>&) = 0;
+  inline void connect(const std::shared_ptr<ReaderWriter>&);
+
+  // startProcessingMessages() starts a new thread to receive and dispatch
+  // incoming messages.
+  // onClose is the optional callback which will be called when the session
+  // endpoint has been closed.
+  // Note: This method is used for explicit control over message handling.
+  //       Most users will use bind() instead of calling this method directly.
+  virtual void startProcessingMessages(const ClosedHandler& onClose = {}) = 0;
+
+  // getPayload() blocks until the next incoming message is received, returning
+  // the payload or an empty function if the connection was lost. The returned
+  // payload is function that can be called on any thread to dispatch the
+  // message to the Session handler.
+  // Note: This method is used for explicit control over message handling.
+  //       Most users will use bind() instead of calling this method directly.
+  virtual std::function<void()> getPayload() = 0;
+
+  // The callback function type called when a request handler is invoked, and
+  // the request returns a successful result.
+  // 'responseTypeInfo' is the type information of the response data structure.
+  // 'responseData' is a pointer to response payload data.
+  using RequestHandlerSuccessCallback =
+      std::function<void(const TypeInfo* responseTypeInfo,
+                         const void* responseData)>;
+
+  // The callback function type used to notify when a DAP request fails.
+  // 'responseTypeInfo' is the type information of the response data structure.
+  // 'message' is the error message
+  using RequestHandlerErrorCallback =
+      std::function<void(const TypeInfo* responseTypeInfo,
+                         const Error& message)>;
+
+  // The callback function type used to invoke a request handler.
+  // 'request' is a pointer to the request data structure
+  // 'onSuccess' is the function to call if the request completed succesfully.
+  // 'onError' is the function to call if the request failed.
+  // For each call of the request handler, 'onSuccess' or 'onError' must be
+  // called exactly once.
+  using GenericRequestHandler =
+      std::function<void(const void* request,
+                         const RequestHandlerSuccessCallback& onSuccess,
+                         const RequestHandlerErrorCallback& onError)>;
+
+  // The callback function type used to handle a response to a request.
+  // 'response' is a pointer to the response data structure. May be nullptr.
+  // 'error' is a pointer to the reponse error message. May be nullptr.
+  // One of 'data' or 'error' will be nullptr.
+  using GenericResponseHandler =
+      std::function<void(const void* response, const Error* error)>;
+
+  // The callback function type used to handle an event.
+  // 'event' is a pointer to the event data structure.
+  using GenericEventHandler = std::function<void(const void* event)>;
+
+  // The callback function type used to notify when a response has been sent
+  // from this session endpoint.
+  // 'response' is a pointer to the response data structure.
+  // 'error' is a pointer to the reponse error message. May be nullptr.
+  using GenericResponseSentHandler =
+      std::function<void(const void* response, const Error* error)>;
+
+  // registerHandler() registers 'handler' as the request handler callback for
+  // requests of the type 'typeinfo'.
+  virtual void registerHandler(const TypeInfo* typeinfo,
+                               const GenericRequestHandler& handler) = 0;
+
+  // registerHandler() registers 'handler' as the event handler callback for
+  // events of the type 'typeinfo'.
+  virtual void registerHandler(const TypeInfo* typeinfo,
+                               const GenericEventHandler& handler) = 0;
+
+  // registerHandler() registers 'handler' as the response-sent handler function
+  // which is called whenever a response of the type 'typeinfo' is sent from
+  // this session endpoint.
+  virtual void registerHandler(const TypeInfo* typeinfo,
+                               const GenericResponseSentHandler& handler) = 0;
+
+  // send() sends a request to the remote endpoint.
+  // 'requestTypeInfo' is the type info of the request data structure.
+  // 'requestTypeInfo' is the type info of the response data structure.
+  // 'request' is a pointer to the request data structure.
+  // 'responseHandler' is the handler function for the response.
+  virtual bool send(const dap::TypeInfo* requestTypeInfo,
+                    const dap::TypeInfo* responseTypeInfo,
+                    const void* request,
+                    const GenericResponseHandler& responseHandler) = 0;
+
+  // send() sends an event to the remote endpoint.
+  // 'eventTypeInfo' is the type info for the event data structure.
+  // 'event' is a pointer to the event data structure.
+  virtual bool send(const TypeInfo* eventTypeInfo, const void* event) = 0;
+};
+
+template <typename F, typename RequestType>
+Session::IsRequestHandlerWithoutCallback<F> Session::registerHandler(
+    F&& handler) {
+  using ResponseType = typename RequestType::Response;
+  const TypeInfo* typeinfo = TypeOf<RequestType>::type();
+  registerHandler(typeinfo,
+                  [handler](const void* args,
+                            const RequestHandlerSuccessCallback& onSuccess,
+                            const RequestHandlerErrorCallback& onError) {
+                    ResponseOrError<ResponseType> res =
+                        handler(*reinterpret_cast<const RequestType*>(args));
+                    if (res.error) {
+                      onError(TypeOf<ResponseType>::type(), res.error);
+                    } else {
+                      onSuccess(TypeOf<ResponseType>::type(), &res.response);
+                    }
+                  });
+}
+
+template <typename F, typename RequestType, typename ResponseType>
+Session::IsRequestHandlerWithCallback<F, ResponseType> Session::registerHandler(
+    F&& handler) {
+  using CallbackType = ParamType<F, 1>;
+  registerHandler(
+      TypeOf<RequestType>::type(),
+      [handler](const void* args,
+                const RequestHandlerSuccessCallback& onSuccess,
+                const RequestHandlerErrorCallback&) {
+        CallbackType responseCallback = [onSuccess](const ResponseType& res) {
+          onSuccess(TypeOf<ResponseType>::type(), &res);
+        };
+        handler(*reinterpret_cast<const RequestType*>(args), responseCallback);
+      });
+}
+
+template <typename F, typename RequestType, typename ResponseType>
+Session::IsRequestHandlerWithCallback<F, ResponseOrError<ResponseType>>
+Session::registerHandler(F&& handler) {
+  using CallbackType = ParamType<F, 1>;
+  registerHandler(
+      TypeOf<RequestType>::type(),
+      [handler](const void* args,
+                const RequestHandlerSuccessCallback& onSuccess,
+                const RequestHandlerErrorCallback& onError) {
+        CallbackType responseCallback =
+            [onError, onSuccess](const ResponseOrError<ResponseType>& res) {
+              if (res.error) {
+                onError(TypeOf<ResponseType>::type(), res.error);
+              } else {
+                onSuccess(TypeOf<ResponseType>::type(), &res.response);
+              }
+            };
+        handler(*reinterpret_cast<const RequestType*>(args), responseCallback);
+      });
+}
+
+template <typename F, typename T>
+Session::IsEvent<T> Session::registerHandler(F&& handler) {
+  auto cb = [handler](const void* args) {
+    handler(*reinterpret_cast<const T*>(args));
+  };
+  const TypeInfo* typeinfo = TypeOf<T>::type();
+  registerHandler(typeinfo, cb);
+}
+
+template <typename F, typename T>
+void Session::registerSentHandler(F&& handler) {
+  auto cb = [handler](const void* response, const Error* error) {
+    if (error != nullptr) {
+      handler(ResponseOrError<T>(*error));
+    } else {
+      handler(ResponseOrError<T>(*reinterpret_cast<const T*>(response)));
+    }
+  };
+  const TypeInfo* typeinfo = TypeOf<T>::type();
+  registerHandler(typeinfo, cb);
+}
+
+template <typename T, typename>
+future<ResponseOrError<typename T::Response>> Session::send(const T& request) {
+  using Response = typename T::Response;
+  promise<ResponseOrError<Response>> promise;
+  auto sent = send(TypeOf<T>::type(), TypeOf<Response>::type(), &request,
+                   [=](const void* result, const Error* error) {
+                     if (error != nullptr) {
+                       promise.set_value(ResponseOrError<Response>(*error));
+                     } else {
+                       promise.set_value(ResponseOrError<Response>(
+                           *reinterpret_cast<const Response*>(result)));
+                     }
+                   });
+  if (!sent) {
+    promise.set_value(Error("Failed to send request"));
+  }
+  return promise.get_future();
+}
+
+template <typename T, typename>
+void Session::send(const T& event) {
+  const TypeInfo* typeinfo = TypeOf<T>::type();
+  send(typeinfo, &event);
+}
+
+void Session::connect(const std::shared_ptr<ReaderWriter>& rw) {
+  connect(rw, rw);
+}
+
+void Session::bind(const std::shared_ptr<dap::Reader>& r,
+                   const std::shared_ptr<dap::Writer>& w,
+                   const ClosedHandler& onClose = {}) {
+  connect(r, w);
+  startProcessingMessages(onClose);
+}
+
+void Session::bind(const std::shared_ptr<ReaderWriter>& rw,
+                   const ClosedHandler& onClose = {}) {
+  bind(rw, rw, onClose);
+}
+
+}  // namespace dap
+
+#endif  // dap_session_h
diff --git a/Utilities/cmcppdap/include/dap/traits.h b/Utilities/cmcppdap/include/dap/traits.h
new file mode 100644
index 0000000..6a0c20d
--- /dev/null
+++ b/Utilities/cmcppdap/include/dap/traits.h
@@ -0,0 +1,159 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef dap_traits_h
+#define dap_traits_h
+
+#include <tuple>
+#include <type_traits>
+
+namespace dap {
+namespace traits {
+
+// NthTypeOf returns the `N`th type in `Types`
+template <int N, typename... Types>
+using NthTypeOf = typename std::tuple_element<N, std::tuple<Types...>>::type;
+
+// `IsTypeOrDerived<BASE, T>::value` is true iff `T` is of type `BASE`, or
+// derives from `BASE`.
+template <typename BASE, typename T>
+using IsTypeOrDerived = std::integral_constant<
+    bool,
+    std::is_base_of<BASE, typename std::decay<T>::type>::value ||
+        std::is_same<BASE, typename std::decay<T>::type>::value>;
+
+// `EachIsTypeOrDerived<N, BASES, TYPES>::value` is true iff all of the types in
+// the std::tuple `TYPES` is of, or derives from the corresponding indexed type
+// in the std::tuple `BASES`.
+// `N` must be equal to the number of types in both the std::tuple `BASES` and
+// `TYPES`.
+template <int N, typename BASES, typename TYPES>
+struct EachIsTypeOrDerived {
+  using base = typename std::tuple_element<N - 1, BASES>::type;
+  using type = typename std::tuple_element<N - 1, TYPES>::type;
+  using last_matches = IsTypeOrDerived<base, type>;
+  using others_match = EachIsTypeOrDerived<N - 1, BASES, TYPES>;
+  static constexpr bool value = last_matches::value && others_match::value;
+};
+
+// EachIsTypeOrDerived specialization for N = 1
+template <typename BASES, typename TYPES>
+struct EachIsTypeOrDerived<1, BASES, TYPES> {
+  using base = typename std::tuple_element<0, BASES>::type;
+  using type = typename std::tuple_element<0, TYPES>::type;
+  static constexpr bool value = IsTypeOrDerived<base, type>::value;
+};
+
+// EachIsTypeOrDerived specialization for N = 0
+template <typename BASES, typename TYPES>
+struct EachIsTypeOrDerived<0, BASES, TYPES> {
+  static constexpr bool value = true;
+};
+
+// Signature describes the signature of a function.
+template <typename RETURN, typename... PARAMETERS>
+struct Signature {
+  // The return type of the function signature
+  using ret = RETURN;
+  // The parameters of the function signature held in a std::tuple
+  using parameters = std::tuple<PARAMETERS...>;
+  // The type of the Nth parameter of function signature
+  template <std::size_t N>
+  using parameter = NthTypeOf<N, PARAMETERS...>;
+  // The total number of parameters
+  static constexpr std::size_t parameter_count = sizeof...(PARAMETERS);
+};
+
+// SignatureOf is a traits helper that infers the signature of the function,
+// method, static method, lambda, or function-like object `F`.
+template <typename F>
+struct SignatureOf {
+  // The signature of the function-like object `F`
+  using type = typename SignatureOf<decltype(&F::operator())>::type;
+};
+
+// SignatureOf specialization for a regular function or static method.
+template <typename R, typename... ARGS>
+struct SignatureOf<R (*)(ARGS...)> {
+  // The signature of the function-like object `F`
+  using type = Signature<typename std::decay<R>::type,
+                         typename std::decay<ARGS>::type...>;
+};
+
+// SignatureOf specialization for a non-static method.
+template <typename R, typename C, typename... ARGS>
+struct SignatureOf<R (C::*)(ARGS...)> {
+  // The signature of the function-like object `F`
+  using type = Signature<typename std::decay<R>::type,
+                         typename std::decay<ARGS>::type...>;
+};
+
+// SignatureOf specialization for a non-static, const method.
+template <typename R, typename C, typename... ARGS>
+struct SignatureOf<R (C::*)(ARGS...) const> {
+  // The signature of the function-like object `F`
+  using type = Signature<typename std::decay<R>::type,
+                         typename std::decay<ARGS>::type...>;
+};
+
+// SignatureOfT is an alias to `typename SignatureOf<F>::type`.
+template <typename F>
+using SignatureOfT = typename SignatureOf<F>::type;
+
+// ParameterType is an alias to `typename SignatureOf<F>::type::parameter<N>`.
+template <typename F, std::size_t N>
+using ParameterType = typename SignatureOfT<F>::template parameter<N>;
+
+// `HasSignature<F, S>::value` is true iff the function-like `F` has a matching
+// signature to the function-like `S`.
+template <typename F, typename S>
+using HasSignature = std::integral_constant<
+    bool,
+    std::is_same<SignatureOfT<F>, SignatureOfT<S>>::value>;
+
+// `Min<A, B>::value` resolves to the smaller value of A and B.
+template <std::size_t A, std::size_t B>
+using Min = std::integral_constant<std::size_t, (A < B ? A : B)>;
+
+// `CompatibleWith<F, S>::value` is true iff the function-like `F`
+// can be called with the argument types of the function-like `S`. Return type
+// of the two functions are not considered.
+template <typename F, typename S>
+using CompatibleWith = std::integral_constant<
+    bool,
+    (SignatureOfT<S>::parameter_count == SignatureOfT<F>::parameter_count) &&
+        EachIsTypeOrDerived<Min<SignatureOfT<S>::parameter_count,
+                                SignatureOfT<F>::parameter_count>::value,
+                            typename SignatureOfT<S>::parameters,
+                            typename SignatureOfT<F>::parameters>::value>;
+
+// If `CONDITION` is true then EnableIf resolves to type T, otherwise an
+// invalid type.
+template <bool CONDITION, typename T = void>
+using EnableIf = typename std::enable_if<CONDITION, T>::type;
+
+// If `BASE` is a base of `T` then EnableIfIsType resolves to type `TRUE_TY`,
+// otherwise an invalid type.
+template <typename BASE, typename T, typename TRUE_TY = void>
+using EnableIfIsType = EnableIf<IsTypeOrDerived<BASE, T>::value, TRUE_TY>;
+
+// If the function-like `F` has a matching signature to the function-like `S`
+// then EnableIfHasSignature resolves to type `TRUE_TY`, otherwise an invalid type.
+template <typename F, typename S, typename TRUE_TY = void>
+using EnableIfHasSignature = EnableIf<HasSignature<F, S>::value, TRUE_TY>;
+
+}  // namespace traits
+}  // namespace dap
+
+#endif  // dap_traits_h
diff --git a/Utilities/cmcppdap/include/dap/typeinfo.h b/Utilities/cmcppdap/include/dap/typeinfo.h
new file mode 100644
index 0000000..d99f277
--- /dev/null
+++ b/Utilities/cmcppdap/include/dap/typeinfo.h
@@ -0,0 +1,59 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef dap_typeinfo_h
+#define dap_typeinfo_h
+
+#include <functional>
+#include <string>
+
+namespace dap {
+
+class any;
+class Deserializer;
+class Serializer;
+
+// The TypeInfo interface provides basic runtime type information about DAP
+// types. TypeInfo is used by the serialization system to encode and decode DAP
+// requests, responses, events and structs.
+struct TypeInfo {
+  virtual ~TypeInfo();
+  virtual std::string name() const = 0;
+  virtual size_t size() const = 0;
+  virtual size_t alignment() const = 0;
+  virtual void construct(void*) const = 0;
+  virtual void copyConstruct(void* dst, const void* src) const = 0;
+  virtual void destruct(void*) const = 0;
+  virtual bool deserialize(const Deserializer*, void*) const = 0;
+  virtual bool serialize(Serializer*, const void*) const = 0;
+
+  // create() allocates and constructs the TypeInfo of type T, registers the
+  // pointer for deletion on cppdap library termination, and returns the pointer
+  // to T.
+  template <typename T, typename... ARGS>
+  static T* create(ARGS&&... args) {
+    auto typeinfo = new T(std::forward<ARGS>(args)...);
+    deleteOnExit(typeinfo);
+    return typeinfo;
+  }
+
+ private:
+  // deleteOnExit() ensures that the TypeInfo is destructed and deleted on
+  // library termination.
+  static void deleteOnExit(TypeInfo*);
+};
+
+}  // namespace dap
+
+#endif  // dap_typeinfo_h
diff --git a/Utilities/cmcppdap/include/dap/typeof.h b/Utilities/cmcppdap/include/dap/typeof.h
new file mode 100644
index 0000000..803bb8d
--- /dev/null
+++ b/Utilities/cmcppdap/include/dap/typeof.h
@@ -0,0 +1,266 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef dap_typeof_h
+#define dap_typeof_h
+
+#include "typeinfo.h"
+#include "types.h"
+
+#include "serialization.h"
+
+namespace dap {
+
+// BasicTypeInfo is an implementation of the TypeInfo interface for the simple
+// template type T.
+template <typename T>
+struct BasicTypeInfo : public TypeInfo {
+  constexpr BasicTypeInfo(std::string&& name) : name_(std::move(name)) {}
+
+  // TypeInfo compliance
+  inline std::string name() const override { return name_; }
+  inline size_t size() const override { return sizeof(T); }
+  inline size_t alignment() const override { return alignof(T); }
+  inline void construct(void* ptr) const override { new (ptr) T(); }
+  inline void copyConstruct(void* dst, const void* src) const override {
+    new (dst) T(*reinterpret_cast<const T*>(src));
+  }
+  inline void destruct(void* ptr) const override {
+    reinterpret_cast<T*>(ptr)->~T();
+  }
+  inline bool deserialize(const Deserializer* d, void* ptr) const override {
+    return d->deserialize(reinterpret_cast<T*>(ptr));
+  }
+  inline bool serialize(Serializer* s, const void* ptr) const override {
+    return s->serialize(*reinterpret_cast<const T*>(ptr));
+  }
+
+ private:
+  std::string name_;
+};
+
+// TypeOf has a template specialization for each DAP type, each declaring a
+// const TypeInfo* type() static member function that describes type T.
+template <typename T>
+struct TypeOf {};
+
+template <>
+struct TypeOf<boolean> {
+  static const TypeInfo* type();
+};
+
+template <>
+struct TypeOf<string> {
+  static const TypeInfo* type();
+};
+
+template <>
+struct TypeOf<integer> {
+  static const TypeInfo* type();
+};
+
+template <>
+struct TypeOf<number> {
+  static const TypeInfo* type();
+};
+
+template <>
+struct TypeOf<object> {
+  static const TypeInfo* type();
+};
+
+template <>
+struct TypeOf<any> {
+  static const TypeInfo* type();
+};
+
+template <>
+struct TypeOf<null> {
+  static const TypeInfo* type();
+};
+
+template <typename T>
+struct TypeOf<array<T>> {
+  static inline const TypeInfo* type() {
+    static auto typeinfo = TypeInfo::create<BasicTypeInfo<array<T>>>(
+        "array<" + TypeOf<T>::type()->name() + ">");
+    return typeinfo;
+  }
+};
+
+template <typename T0, typename... Types>
+struct TypeOf<variant<T0, Types...>> {
+  static inline const TypeInfo* type() {
+    static auto typeinfo =
+        TypeInfo::create<BasicTypeInfo<variant<T0, Types...>>>("variant");
+    return typeinfo;
+  }
+};
+
+template <typename T>
+struct TypeOf<optional<T>> {
+  static inline const TypeInfo* type() {
+    static auto typeinfo = TypeInfo::create<BasicTypeInfo<optional<T>>>(
+        "optional<" + TypeOf<T>::type()->name() + ">");
+    return typeinfo;
+  }
+};
+
+// DAP_OFFSETOF() macro is a generalization of the offsetof() macro defined in
+// <cstddef>. It evaluates to the offset of the given field, with fewer
+// restrictions than offsetof(). We cast the address '32' and subtract it again,
+// because null-dereference is undefined behavior.
+#define DAP_OFFSETOF(s, m) \
+  ((int)(size_t) & reinterpret_cast<const volatile char&>((((s*)32)->m)) - 32)
+
+// internal functionality
+namespace detail {
+template <class T, class M>
+M member_type(M T::*);
+}  // namespace detail
+
+// DAP_TYPEOF() returns the type of the struct (s) member (m).
+#define DAP_TYPEOF(s, m) decltype(detail::member_type(&s::m))
+
+// DAP_FIELD() declares a structure field for the DAP_IMPLEMENT_STRUCT_TYPEINFO
+// macro.
+// FIELD is the name of the struct field.
+// NAME is the serialized name of the field, as described by the DAP
+// specification.
+#define DAP_FIELD(FIELD, NAME)                       \
+  ::dap::Field {                                     \
+    NAME, DAP_OFFSETOF(StructTy, FIELD),             \
+        TypeOf<DAP_TYPEOF(StructTy, FIELD)>::type(), \
+  }
+
+// DAP_DECLARE_STRUCT_TYPEINFO() declares a TypeOf<> specialization for STRUCT.
+// Must be used within the 'dap' namespace.
+#define DAP_DECLARE_STRUCT_TYPEINFO(STRUCT)                         \
+  template <>                                                       \
+  struct TypeOf<STRUCT> {                                           \
+    static constexpr bool has_custom_serialization = true;          \
+    static const TypeInfo* type();                                  \
+    static bool deserializeFields(const Deserializer*, void* obj);  \
+    static bool serializeFields(FieldSerializer*, const void* obj); \
+  }
+
+// DAP_IMPLEMENT_STRUCT_FIELD_SERIALIZATION() implements the deserializeFields()
+// and serializeFields() static methods of a TypeOf<> specialization. Used
+// internally by DAP_IMPLEMENT_STRUCT_TYPEINFO() and
+// DAP_IMPLEMENT_STRUCT_TYPEINFO_EXT().
+// You probably do not want to use this directly.
+#define DAP_IMPLEMENT_STRUCT_FIELD_SERIALIZATION(STRUCT, NAME, ...)           \
+  bool TypeOf<STRUCT>::deserializeFields(const Deserializer* fd, void* obj) { \
+    using StructTy = STRUCT;                                                  \
+    (void)sizeof(StructTy); /* avoid unused 'using' warning */                \
+    for (auto field : std::initializer_list<Field>{__VA_ARGS__}) {            \
+      if (!fd->field(field.name, [&](Deserializer* d) {                       \
+            auto ptr = reinterpret_cast<uint8_t*>(obj) + field.offset;        \
+            return field.type->deserialize(d, ptr);                           \
+          })) {                                                               \
+        return false;                                                         \
+      }                                                                       \
+    }                                                                         \
+    return true;                                                              \
+  }                                                                           \
+  bool TypeOf<STRUCT>::serializeFields(FieldSerializer* fs, const void* obj) {\
+    using StructTy = STRUCT;                                                  \
+    (void)sizeof(StructTy); /* avoid unused 'using' warning */                \
+    for (auto field : std::initializer_list<Field>{__VA_ARGS__}) {            \
+      if (!fs->field(field.name, [&](Serializer* s) {                         \
+            auto ptr = reinterpret_cast<const uint8_t*>(obj) + field.offset;  \
+            return field.type->serialize(s, ptr);                             \
+          })) {                                                               \
+        return false;                                                         \
+      }                                                                       \
+    }                                                                         \
+    return true;                                                              \
+  }
+
+// DAP_IMPLEMENT_STRUCT_TYPEINFO() implements the type() member function for the
+// TypeOf<> specialization for STRUCT.
+// STRUCT is the structure typename.
+// NAME is the serialized name of the structure, as described by the DAP
+// specification. The variadic (...) parameters should be a repeated list of
+// DAP_FIELD()s, one for each field of the struct.
+// Must be used within the 'dap' namespace.
+#define DAP_IMPLEMENT_STRUCT_TYPEINFO(STRUCT, NAME, ...)                    \
+  DAP_IMPLEMENT_STRUCT_FIELD_SERIALIZATION(STRUCT, NAME, __VA_ARGS__)       \
+  const ::dap::TypeInfo* TypeOf<STRUCT>::type() {                           \
+    struct TI : BasicTypeInfo<STRUCT> {                                     \
+      TI() : BasicTypeInfo<STRUCT>(NAME) {}                                 \
+      bool deserialize(const Deserializer* d, void* obj) const override {   \
+        return deserializeFields(d, obj);                                   \
+      }                                                                     \
+      bool serialize(Serializer* s, const void* obj) const override {       \
+        return s->object(                                                   \
+            [&](FieldSerializer* fs) { return serializeFields(fs, obj); }); \
+      }                                                                     \
+    };                                                                      \
+    static TI typeinfo;                                                     \
+    return &typeinfo;                                                       \
+  }
+
+// DAP_STRUCT_TYPEINFO() is a helper for declaring and implementing a TypeOf<>
+// specialization for STRUCT in a single statement.
+// Must be used within the 'dap' namespace.
+#define DAP_STRUCT_TYPEINFO(STRUCT, NAME, ...) \
+  DAP_DECLARE_STRUCT_TYPEINFO(STRUCT);         \
+  DAP_IMPLEMENT_STRUCT_TYPEINFO(STRUCT, NAME, __VA_ARGS__)
+
+// DAP_IMPLEMENT_STRUCT_TYPEINFO_EXT() implements the type() member function for
+// the TypeOf<> specialization for STRUCT that derives from BASE.
+// STRUCT is the structure typename.
+// BASE is the base structure typename.
+// NAME is the serialized name of the structure, as described by the DAP
+// specification. The variadic (...) parameters should be a repeated list of
+// DAP_FIELD()s, one for each field of the struct.
+// Must be used within the 'dap' namespace.
+#define DAP_IMPLEMENT_STRUCT_TYPEINFO_EXT(STRUCT, BASE, NAME, ...)        \
+  static_assert(std::is_base_of<BASE, STRUCT>::value,                     \
+                #STRUCT " does not derive from " #BASE);                  \
+  DAP_IMPLEMENT_STRUCT_FIELD_SERIALIZATION(STRUCT, NAME, __VA_ARGS__)     \
+  const ::dap::TypeInfo* TypeOf<STRUCT>::type() {                         \
+    struct TI : BasicTypeInfo<STRUCT> {                                   \
+      TI() : BasicTypeInfo<STRUCT>(NAME) {}                               \
+      bool deserialize(const Deserializer* d, void* obj) const override { \
+        auto derived = static_cast<STRUCT*>(obj);                         \
+        auto base = static_cast<BASE*>(obj);                              \
+        return TypeOf<BASE>::deserializeFields(d, base) &&                \
+               deserializeFields(d, derived);                             \
+      }                                                                   \
+      bool serialize(Serializer* s, const void* obj) const override {     \
+        return s->object([&](FieldSerializer* fs) {                       \
+          auto derived = static_cast<const STRUCT*>(obj);                 \
+          auto base = static_cast<const BASE*>(obj);                      \
+          return TypeOf<BASE>::serializeFields(fs, base) &&               \
+                 serializeFields(fs, derived);                            \
+        });                                                               \
+      }                                                                   \
+    };                                                                    \
+    static TI typeinfo;                                                   \
+    return &typeinfo;                                                     \
+  }
+
+// DAP_STRUCT_TYPEINFO_EXT() is a helper for declaring and implementing a
+// TypeOf<> specialization for STRUCT that derives from BASE in a single
+// statement.
+// Must be used within the 'dap' namespace.
+#define DAP_STRUCT_TYPEINFO_EXT(STRUCT, BASE, NAME, ...) \
+  DAP_DECLARE_STRUCT_TYPEINFO(STRUCT);                   \
+  DAP_IMPLEMENT_STRUCT_TYPEINFO_EXT(STRUCT, BASE, NAME, __VA_ARGS__)
+
+}  // namespace dap
+
+#endif  // dap_typeof_h
diff --git a/Utilities/cmcppdap/include/dap/types.h b/Utilities/cmcppdap/include/dap/types.h
new file mode 100644
index 0000000..7954e87
--- /dev/null
+++ b/Utilities/cmcppdap/include/dap/types.h
@@ -0,0 +1,104 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// This file holds the basic serializable types used by the debug adapter
+// protocol.
+
+#ifndef dap_types_h
+#define dap_types_h
+
+#include "any.h"
+#include "optional.h"
+#include "variant.h"
+
+#include <unordered_map>
+#include <vector>
+
+#include <stdint.h>
+
+namespace dap {
+
+// string is a sequence of characters.
+// string defaults to an empty string.
+using string = std::string;
+
+// boolean holds a true or false value.
+// boolean defaults to false.
+class boolean {
+ public:
+  inline boolean() : val(false) {}
+  inline boolean(bool i) : val(i) {}
+  inline operator bool() const { return val; }
+  inline boolean& operator=(bool i) {
+    val = i;
+    return *this;
+  }
+
+ private:
+  bool val;
+};
+
+// integer holds a whole signed number.
+// integer defaults to 0.
+class integer {
+ public:
+  inline integer() : val(0) {}
+  inline integer(int64_t i) : val(i) {}
+  inline operator int64_t() const { return val; }
+  inline integer& operator=(int64_t i) {
+    val = i;
+    return *this;
+  }
+  inline integer operator++(int) {
+    auto copy = *this;
+    val++;
+    return copy;
+  }
+
+ private:
+  int64_t val;
+};
+
+// number holds a 64-bit floating point number.
+// number defaults to 0.
+class number {
+ public:
+  inline number() : val(0.0) {}
+  inline number(double i) : val(i) {}
+  inline operator double() const { return val; }
+  inline number& operator=(double i) {
+    val = i;
+    return *this;
+  }
+
+ private:
+  double val;
+};
+
+// array is a list of items of type T.
+// array defaults to an empty list.
+template <typename T>
+using array = std::vector<T>;
+
+// object is a map of string to any.
+// object defaults to an empty map.
+using object = std::unordered_map<string, any>;
+
+// null represents no value.
+// null is used by any to check for no-value.
+using null = std::nullptr_t;
+
+}  // namespace dap
+
+#endif  // dap_types_h
diff --git a/Utilities/cmcppdap/include/dap/variant.h b/Utilities/cmcppdap/include/dap/variant.h
new file mode 100644
index 0000000..96e57c2
--- /dev/null
+++ b/Utilities/cmcppdap/include/dap/variant.h
@@ -0,0 +1,108 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef dap_variant_h
+#define dap_variant_h
+
+#include "any.h"
+
+namespace dap {
+
+// internal functionality
+namespace detail {
+template <typename T, typename...>
+struct TypeIsIn {
+  static constexpr bool value = false;
+};
+
+template <typename T, typename List0, typename... ListN>
+struct TypeIsIn<T, List0, ListN...> {
+  static constexpr bool value =
+      std::is_same<T, List0>::value || TypeIsIn<T, ListN...>::value;
+};
+}  // namespace detail
+
+// variant represents a type-safe union of DAP types.
+// variant can hold a value of any of the template argument types.
+// variant defaults to a default-constructed T0.
+template <typename T0, typename... Types>
+class variant {
+ public:
+  // constructors
+  inline variant();
+  template <typename T>
+  inline variant(const T& val);
+
+  // assignment
+  template <typename T>
+  inline variant& operator=(const T& val);
+
+  // get() returns the contained value of the type T.
+  // If the any does not contain a value of type T, then get() will assert.
+  template <typename T>
+  inline T& get() const;
+
+  // is() returns true iff the contained value is of type T.
+  template <typename T>
+  inline bool is() const;
+
+  // accepts() returns true iff the variant accepts values of type T.
+  template <typename T>
+  static constexpr bool accepts();
+
+ private:
+  friend class Serializer;
+  friend class Deserializer;
+  any value;
+};
+
+template <typename T0, typename... Types>
+variant<T0, Types...>::variant() : value(T0()) {}
+
+template <typename T0, typename... Types>
+template <typename T>
+variant<T0, Types...>::variant(const T& v) : value(v) {
+  static_assert(accepts<T>(), "variant does not accept template type T");
+}
+
+template <typename T0, typename... Types>
+template <typename T>
+variant<T0, Types...>& variant<T0, Types...>::operator=(const T& v) {
+  static_assert(accepts<T>(), "variant does not accept template type T");
+  value = v;
+  return *this;
+}
+
+template <typename T0, typename... Types>
+template <typename T>
+T& variant<T0, Types...>::get() const {
+  static_assert(accepts<T>(), "variant does not accept template type T");
+  return value.get<T>();
+}
+
+template <typename T0, typename... Types>
+template <typename T>
+bool variant<T0, Types...>::is() const {
+  return value.is<T>();
+}
+
+template <typename T0, typename... Types>
+template <typename T>
+constexpr bool variant<T0, Types...>::accepts() {
+  return detail::TypeIsIn<T, T0, Types...>::value;
+}
+
+}  // namespace dap
+
+#endif  // dap_variant_h
diff --git a/Utilities/cmcppdap/src/any_test.cpp b/Utilities/cmcppdap/src/any_test.cpp
new file mode 100644
index 0000000..7dfb73c
--- /dev/null
+++ b/Utilities/cmcppdap/src/any_test.cpp
@@ -0,0 +1,262 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dap/any.h"
+#include "dap/typeof.h"
+#include "dap/types.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace dap {
+
+struct AnyTestObject {
+  dap::integer i;
+  dap::number n;
+};
+
+DAP_STRUCT_TYPEINFO(AnyTestObject,
+                    "AnyTestObject",
+                    DAP_FIELD(i, "i"),
+                    DAP_FIELD(n, "n"));
+
+inline bool operator==(const AnyTestObject& a, const AnyTestObject& b) {
+  return a.i == b.i && a.n == b.n;
+}
+
+}  // namespace dap
+
+namespace {
+
+template <typename T>
+struct TestValue {};
+
+template <>
+struct TestValue<dap::null> {
+  static const dap::null value;
+};
+template <>
+struct TestValue<dap::integer> {
+  static const dap::integer value;
+};
+template <>
+struct TestValue<dap::boolean> {
+  static const dap::boolean value;
+};
+template <>
+struct TestValue<dap::number> {
+  static const dap::number value;
+};
+template <>
+struct TestValue<dap::string> {
+  static const dap::string value;
+};
+template <>
+struct TestValue<dap::array<dap::string>> {
+  static const dap::array<dap::string> value;
+};
+template <>
+struct TestValue<dap::AnyTestObject> {
+  static const dap::AnyTestObject value;
+};
+
+const dap::null TestValue<dap::null>::value = nullptr;
+const dap::integer TestValue<dap::integer>::value = 20;
+const dap::boolean TestValue<dap::boolean>::value = true;
+const dap::number TestValue<dap::number>::value = 123.45;
+const dap::string TestValue<dap::string>::value = "hello world";
+const dap::array<dap::string> TestValue<dap::array<dap::string>>::value = {
+    "one", "two", "three"};
+const dap::AnyTestObject TestValue<dap::AnyTestObject>::value = {10, 20.30};
+
+}  // namespace
+
+TEST(Any, EmptyConstruct) {
+  dap::any any;
+  ASSERT_TRUE(any.is<dap::null>());
+  ASSERT_FALSE(any.is<dap::boolean>());
+  ASSERT_FALSE(any.is<dap::integer>());
+  ASSERT_FALSE(any.is<dap::number>());
+  ASSERT_FALSE(any.is<dap::object>());
+  ASSERT_FALSE(any.is<dap::string>());
+  ASSERT_FALSE(any.is<dap::array<dap::integer>>());
+  ASSERT_FALSE(any.is<dap::AnyTestObject>());
+}
+
+TEST(Any, Boolean) {
+  dap::any any(dap::boolean(true));
+  ASSERT_TRUE(any.is<dap::boolean>());
+  ASSERT_EQ(any.get<dap::boolean>(), dap::boolean(true));
+}
+
+TEST(Any, Integer) {
+  dap::any any(dap::integer(10));
+  ASSERT_TRUE(any.is<dap::integer>());
+  ASSERT_EQ(any.get<dap::integer>(), dap::integer(10));
+}
+
+TEST(Any, Number) {
+  dap::any any(dap::number(123.0f));
+  ASSERT_TRUE(any.is<dap::number>());
+  ASSERT_EQ(any.get<dap::number>(), dap::number(123.0f));
+}
+
+TEST(Any, String) {
+  dap::any any(dap::string("hello world"));
+  ASSERT_TRUE(any.is<dap::string>());
+  ASSERT_EQ(any.get<dap::string>(), dap::string("hello world"));
+}
+
+TEST(Any, Array) {
+  using array = dap::array<dap::integer>;
+  dap::any any(array({10, 20, 30}));
+  ASSERT_TRUE(any.is<array>());
+  ASSERT_EQ(any.get<array>(), array({10, 20, 30}));
+}
+
+TEST(Any, Object) {
+  dap::object o;
+  o["one"] = dap::integer(1);
+  o["two"] = dap::integer(2);
+  o["three"] = dap::integer(3);
+  dap::any any(o);
+  ASSERT_TRUE(any.is<dap::object>());
+  if (any.is<dap::object>()) {
+    auto got = any.get<dap::object>();
+    ASSERT_EQ(got.size(), 3U);
+    ASSERT_EQ(got.count("one"), 1U);
+    ASSERT_EQ(got.count("two"), 1U);
+    ASSERT_EQ(got.count("three"), 1U);
+    ASSERT_TRUE(got["one"].is<dap::integer>());
+    ASSERT_TRUE(got["two"].is<dap::integer>());
+    ASSERT_TRUE(got["three"].is<dap::integer>());
+    ASSERT_EQ(got["one"].get<dap::integer>(), dap::integer(1));
+    ASSERT_EQ(got["two"].get<dap::integer>(), dap::integer(2));
+    ASSERT_EQ(got["three"].get<dap::integer>(), dap::integer(3));
+  }
+}
+
+TEST(Any, TestObject) {
+  dap::any any(dap::AnyTestObject{5, 3.0});
+  ASSERT_TRUE(any.is<dap::AnyTestObject>());
+  ASSERT_EQ(any.get<dap::AnyTestObject>().i, 5);
+  ASSERT_EQ(any.get<dap::AnyTestObject>().n, 3.0);
+}
+
+template <typename T>
+class AnyT : public ::testing::Test {
+ protected:
+  template <typename T0,
+            typename = std::enable_if<std::is_same<T, T0>::value &&
+                                      !std::is_same<T0, dap::null>::value>>
+  void check_val(const dap::any& any, const T0& expect) {
+    ASSERT_EQ(any.is<T>(), any.is<T0>());
+    ASSERT_EQ(any.get<T>(), expect);
+  }
+
+  // Special case for Null assignment, as we can assign nullptr_t to any but
+  // can't `get()` it
+  template <typename = dap::null>
+  void check_val(const dap::any& any, const dap::null& expect) {
+    ASSERT_EQ(nullptr, expect);
+    ASSERT_TRUE(any.is<dap::null>());
+  }
+
+  void check_type(const dap::any& any) {
+    ASSERT_EQ(any.is<dap::null>(), (std::is_same<T, dap::null>::value));
+    ASSERT_EQ(any.is<dap::integer>(), (std::is_same<T, dap::integer>::value));
+    ASSERT_EQ(any.is<dap::boolean>(), (std::is_same<T, dap::boolean>::value));
+    ASSERT_EQ(any.is<dap::number>(), (std::is_same<T, dap::number>::value));
+    ASSERT_EQ(any.is<dap::string>(), (std::is_same<T, dap::string>::value));
+    ASSERT_EQ(any.is<dap::array<dap::string>>(),
+              (std::is_same<T, dap::array<dap::string>>::value));
+    ASSERT_EQ(any.is<dap::AnyTestObject>(),
+              (std::is_same<T, dap::AnyTestObject>::value));
+  }
+};
+TYPED_TEST_SUITE_P(AnyT);
+
+TYPED_TEST_P(AnyT, CopyConstruct) {
+  auto val = TestValue<TypeParam>::value;
+  dap::any any(val);
+  this->check_type(any);
+  this->check_val(any, val);
+}
+
+TYPED_TEST_P(AnyT, MoveConstruct) {
+  auto val = TestValue<TypeParam>::value;
+  dap::any any(std::move(val));
+  this->check_type(any);
+  this->check_val(any, val);
+}
+
+TYPED_TEST_P(AnyT, Assign) {
+  auto val = TestValue<TypeParam>::value;
+  dap::any any;
+  any = val;
+  this->check_type(any);
+  this->check_val(any, val);
+}
+
+TYPED_TEST_P(AnyT, MoveAssign) {
+  auto val = TestValue<TypeParam>::value;
+  dap::any any;
+  any = std::move(val);
+  this->check_type(any);
+  this->check_val(any, val);
+}
+
+TYPED_TEST_P(AnyT, RepeatedAssign) {
+  dap::string str = "hello world";
+  auto val = TestValue<TypeParam>::value;
+  dap::any any;
+  any = str;
+  any = val;
+  this->check_type(any);
+  this->check_val(any, val);
+}
+
+TYPED_TEST_P(AnyT, RepeatedMoveAssign) {
+  dap::string str = "hello world";
+  auto val = TestValue<TypeParam>::value;
+  dap::any any;
+  any = std::move(str);
+  any = std::move(val);
+  this->check_type(any);
+  this->check_val(any, val);
+}
+
+REGISTER_TYPED_TEST_SUITE_P(AnyT,
+                            CopyConstruct,
+                            MoveConstruct,
+                            Assign,
+                            MoveAssign,
+                            RepeatedAssign,
+                            RepeatedMoveAssign);
+
+using AnyTypes = ::testing::Types<dap::null,
+                                  dap::integer,
+                                  dap::boolean,
+                                  dap::number,
+                                  dap::string,
+                                  dap::array<dap::string>,
+                                  dap::AnyTestObject>;
+INSTANTIATE_TYPED_TEST_SUITE_P(T, AnyT, AnyTypes);
+
+TEST(Any, Reset) {
+  dap::any any(dap::integer(10));
+  ASSERT_TRUE(any.is<dap::integer>());
+  any.reset();
+  ASSERT_FALSE(any.is<dap::integer>());
+}
diff --git a/Utilities/cmcppdap/src/chan.h b/Utilities/cmcppdap/src/chan.h
new file mode 100644
index 0000000..f2345e9
--- /dev/null
+++ b/Utilities/cmcppdap/src/chan.h
@@ -0,0 +1,90 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef dap_chan_h
+#define dap_chan_h
+
+#include "dap/optional.h"
+
+#include <condition_variable>
+#include <mutex>
+#include <queue>
+
+namespace dap {
+
+template <typename T>
+struct Chan {
+ public:
+  void reset();
+  void close();
+  optional<T> take();
+  void put(T&& in);
+  void put(const T& in);
+
+ private:
+  bool closed = false;
+  std::queue<T> queue;
+  std::condition_variable cv;
+  std::mutex mutex;
+};
+
+template <typename T>
+void Chan<T>::reset() {
+  std::unique_lock<std::mutex> lock(mutex);
+  queue = {};
+  closed = false;
+}
+
+template <typename T>
+void Chan<T>::close() {
+  std::unique_lock<std::mutex> lock(mutex);
+  closed = true;
+  cv.notify_all();
+}
+
+template <typename T>
+optional<T> Chan<T>::take() {
+  std::unique_lock<std::mutex> lock(mutex);
+  cv.wait(lock, [&] { return queue.size() > 0 || closed; });
+  if (queue.size() == 0) {
+    return optional<T>();
+  }
+  auto out = std::move(queue.front());
+  queue.pop();
+  return optional<T>(std::move(out));
+}
+
+template <typename T>
+void Chan<T>::put(T&& in) {
+  std::unique_lock<std::mutex> lock(mutex);
+  auto notify = queue.size() == 0 && !closed;
+  queue.push(std::move(in));
+  if (notify) {
+    cv.notify_all();
+  }
+}
+
+template <typename T>
+void Chan<T>::put(const T& in) {
+  std::unique_lock<std::mutex> lock(mutex);
+  auto notify = queue.size() == 0 && !closed;
+  queue.push(in);
+  if (notify) {
+    cv.notify_all();
+  }
+}
+
+}  // namespace dap
+
+#endif  // dap_chan_h
diff --git a/Utilities/cmcppdap/src/chan_test.cpp b/Utilities/cmcppdap/src/chan_test.cpp
new file mode 100644
index 0000000..4d7e0a4
--- /dev/null
+++ b/Utilities/cmcppdap/src/chan_test.cpp
@@ -0,0 +1,35 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "chan.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include <thread>
+
+TEST(ChanTest, PutTakeClose) {
+  dap::Chan<int> chan;
+  auto thread = std::thread([&] {
+    chan.put(10);
+    chan.put(20);
+    chan.put(30);
+    chan.close();
+  });
+  EXPECT_EQ(chan.take(), dap::optional<int>(10));
+  EXPECT_EQ(chan.take(), dap::optional<int>(20));
+  EXPECT_EQ(chan.take(), dap::optional<int>(30));
+  EXPECT_EQ(chan.take(), dap::optional<int>());
+  thread.join();
+}
diff --git a/Utilities/cmcppdap/src/content_stream.cpp b/Utilities/cmcppdap/src/content_stream.cpp
new file mode 100644
index 0000000..05d7f47
--- /dev/null
+++ b/Utilities/cmcppdap/src/content_stream.cpp
@@ -0,0 +1,189 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "content_stream.h"
+
+#include "dap/io.h"
+
+#include <string.h>   // strlen
+#include <algorithm>  // std::min
+
+namespace dap {
+
+////////////////////////////////////////////////////////////////////////////////
+// ContentReader
+////////////////////////////////////////////////////////////////////////////////
+ContentReader::ContentReader(const std::shared_ptr<Reader>& reader)
+    : reader(reader) {}
+
+ContentReader& ContentReader::operator=(ContentReader&& rhs) noexcept {
+  buf = std::move(rhs.buf);
+  reader = std::move(rhs.reader);
+  return *this;
+}
+
+bool ContentReader::isOpen() {
+  return reader ? reader->isOpen() : false;
+}
+
+void ContentReader::close() {
+  if (reader) {
+    reader->close();
+  }
+}
+
+std::string ContentReader::read() {
+  matched_idx = 0;
+
+  // Find Content-Length header prefix
+  if (!scan("Content-Length:")) {
+    return "";
+  }
+
+  // Skip whitespace and tabs
+  while (matchAny(" \t")) {
+  }
+
+  // Parse length
+  size_t len = 0;
+  while (true) {
+    auto c = matchAny("0123456789");
+    if (c == 0) {
+      break;
+    }
+    len *= 10;
+    len += size_t(c) - size_t('0');
+  }
+  if (len == 0) {
+    return "";
+  }
+  // Expect \r\n\r\n
+  if (!match("\r\n\r\n")) {
+    return "";
+  }
+
+  // Read message
+  if (!buffer(len + matched_idx)) {
+    return "";
+  }
+
+  for (size_t i = 0; i < matched_idx; i++) {
+    buf.pop_front();
+  }
+
+  std::string out;
+  out.reserve(len);
+  for (size_t i = 0; i < len; i++) {
+    out.push_back(static_cast<char>(buf.front()));
+    buf.pop_front();
+  }
+  return out;
+}
+
+bool ContentReader::scan(const uint8_t* seq, size_t len) {
+  while (buffer(len)) {
+    if (match(seq, len)) {
+      return true;
+    }
+    buf.pop_front();
+  }
+  return false;
+}
+
+bool ContentReader::scan(const char* str) {
+  auto len = strlen(str);
+  return scan(reinterpret_cast<const uint8_t*>(str), len);
+}
+
+bool ContentReader::match(const uint8_t* seq, size_t len) {
+  if (!buffer(len + matched_idx)) {
+    return false;
+  }
+  auto it = matched_idx;
+  for (size_t i = 0; i < len; i++, it++) {
+    if (buf[it] != seq[i]) {
+      return false;
+    }
+  }
+
+  matched_idx += len;
+  return true;
+}
+
+bool ContentReader::match(const char* str) {
+  auto len = strlen(str);
+  return match(reinterpret_cast<const uint8_t*>(str), len);
+}
+
+char ContentReader::matchAny(const char* chars) {
+  if (!buffer(1 + matched_idx)) {
+    return false;
+  }
+  int c = buf[matched_idx];
+  if (auto p = strchr(chars, c)) {
+    matched_idx++;
+    return *p;
+  }
+  return 0;
+}
+
+bool ContentReader::buffer(size_t bytes) {
+  if (bytes < buf.size()) {
+    return true;
+  }
+  bytes -= buf.size();
+  while (bytes > 0) {
+    uint8_t chunk[256];
+    auto numWant = std::min(sizeof(chunk), bytes);
+    auto numGot = reader->read(chunk, numWant);
+    if (numGot == 0) {
+      return false;
+    }
+    for (size_t i = 0; i < numGot; i++) {
+      buf.push_back(chunk[i]);
+    }
+    bytes -= numGot;
+  }
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ContentWriter
+////////////////////////////////////////////////////////////////////////////////
+ContentWriter::ContentWriter(const std::shared_ptr<Writer>& rhs)
+    : writer(rhs) {}
+
+ContentWriter& ContentWriter::operator=(ContentWriter&& rhs) noexcept {
+  writer = std::move(rhs.writer);
+  return *this;
+}
+
+bool ContentWriter::isOpen() {
+  return writer ? writer->isOpen() : false;
+}
+
+void ContentWriter::close() {
+  if (writer) {
+    writer->close();
+  }
+}
+
+bool ContentWriter::write(const std::string& msg) const {
+  auto header =
+      std::string("Content-Length: ") + std::to_string(msg.size()) + "\r\n\r\n";
+  return writer->write(header.data(), header.size()) &&
+         writer->write(msg.data(), msg.size());
+}
+
+}  // namespace dap
diff --git a/Utilities/cmcppdap/src/content_stream.h b/Utilities/cmcppdap/src/content_stream.h
new file mode 100644
index 0000000..1fd0849
--- /dev/null
+++ b/Utilities/cmcppdap/src/content_stream.h
@@ -0,0 +1,69 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef dap_content_stream_h
+#define dap_content_stream_h
+
+#include <deque>
+#include <memory>
+#include <string>
+
+#include <stdint.h>
+
+namespace dap {
+
+// Forward declarations
+class Reader;
+class Writer;
+
+class ContentReader {
+ public:
+  ContentReader() = default;
+  ContentReader(const std::shared_ptr<Reader>&);
+  ContentReader& operator=(ContentReader&&) noexcept;
+
+  bool isOpen();
+  void close();
+  std::string read();
+
+ private:
+  bool scan(const uint8_t* seq, size_t len);
+  bool scan(const char* str);
+  bool match(const uint8_t* seq, size_t len);
+  bool match(const char* str);
+  char matchAny(const char* chars);
+  bool buffer(size_t bytes);
+
+  std::shared_ptr<Reader> reader;
+  std::deque<uint8_t> buf;
+  uint32_t matched_idx = 0;
+};
+
+class ContentWriter {
+ public:
+  ContentWriter() = default;
+  ContentWriter(const std::shared_ptr<Writer>&);
+  ContentWriter& operator=(ContentWriter&&) noexcept;
+
+  bool isOpen();
+  void close();
+  bool write(const std::string&) const;
+
+ private:
+  std::shared_ptr<Writer> writer;
+};
+
+}  // namespace dap
+
+#endif  // dap_content_stream_h
diff --git a/Utilities/cmcppdap/src/content_stream_test.cpp b/Utilities/cmcppdap/src/content_stream_test.cpp
new file mode 100644
index 0000000..80939a8
--- /dev/null
+++ b/Utilities/cmcppdap/src/content_stream_test.cpp
@@ -0,0 +1,99 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "content_stream.h"
+
+#include "string_buffer.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include <memory>
+
+namespace {
+
+// SingleByteReader wraps a dap::Reader to only provide a single byte for each
+// read() call, regardless of the number of bytes actually requested.
+class SingleByteReader : public dap::Reader {
+ public:
+  SingleByteReader(std::unique_ptr<dap::Reader>&& inner)
+      : inner(std::move(inner)) {}
+
+  bool isOpen() override { return inner->isOpen(); }
+  void close() override { return inner->close(); }
+  size_t read(void* buffer, size_t) override { return inner->read(buffer, 1); };
+
+ private:
+  std::unique_ptr<dap::Reader> inner;
+};
+
+}  // namespace
+
+TEST(ContentStreamTest, Write) {
+  auto sb = dap::StringBuffer::create();
+  auto ptr = sb.get();
+  dap::ContentWriter cw(std::move(sb));
+  cw.write("Content payload number one");
+  cw.write("Content payload number two");
+  cw.write("Content payload number three");
+  ASSERT_EQ(ptr->string(),
+            "Content-Length: 26\r\n\r\nContent payload number one"
+            "Content-Length: 26\r\n\r\nContent payload number two"
+            "Content-Length: 28\r\n\r\nContent payload number three");
+}
+
+TEST(ContentStreamTest, Read) {
+  auto sb = dap::StringBuffer::create();
+  sb->write("Content-Length: 26\r\n\r\nContent payload number one");
+  sb->write("some unrecognised garbage");
+  sb->write("Content-Length: 26\r\n\r\nContent payload number two");
+  sb->write("some more unrecognised garbage");
+  sb->write("Content-Length: 28\r\n\r\nContent payload number three");
+  dap::ContentReader cs(std::move(sb));
+  ASSERT_EQ(cs.read(), "Content payload number one");
+  ASSERT_EQ(cs.read(), "Content payload number two");
+  ASSERT_EQ(cs.read(), "Content payload number three");
+  ASSERT_EQ(cs.read(), "");
+}
+
+TEST(ContentStreamTest, ShortRead) {
+  auto sb = dap::StringBuffer::create();
+  sb->write("Content-Length: 26\r\n\r\nContent payload number one");
+  sb->write("some unrecognised garbage");
+  sb->write("Content-Length: 26\r\n\r\nContent payload number two");
+  sb->write("some more unrecognised garbage");
+  sb->write("Content-Length: 28\r\n\r\nContent payload number three");
+  dap::ContentReader cs(
+      std::unique_ptr<SingleByteReader>(new SingleByteReader(std::move(sb))));
+  ASSERT_EQ(cs.read(), "Content payload number one");
+  ASSERT_EQ(cs.read(), "Content payload number two");
+  ASSERT_EQ(cs.read(), "Content payload number three");
+  ASSERT_EQ(cs.read(), "");
+}
+
+TEST(ContentStreamTest, PartialReadAndParse) {
+  auto sb = std::make_shared<dap::StringBuffer>();
+  dap::ContentReader cs(sb);
+  sb->write("Content");
+  ASSERT_EQ(cs.read(), "");
+  sb->write("-Length: ");
+  ASSERT_EQ(cs.read(), "");
+  sb->write("26");
+  ASSERT_EQ(cs.read(), "");
+  sb->write("\r\n\r\n");
+  ASSERT_EQ(cs.read(), "");
+  sb->write("Content payload number one");
+  ASSERT_EQ(cs.read(), "Content payload number one");
+  ASSERT_EQ(cs.read(), "");
+}
diff --git a/Utilities/cmcppdap/src/dap_test.cpp b/Utilities/cmcppdap/src/dap_test.cpp
new file mode 100644
index 0000000..f31be46
--- /dev/null
+++ b/Utilities/cmcppdap/src/dap_test.cpp
@@ -0,0 +1,72 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dap/dap.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include <condition_variable>
+#include <mutex>
+#include <thread>
+#include <vector>
+
+int main(int argc, char** argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
+
+TEST(DAP, PairedInitializeTerminate) {
+  dap::initialize();
+  dap::terminate();
+}
+
+TEST(DAP, NestedInitializeTerminate) {
+  dap::initialize();
+  dap::initialize();
+  dap::initialize();
+  dap::terminate();
+  dap::terminate();
+  dap::terminate();
+}
+
+TEST(DAP, MultiThreadedInitializeTerminate) {
+  const size_t numThreads = 64;
+
+  std::mutex mutex;
+  std::condition_variable cv;
+  size_t numInits = 0;
+
+  std::vector<std::thread> threads;
+  threads.reserve(numThreads);
+  for (size_t i = 0; i < numThreads; i++) {
+    threads.emplace_back([&] {
+      dap::initialize();
+      {
+        std::unique_lock<std::mutex> lock(mutex);
+        numInits++;
+        if (numInits == numThreads) {
+          cv.notify_all();
+        } else {
+          cv.wait(lock, [&] { return numInits == numThreads; });
+        }
+      }
+      dap::terminate();
+    });
+  }
+
+  for (auto& thread : threads) {
+    thread.join();
+  }
+}
diff --git a/Utilities/cmcppdap/src/io.cpp b/Utilities/cmcppdap/src/io.cpp
new file mode 100644
index 0000000..b4133e5
--- /dev/null
+++ b/Utilities/cmcppdap/src/io.cpp
@@ -0,0 +1,258 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dap/io.h"
+
+#include <atomic>
+#include <condition_variable>
+#include <cstdarg>
+#include <cstdio>
+#include <cstring>  // strlen
+#include <deque>
+#include <mutex>
+#include <string>
+
+namespace {
+
+class Pipe : public dap::ReaderWriter {
+ public:
+  // dap::ReaderWriter compliance
+  bool isOpen() override {
+    std::unique_lock<std::mutex> lock(mutex);
+    return !closed;
+  }
+
+  void close() override {
+    std::unique_lock<std::mutex> lock(mutex);
+    closed = true;
+    cv.notify_all();
+  }
+
+  size_t read(void* buffer, size_t bytes) override {
+    std::unique_lock<std::mutex> lock(mutex);
+    auto out = reinterpret_cast<uint8_t*>(buffer);
+    size_t n = 0;
+    while (true) {
+      cv.wait(lock, [&] { return closed || data.size() > 0; });
+      if (closed) {
+        return n;
+      }
+      for (; n < bytes && data.size() > 0; n++) {
+        out[n] = data.front();
+        data.pop_front();
+      }
+      if (n == bytes) {
+        return n;
+      }
+    }
+  }
+
+  bool write(const void* buffer, size_t bytes) override {
+    std::unique_lock<std::mutex> lock(mutex);
+    if (closed) {
+      return false;
+    }
+    if (bytes == 0) {
+      return true;
+    }
+    auto notify = data.size() == 0;
+    auto src = reinterpret_cast<const uint8_t*>(buffer);
+    for (size_t i = 0; i < bytes; i++) {
+      data.emplace_back(src[i]);
+    }
+    if (notify) {
+      cv.notify_all();
+    }
+    return true;
+  }
+
+ private:
+  std::mutex mutex;
+  std::condition_variable cv;
+  std::deque<uint8_t> data;
+  bool closed = false;
+};
+
+class RW : public dap::ReaderWriter {
+ public:
+  RW(const std::shared_ptr<Reader>& r, const std::shared_ptr<Writer>& w)
+      : r(r), w(w) {}
+
+  // dap::ReaderWriter compliance
+  bool isOpen() override { return r->isOpen() && w->isOpen(); }
+  void close() override {
+    r->close();
+    w->close();
+  }
+  size_t read(void* buffer, size_t n) override { return r->read(buffer, n); }
+  bool write(const void* buffer, size_t n) override {
+    return w->write(buffer, n);
+  }
+
+ private:
+  const std::shared_ptr<dap::Reader> r;
+  const std::shared_ptr<dap::Writer> w;
+};
+
+class File : public dap::ReaderWriter {
+ public:
+  File(FILE* f, bool closable) : f(f), closable(closable) {}
+
+  ~File() { close(); }
+
+  // dap::ReaderWriter compliance
+  bool isOpen() override { return !closed; }
+  void close() override {
+    if (closable) {
+      if (!closed.exchange(true)) {
+        fclose(f);
+      }
+    }
+  }
+  size_t read(void* buffer, size_t n) override {
+    std::unique_lock<std::mutex> lock(readMutex);
+    auto out = reinterpret_cast<char*>(buffer);
+    for (size_t i = 0; i < n; i++) {
+      int c = fgetc(f);
+      if (c == EOF) {
+        return i;
+      }
+      out[i] = char(c);
+    }
+    return n;
+  }
+  bool write(const void* buffer, size_t n) override {
+    std::unique_lock<std::mutex> lock(writeMutex);
+    if (fwrite(buffer, 1, n, f) == n) {
+      fflush(f);
+      return true;
+    }
+    return false;
+  }
+
+ private:
+  FILE* const f;
+  const bool closable;
+  std::mutex readMutex;
+  std::mutex writeMutex;
+  std::atomic<bool> closed = {false};
+};
+
+class ReaderSpy : public dap::Reader {
+ public:
+  ReaderSpy(const std::shared_ptr<dap::Reader>& r,
+            const std::shared_ptr<dap::Writer>& s,
+            const std::string& prefix)
+      : r(r), s(s), prefix(prefix) {}
+
+  // dap::Reader compliance
+  bool isOpen() override { return r->isOpen(); }
+  void close() override { r->close(); }
+  size_t read(void* buffer, size_t n) override {
+    auto c = r->read(buffer, n);
+    if (c > 0) {
+      auto chars = reinterpret_cast<const char*>(buffer);
+      std::string buf = prefix;
+      buf.append(chars, chars + c);
+      s->write(buf.data(), buf.size());
+    }
+    return c;
+  }
+
+ private:
+  const std::shared_ptr<dap::Reader> r;
+  const std::shared_ptr<dap::Writer> s;
+  const std::string prefix;
+};
+
+class WriterSpy : public dap::Writer {
+ public:
+  WriterSpy(const std::shared_ptr<dap::Writer>& w,
+            const std::shared_ptr<dap::Writer>& s,
+            const std::string& prefix)
+      : w(w), s(s), prefix(prefix) {}
+
+  // dap::Writer compliance
+  bool isOpen() override { return w->isOpen(); }
+  void close() override { w->close(); }
+  bool write(const void* buffer, size_t n) override {
+    if (!w->write(buffer, n)) {
+      return false;
+    }
+    auto chars = reinterpret_cast<const char*>(buffer);
+    std::string buf = prefix;
+    buf.append(chars, chars + n);
+    s->write(buf.data(), buf.size());
+    return true;
+  }
+
+ private:
+  const std::shared_ptr<dap::Writer> w;
+  const std::shared_ptr<dap::Writer> s;
+  const std::string prefix;
+};
+
+}  // anonymous namespace
+
+namespace dap {
+
+std::shared_ptr<ReaderWriter> ReaderWriter::create(
+    const std::shared_ptr<Reader>& r,
+    const std::shared_ptr<Writer>& w) {
+  return std::make_shared<RW>(r, w);
+}
+
+std::shared_ptr<ReaderWriter> pipe() {
+  return std::make_shared<Pipe>();
+}
+
+std::shared_ptr<ReaderWriter> file(FILE* f, bool closable /* = true */) {
+  return std::make_shared<File>(f, closable);
+}
+
+std::shared_ptr<ReaderWriter> file(const char* path) {
+  if (auto f = fopen(path, "wb")) {
+    return std::make_shared<File>(f, true);
+  }
+  return nullptr;
+}
+
+// spy() returns a Reader that copies all reads from the Reader r to the Writer
+// s, using the given optional prefix.
+std::shared_ptr<Reader> spy(const std::shared_ptr<Reader>& r,
+                            const std::shared_ptr<Writer>& s,
+                            const char* prefix /* = "\n<-" */) {
+  return std::make_shared<ReaderSpy>(r, s, prefix);
+}
+
+// spy() returns a Writer that copies all writes to the Writer w to the Writer
+// s, using the given optional prefix.
+std::shared_ptr<Writer> spy(const std::shared_ptr<Writer>& w,
+                            const std::shared_ptr<Writer>& s,
+                            const char* prefix /* = "\n->" */) {
+  return std::make_shared<WriterSpy>(w, s, prefix);
+}
+
+bool writef(const std::shared_ptr<Writer>& w, const char* msg, ...) {
+  char buf[2048];
+
+  va_list vararg;
+  va_start(vararg, msg);
+  vsnprintf(buf, sizeof(buf), msg, vararg);
+  va_end(vararg);
+
+  return w->write(buf, strlen(buf));
+}
+
+}  // namespace dap
diff --git a/Utilities/cmcppdap/src/json_serializer.h b/Utilities/cmcppdap/src/json_serializer.h
new file mode 100644
index 0000000..32a7ce4
--- /dev/null
+++ b/Utilities/cmcppdap/src/json_serializer.h
@@ -0,0 +1,47 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef dap_json_serializer_h
+#define dap_json_serializer_h
+
+#if defined(CPPDAP_JSON_NLOHMANN)
+#include "nlohmann_json_serializer.h"
+#elif defined(CPPDAP_JSON_RAPID)
+#include "rapid_json_serializer.h"
+#elif defined(CPPDAP_JSON_JSONCPP)
+#include "jsoncpp_json_serializer.h"
+#else
+#error "Unrecognised cppdap JSON library"
+#endif
+
+namespace dap {
+namespace json {
+
+#if defined(CPPDAP_JSON_NLOHMANN)
+using Deserializer = NlohmannDeserializer;
+using Serializer = NlohmannSerializer;
+#elif defined(CPPDAP_JSON_RAPID)
+using Deserializer = RapidDeserializer;
+using Serializer = RapidSerializer;
+#elif defined(CPPDAP_JSON_JSONCPP)
+using Deserializer = JsonCppDeserializer;
+using Serializer = JsonCppSerializer;
+#else
+#error "Unrecognised cppdap JSON library"
+#endif
+
+}  // namespace json
+}  // namespace dap
+
+#endif  // dap_json_serializer_h
diff --git a/Utilities/cmcppdap/src/json_serializer_test.cpp b/Utilities/cmcppdap/src/json_serializer_test.cpp
new file mode 100644
index 0000000..3416cd9
--- /dev/null
+++ b/Utilities/cmcppdap/src/json_serializer_test.cpp
@@ -0,0 +1,266 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "json_serializer.h"
+
+#include "dap/typeinfo.h"
+#include "dap/typeof.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace dap {
+
+struct JSONInnerTestObject {
+  integer i;
+};
+
+DAP_STRUCT_TYPEINFO(JSONInnerTestObject,
+                    "json-inner-test-object",
+                    DAP_FIELD(i, "i"));
+
+struct JSONTestObject {
+  boolean b;
+  integer i;
+  number n;
+  array<integer> a;
+  object o;
+  string s;
+  optional<integer> o1;
+  optional<integer> o2;
+  JSONInnerTestObject inner;
+};
+
+DAP_STRUCT_TYPEINFO(JSONTestObject,
+                    "json-test-object",
+                    DAP_FIELD(b, "b"),
+                    DAP_FIELD(i, "i"),
+                    DAP_FIELD(n, "n"),
+                    DAP_FIELD(a, "a"),
+                    DAP_FIELD(o, "o"),
+                    DAP_FIELD(s, "s"),
+                    DAP_FIELD(o1, "o1"),
+                    DAP_FIELD(o2, "o2"),
+                    DAP_FIELD(inner, "inner"));
+
+struct JSONObjectNoFields {};
+
+DAP_STRUCT_TYPEINFO(JSONObjectNoFields, "json-object-no-fields");
+
+struct SimpleJSONTestObject {
+  boolean b;
+  integer i;
+};
+DAP_STRUCT_TYPEINFO(SimpleJSONTestObject,
+                    "simple-json-test-object",
+                    DAP_FIELD(b, "b"),
+                    DAP_FIELD(i, "i"));
+
+}  // namespace dap
+
+class JSONSerializer : public testing::Test {
+ protected:
+  static dap::object GetSimpleObject() {
+    return dap::object({{"one", dap::integer(1)},
+                        {"two", dap::number(2)},
+                        {"three", dap::string("three")},
+                        {"four", dap::boolean(true)}});
+  }
+  void TEST_SIMPLE_OBJECT(const dap::object& obj) {
+    NESTED_TEST_FAILED = true;
+    auto ref_obj = GetSimpleObject();
+    ASSERT_EQ(obj.size(), ref_obj.size());
+    ASSERT_TRUE(obj.at("one").is<dap::integer>());
+    ASSERT_TRUE(obj.at("two").is<dap::number>());
+    ASSERT_TRUE(obj.at("three").is<dap::string>());
+    ASSERT_TRUE(obj.at("four").is<dap::boolean>());
+
+    ASSERT_EQ(ref_obj.at("one").get<dap::integer>(),
+              obj.at("one").get<dap::integer>());
+    ASSERT_EQ(ref_obj.at("two").get<dap::number>(),
+              obj.at("two").get<dap::number>());
+    ASSERT_EQ(ref_obj.at("three").get<dap::string>(),
+              obj.at("three").get<dap::string>());
+    ASSERT_EQ(ref_obj.at("four").get<dap::boolean>(),
+              obj.at("four").get<dap::boolean>());
+    NESTED_TEST_FAILED = false;
+  }
+  template <typename T>
+  void TEST_SERIALIZING_DESERIALIZING(const T& encoded, T& decoded) {
+    NESTED_TEST_FAILED = true;
+    dap::json::Serializer s;
+    ASSERT_TRUE(s.serialize(encoded));
+    dap::json::Deserializer d(s.dump());
+    ASSERT_TRUE(d.deserialize(&decoded));
+    NESTED_TEST_FAILED = false;
+  }
+  bool NESTED_TEST_FAILED = false;
+#define _ASSERT_PASS(NESTED_TEST) \
+  NESTED_TEST;                    \
+  ASSERT_FALSE(NESTED_TEST_FAILED);
+};
+
+TEST_F(JSONSerializer, SerializeDeserialize) {
+  dap::JSONTestObject encoded;
+  encoded.b = true;
+  encoded.i = 32;
+  encoded.n = 123.456;
+  encoded.a = {2, 4, 6, 8, 0x100000000, -2, -4, -6, -8, -0x100000000};
+  encoded.o["one"] = dap::integer(1);
+  encoded.o["two"] = dap::number(2);
+  encoded.s = "hello world";
+  encoded.o2 = 42;
+  encoded.inner.i = 70;
+
+  dap::json::Serializer s;
+  ASSERT_TRUE(s.serialize(encoded));
+
+  dap::JSONTestObject decoded;
+  dap::json::Deserializer d(s.dump());
+  ASSERT_TRUE(d.deserialize(&decoded));
+
+  ASSERT_EQ(encoded.b, decoded.b);
+  ASSERT_EQ(encoded.i, decoded.i);
+  ASSERT_EQ(encoded.n, decoded.n);
+  ASSERT_EQ(encoded.a, decoded.a);
+  ASSERT_EQ(encoded.o["one"].get<dap::integer>(),
+            decoded.o["one"].get<dap::integer>());
+  ASSERT_EQ(encoded.o["two"].get<dap::number>(),
+            decoded.o["two"].get<dap::number>());
+  ASSERT_EQ(encoded.s, decoded.s);
+  ASSERT_EQ(encoded.o2, decoded.o2);
+  ASSERT_EQ(encoded.inner.i, decoded.inner.i);
+}
+
+TEST_F(JSONSerializer, SerializeObjectNoFields) {
+  dap::JSONObjectNoFields obj;
+  dap::json::Serializer s;
+  ASSERT_TRUE(s.serialize(obj));
+  ASSERT_EQ(s.dump(), "{}");
+}
+
+TEST_F(JSONSerializer, SerializeDeserializeObject) {
+  dap::object encoded = GetSimpleObject();
+  dap::object decoded;
+  _ASSERT_PASS(TEST_SERIALIZING_DESERIALIZING(encoded, decoded));
+  _ASSERT_PASS(TEST_SIMPLE_OBJECT(decoded));
+}
+
+TEST_F(JSONSerializer, SerializeDeserializeEmbeddedObject) {
+  dap::object encoded;
+  dap::object decoded;
+  // object nested inside object
+  dap::object encoded_embed_obj = GetSimpleObject();
+  dap::object decoded_embed_obj;
+  encoded["embed_obj"] = encoded_embed_obj;
+  _ASSERT_PASS(TEST_SERIALIZING_DESERIALIZING(encoded, decoded));
+  ASSERT_TRUE(decoded["embed_obj"].is<dap::object>());
+  decoded_embed_obj = decoded["embed_obj"].get<dap::object>();
+  _ASSERT_PASS(TEST_SIMPLE_OBJECT(decoded_embed_obj));
+}
+
+TEST_F(JSONSerializer, SerializeDeserializeEmbeddedStruct) {
+  dap::object encoded;
+  dap::object decoded;
+  // object nested inside object
+  dap::SimpleJSONTestObject encoded_embed_struct;
+  encoded_embed_struct.b = true;
+  encoded_embed_struct.i = 50;
+  encoded["embed_struct"] = encoded_embed_struct;
+
+  dap::object decoded_embed_obj;
+  _ASSERT_PASS(TEST_SERIALIZING_DESERIALIZING(encoded, decoded));
+  ASSERT_TRUE(decoded["embed_struct"].is<dap::object>());
+  decoded_embed_obj = decoded["embed_struct"].get<dap::object>();
+  ASSERT_TRUE(decoded_embed_obj.at("b").is<dap::boolean>());
+  ASSERT_TRUE(decoded_embed_obj.at("i").is<dap::integer>());
+
+  ASSERT_EQ(encoded_embed_struct.b, decoded_embed_obj["b"].get<dap::boolean>());
+  ASSERT_EQ(encoded_embed_struct.i, decoded_embed_obj["i"].get<dap::integer>());
+}
+
+TEST_F(JSONSerializer, SerializeDeserializeEmbeddedIntArray) {
+  dap::object encoded;
+  dap::object decoded;
+  // array nested inside object
+  dap::array<dap::integer> encoded_embed_arr = {1, 2, 3, 4};
+  dap::array<dap::any> decoded_embed_arr;
+
+  encoded["embed_arr"] = encoded_embed_arr;
+
+  _ASSERT_PASS(TEST_SERIALIZING_DESERIALIZING(encoded, decoded));
+  // TODO: Deserializing array should infer basic member types
+  ASSERT_TRUE(decoded["embed_arr"].is<dap::array<dap::any>>());
+  decoded_embed_arr = decoded["embed_arr"].get<dap::array<dap::any>>();
+  ASSERT_EQ(encoded_embed_arr.size(), decoded_embed_arr.size());
+  for (std::size_t i = 0; i < decoded_embed_arr.size(); i++) {
+    ASSERT_TRUE(decoded_embed_arr[i].is<dap::integer>());
+    ASSERT_EQ(encoded_embed_arr[i], decoded_embed_arr[i].get<dap::integer>());
+  }
+}
+
+TEST_F(JSONSerializer, SerializeDeserializeEmbeddedObjectArray) {
+  dap::object encoded;
+  dap::object decoded;
+
+  dap::array<dap::object> encoded_embed_arr = {GetSimpleObject(),
+                                               GetSimpleObject()};
+  dap::array<dap::any> decoded_embed_arr;
+
+  encoded["embed_arr"] = encoded_embed_arr;
+
+  _ASSERT_PASS(TEST_SERIALIZING_DESERIALIZING(encoded, decoded));
+  // TODO: Deserializing array should infer basic member types
+  ASSERT_TRUE(decoded["embed_arr"].is<dap::array<dap::any>>());
+  decoded_embed_arr = decoded["embed_arr"].get<dap::array<dap::any>>();
+  ASSERT_EQ(encoded_embed_arr.size(), decoded_embed_arr.size());
+  for (std::size_t i = 0; i < decoded_embed_arr.size(); i++) {
+    ASSERT_TRUE(decoded_embed_arr[i].is<dap::object>());
+    _ASSERT_PASS(TEST_SIMPLE_OBJECT(decoded_embed_arr[i].get<dap::object>()));
+  }
+}
+
+TEST_F(JSONSerializer, DeserializeSerializeEmptyObject) {
+  auto empty_obj = "{}";
+  dap::object decoded;
+  dap::json::Deserializer d(empty_obj);
+  ASSERT_TRUE(d.deserialize(&decoded));
+  dap::json::Serializer s;
+  ASSERT_TRUE(s.serialize(decoded));
+  ASSERT_EQ(s.dump(), empty_obj);
+}
+
+TEST_F(JSONSerializer, SerializeDeserializeEmbeddedEmptyObject) {
+  dap::object encoded_empty_obj;
+  dap::object encoded = {{"empty_obj", encoded_empty_obj}};
+  dap::object decoded;
+
+  _ASSERT_PASS(TEST_SERIALIZING_DESERIALIZING(encoded, decoded));
+  ASSERT_TRUE(decoded["empty_obj"].is<dap::object>());
+  dap::object decoded_empty_obj = decoded["empty_obj"].get<dap::object>();
+  ASSERT_EQ(encoded_empty_obj.size(), decoded_empty_obj.size());
+}
+
+TEST_F(JSONSerializer, SerializeDeserializeObjectWithNulledField) {
+  auto thing = dap::any(dap::null());
+  dap::object encoded;
+  encoded["nulled_field"] = dap::null();
+  dap::json::Serializer s;
+  ASSERT_TRUE(s.serialize(encoded));
+  dap::object decoded;
+  auto dump = s.dump();
+  dap::json::Deserializer d(dump);
+  ASSERT_TRUE(d.deserialize(&decoded));
+  ASSERT_TRUE(encoded["nulled_field"].is<dap::null>());
+}
diff --git a/Utilities/cmcppdap/src/jsoncpp_json_serializer.cpp b/Utilities/cmcppdap/src/jsoncpp_json_serializer.cpp
new file mode 100644
index 0000000..0d037a9
--- /dev/null
+++ b/Utilities/cmcppdap/src/jsoncpp_json_serializer.cpp
@@ -0,0 +1,272 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "jsoncpp_json_serializer.h"
+
+#include "null_json_serializer.h"
+
+#include <cm3p/json/json.h>
+#include <cstdlib>
+#include <memory>
+
+namespace dap {
+namespace json {
+
+JsonCppDeserializer::JsonCppDeserializer(const std::string& str)
+    : json(new Json::Value(JsonCppDeserializer::parse(str))), ownsJson(true) {}
+
+JsonCppDeserializer::JsonCppDeserializer(const Json::Value* json)
+    : json(json), ownsJson(false) {}
+
+JsonCppDeserializer::~JsonCppDeserializer() {
+  if (ownsJson) {
+    delete json;
+  }
+}
+
+bool JsonCppDeserializer::deserialize(dap::boolean* v) const {
+  if (!json->isBool()) {
+    return false;
+  }
+  *v = json->asBool();
+  return true;
+}
+
+bool JsonCppDeserializer::deserialize(dap::integer* v) const {
+  if (!json->isInt64()) {
+    return false;
+  }
+  *v = json->asInt64();
+  return true;
+}
+
+bool JsonCppDeserializer::deserialize(dap::number* v) const {
+  if (!json->isNumeric()) {
+    return false;
+  }
+  *v = json->asDouble();
+  return true;
+}
+
+bool JsonCppDeserializer::deserialize(dap::string* v) const {
+  if (!json->isString()) {
+    return false;
+  }
+  *v = json->asString();
+  return true;
+}
+
+bool JsonCppDeserializer::deserialize(dap::object* v) const {
+  v->reserve(json->size());
+  for (auto i = json->begin(); i != json->end(); i++) {
+    JsonCppDeserializer d(&*i);
+    dap::any val;
+    if (!d.deserialize(&val)) {
+      return false;
+    }
+    (*v)[i.name()] = val;
+  }
+  return true;
+}
+
+bool JsonCppDeserializer::deserialize(dap::any* v) const {
+  if (json->isBool()) {
+    *v = dap::boolean(json->asBool());
+  } else if (json->type() == Json::ValueType::realValue) {
+    // json->isDouble() returns true for integers as well, so we need to
+    // explicitly look for the realValue type.
+    *v = dap::number(json->asDouble());
+  } else if (json->isInt64()) {
+    *v = dap::integer(json->asInt64());
+  } else if (json->isString()) {
+    *v = json->asString();
+  } else if (json->isObject()) {
+    dap::object obj;
+    if (!deserialize(&obj)) {
+      return false;
+    }
+    *v = obj;
+  } else if (json->isArray()) {
+    dap::array<any> arr;
+    if (!deserialize(&arr)) {
+      return false;
+    }
+    *v = arr;
+  } else if (json->isNull()) {
+    *v = null();
+  } else {
+    return false;
+  }
+  return true;
+}
+
+size_t JsonCppDeserializer::count() const {
+  return json->size();
+}
+
+bool JsonCppDeserializer::array(
+    const std::function<bool(dap::Deserializer*)>& cb) const {
+  if (!json->isArray()) {
+    return false;
+  }
+  for (const auto& value : *json) {
+    JsonCppDeserializer d(&value);
+    if (!cb(&d)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool JsonCppDeserializer::field(
+    const std::string& name,
+    const std::function<bool(dap::Deserializer*)>& cb) const {
+  if (!json->isObject()) {
+    return false;
+  }
+  auto value = json->find(name.data(), name.data() + name.size());
+  if (value == nullptr) {
+    return cb(&NullDeserializer::instance);
+  }
+  JsonCppDeserializer d(value);
+  return cb(&d);
+}
+
+Json::Value JsonCppDeserializer::parse(const std::string& text) {
+  Json::CharReaderBuilder builder;
+  auto jsonReader = std::unique_ptr<Json::CharReader>(builder.newCharReader());
+  Json::Value json;
+  std::string error;
+  if (!jsonReader->parse(text.data(), text.data() + text.size(), &json,
+                         &error)) {
+    // cppdap expects that the JSON layer does not throw exceptions.
+    std::abort();
+  }
+  return json;
+}
+
+JsonCppSerializer::JsonCppSerializer()
+    : json(new Json::Value()), ownsJson(true) {}
+
+JsonCppSerializer::JsonCppSerializer(Json::Value* json)
+    : json(json), ownsJson(false) {}
+
+JsonCppSerializer::~JsonCppSerializer() {
+  if (ownsJson) {
+    delete json;
+  }
+}
+
+std::string JsonCppSerializer::dump() const {
+  Json::StreamWriterBuilder writer;
+  return Json::writeString(writer, *json);
+}
+
+bool JsonCppSerializer::serialize(dap::boolean v) {
+  *json = (bool)v;
+  return true;
+}
+
+bool JsonCppSerializer::serialize(dap::integer v) {
+  *json = (Json::LargestInt)v;
+  return true;
+}
+
+bool JsonCppSerializer::serialize(dap::number v) {
+  *json = (double)v;
+  return true;
+}
+
+bool JsonCppSerializer::serialize(const dap::string& v) {
+  *json = v;
+  return true;
+}
+
+bool JsonCppSerializer::serialize(const dap::object& v) {
+  if (!json->isObject()) {
+    *json = Json::Value(Json::objectValue);
+  }
+  for (auto& it : v) {
+    JsonCppSerializer s(&(*json)[it.first]);
+    if (!s.serialize(it.second)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool JsonCppSerializer::serialize(const dap::any& v) {
+  if (v.is<dap::boolean>()) {
+    *json = (bool)v.get<dap::boolean>();
+  } else if (v.is<dap::integer>()) {
+    *json = (Json::LargestInt)v.get<dap::integer>();
+  } else if (v.is<dap::number>()) {
+    *json = (double)v.get<dap::number>();
+  } else if (v.is<dap::string>()) {
+    *json = v.get<dap::string>();
+  } else if (v.is<dap::object>()) {
+    // reachable if dap::object nested is inside other dap::object
+    return serialize(v.get<dap::object>());
+  } else if (v.is<dap::null>()) {
+  } else {
+    // reachable if array or custom serialized type is nested inside other
+    auto type = get_any_type(v);
+    auto value = get_any_val(v);
+    if (type && value) {
+      return type->serialize(this, value);
+    }
+    return false;
+  }
+  return true;
+}
+
+bool JsonCppSerializer::array(size_t count,
+                              const std::function<bool(dap::Serializer*)>& cb) {
+  *json = Json::Value(Json::arrayValue);
+  for (size_t i = 0; i < count; i++) {
+    JsonCppSerializer s(&(*json)[Json::Value::ArrayIndex(i)]);
+    if (!cb(&s)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool JsonCppSerializer::object(
+    const std::function<bool(dap::FieldSerializer*)>& cb) {
+  struct FS : public FieldSerializer {
+    Json::Value* const json;
+
+    FS(Json::Value* json) : json(json) {}
+    bool field(const std::string& name, const SerializeFunc& cb) override {
+      JsonCppSerializer s(&(*json)[name]);
+      auto res = cb(&s);
+      if (s.removed) {
+        json->removeMember(name);
+      }
+      return res;
+    }
+  };
+
+  *json = Json::Value(Json::objectValue);
+  FS fs{json};
+  return cb(&fs);
+}
+
+void JsonCppSerializer::remove() {
+  removed = true;
+}
+
+}  // namespace json
+}  // namespace dap
diff --git a/Utilities/cmcppdap/src/jsoncpp_json_serializer.h b/Utilities/cmcppdap/src/jsoncpp_json_serializer.h
new file mode 100644
index 0000000..93c510b
--- /dev/null
+++ b/Utilities/cmcppdap/src/jsoncpp_json_serializer.h
@@ -0,0 +1,134 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef dap_jsoncpp_json_serializer_h
+#define dap_jsoncpp_json_serializer_h
+
+#include "dap/protocol.h"
+#include "dap/serialization.h"
+#include "dap/types.h"
+
+#include <cm3p/json/forwards.h>
+
+namespace dap {
+namespace json {
+
+struct JsonCppDeserializer : public dap::Deserializer {
+  explicit JsonCppDeserializer(const std::string&);
+  ~JsonCppDeserializer();
+
+  // dap::Deserializer compliance
+  bool deserialize(boolean* v) const override;
+  bool deserialize(integer* v) const override;
+  bool deserialize(number* v) const override;
+  bool deserialize(string* v) const override;
+  bool deserialize(object* v) const override;
+  bool deserialize(any* v) const override;
+  size_t count() const override;
+  bool array(const std::function<bool(dap::Deserializer*)>&) const override;
+  bool field(const std::string& name,
+             const std::function<bool(dap::Deserializer*)>&) const override;
+
+  // Unhide base overloads
+  template <typename T>
+  inline bool field(const std::string& name, T* v) {
+    return dap::Deserializer::field(name, v);
+  }
+
+  template <typename T,
+            typename = std::enable_if<TypeOf<T>::has_custom_serialization>>
+  inline bool deserialize(T* v) const {
+    return dap::Deserializer::deserialize(v);
+  }
+
+  template <typename T>
+  inline bool deserialize(dap::array<T>* v) const {
+    return dap::Deserializer::deserialize(v);
+  }
+
+  template <typename T>
+  inline bool deserialize(dap::optional<T>* v) const {
+    return dap::Deserializer::deserialize(v);
+  }
+
+  template <typename T0, typename... Types>
+  inline bool deserialize(dap::variant<T0, Types...>* v) const {
+    return dap::Deserializer::deserialize(v);
+  }
+
+  template <typename T>
+  inline bool field(const std::string& name, T* v) const {
+    return dap::Deserializer::deserialize(name, v);
+  }
+
+ private:
+  JsonCppDeserializer(const Json::Value*);
+  static Json::Value parse(const std::string& text);
+  const Json::Value* const json;
+  const bool ownsJson;
+};
+
+struct JsonCppSerializer : public dap::Serializer {
+  JsonCppSerializer();
+  ~JsonCppSerializer();
+
+  std::string dump() const;
+
+  // dap::Serializer compliance
+  bool serialize(boolean v) override;
+  bool serialize(integer v) override;
+  bool serialize(number v) override;
+  bool serialize(const string& v) override;
+  bool serialize(const dap::object& v) override;
+  bool serialize(const any& v) override;
+  bool array(size_t count,
+             const std::function<bool(dap::Serializer*)>&) override;
+  bool object(const std::function<bool(dap::FieldSerializer*)>&) override;
+  void remove() override;
+
+  // Unhide base overloads
+  template <typename T,
+            typename = std::enable_if<TypeOf<T>::has_custom_serialization>>
+  inline bool serialize(const T& v) {
+    return dap::Serializer::serialize(v);
+  }
+
+  template <typename T>
+  inline bool serialize(const dap::array<T>& v) {
+    return dap::Serializer::serialize(v);
+  }
+
+  template <typename T>
+  inline bool serialize(const dap::optional<T>& v) {
+    return dap::Serializer::serialize(v);
+  }
+
+  template <typename T0, typename... Types>
+  inline bool serialize(const dap::variant<T0, Types...>& v) {
+    return dap::Serializer::serialize(v);
+  }
+
+  inline bool serialize(const char* v) { return dap::Serializer::serialize(v); }
+
+ private:
+  JsonCppSerializer(Json::Value*);
+  Json::Value* const json;
+  const bool ownsJson;
+  bool removed = false;
+};
+
+}  // namespace json
+}  // namespace dap
+
+#endif  // dap_jsoncpp_json_serializer_h
diff --git a/Utilities/cmcppdap/src/network.cpp b/Utilities/cmcppdap/src/network.cpp
new file mode 100644
index 0000000..613c234
--- /dev/null
+++ b/Utilities/cmcppdap/src/network.cpp
@@ -0,0 +1,100 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dap/network.h"
+
+#include "socket.h"
+
+#include <atomic>
+#include <mutex>
+#include <string>
+#include <thread>
+
+namespace {
+
+class Impl : public dap::net::Server {
+ public:
+  Impl() : stopped{true} {}
+
+  ~Impl() { stop(); }
+
+  bool start(int port,
+             const OnConnect& onConnect,
+             const OnError& onError) override {
+    std::unique_lock<std::mutex> lock(mutex);
+    stopWithLock();
+    socket = std::unique_ptr<dap::Socket>(
+        new dap::Socket("localhost", std::to_string(port).c_str()));
+
+    if (!socket->isOpen()) {
+      onError("Failed to open socket");
+      return false;
+    }
+
+    stopped = false;
+    thread = std::thread([=] {
+      while (true) {
+        if (auto rw = socket->accept()) {
+          onConnect(rw);
+          continue;
+        }
+        if (!stopped) {
+          onError("Failed to accept connection");
+        }
+        break;
+      };
+    });
+
+    return true;
+  }
+
+  void stop() override {
+    std::unique_lock<std::mutex> lock(mutex);
+    stopWithLock();
+  }
+
+ private:
+  bool isRunning() { return !stopped; }
+
+  void stopWithLock() {
+    if (!stopped.exchange(true)) {
+      socket->close();
+      thread.join();
+    }
+  }
+
+  std::mutex mutex;
+  std::thread thread;
+  std::unique_ptr<dap::Socket> socket;
+  std::atomic<bool> stopped;
+  OnError errorHandler;
+};
+
+}  // anonymous namespace
+
+namespace dap {
+namespace net {
+
+std::unique_ptr<Server> Server::create() {
+  return std::unique_ptr<Server>(new Impl());
+}
+
+std::shared_ptr<ReaderWriter> connect(const char* addr,
+                                      int port,
+                                      uint32_t timeoutMillis) {
+  return Socket::connect(addr, std::to_string(port).c_str(), timeoutMillis);
+}
+
+}  // namespace net
+}  // namespace dap
diff --git a/Utilities/cmcppdap/src/network_test.cpp b/Utilities/cmcppdap/src/network_test.cpp
new file mode 100644
index 0000000..57bb0a9
--- /dev/null
+++ b/Utilities/cmcppdap/src/network_test.cpp
@@ -0,0 +1,110 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dap/network.h"
+#include "dap/io.h"
+
+#include "chan.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include <chrono>
+#include <thread>
+
+namespace {
+
+constexpr int port = 19021;
+
+bool write(const std::shared_ptr<dap::Writer>& w, const std::string& s) {
+  return w->write(s.data(), s.size()) && w->write("\0", 1);
+}
+
+std::string read(const std::shared_ptr<dap::Reader>& r) {
+  char c;
+  std::string s;
+  while (r->read(&c, sizeof(c)) > 0) {
+    if (c == '\0') {
+      return s;
+    }
+    s += c;
+  }
+  return r->isOpen() ? "<read failed>" : "<stream closed>";
+}
+
+}  // anonymous namespace
+
+TEST(Network, ClientServer) {
+  dap::Chan<bool> done;
+  auto server = dap::net::Server::create();
+  if (!server->start(
+          port,
+          [&](const std::shared_ptr<dap::ReaderWriter>& rw) {
+            ASSERT_EQ(read(rw), "client to server");
+            ASSERT_TRUE(write(rw, "server to client"));
+            done.put(true);
+          },
+          [&](const char* err) { FAIL() << "Server error: " << err; })) {
+    FAIL() << "Couldn't start server";
+    return;
+  }
+
+  for (int i = 0; i < 5; i++) {
+    auto client = dap::net::connect("localhost", port);
+    ASSERT_NE(client, nullptr) << "Failed to connect client " << i;
+    ASSERT_TRUE(write(client, "client to server"));
+    ASSERT_EQ(read(client), "server to client");
+    done.take();
+    std::this_thread::sleep_for(std::chrono::seconds(1));
+  }
+
+  server.reset();
+}
+
+TEST(Network, ServerRepeatStopAndRestart) {
+  dap::Chan<bool> done;
+  auto onConnect = [&](const std::shared_ptr<dap::ReaderWriter>& rw) {
+    ASSERT_EQ(read(rw), "client to server");
+    ASSERT_TRUE(write(rw, "server to client"));
+    done.put(true);
+  };
+  auto onError = [&](const char* err) { FAIL() << "Server error: " << err; };
+
+  auto server = dap::net::Server::create();
+  if (!server->start(port, onConnect, onError)) {
+    FAIL() << "Couldn't start server";
+    return;
+  }
+
+  server->stop();
+  server->stop();
+  server->stop();
+
+  if (!server->start(port, onConnect, onError)) {
+    FAIL() << "Couldn't restart server";
+    return;
+  }
+
+  auto client = dap::net::connect("localhost", port);
+  ASSERT_NE(client, nullptr) << "Failed to connect";
+  ASSERT_TRUE(write(client, "client to server"));
+  ASSERT_EQ(read(client), "server to client");
+  done.take();
+
+  server->stop();
+  server->stop();
+  server->stop();
+
+  server.reset();
+}
diff --git a/Utilities/cmcppdap/src/nlohmann_json_serializer.cpp b/Utilities/cmcppdap/src/nlohmann_json_serializer.cpp
new file mode 100644
index 0000000..7834230
--- /dev/null
+++ b/Utilities/cmcppdap/src/nlohmann_json_serializer.cpp
@@ -0,0 +1,260 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "nlohmann_json_serializer.h"
+
+#include "null_json_serializer.h"
+
+// Disable JSON exceptions. We should be guarding against any exceptions being
+// fired in this file.
+#define JSON_NOEXCEPTION 1
+#include <nlohmann/json.hpp>
+
+namespace dap {
+namespace json {
+
+NlohmannDeserializer::NlohmannDeserializer(const std::string& str)
+    : json(new nlohmann::json(nlohmann::json::parse(str, nullptr, false))),
+      ownsJson(true) {}
+
+NlohmannDeserializer::NlohmannDeserializer(const nlohmann::json* json)
+    : json(json), ownsJson(false) {}
+
+NlohmannDeserializer::~NlohmannDeserializer() {
+  if (ownsJson) {
+    delete json;
+  }
+}
+
+bool NlohmannDeserializer::deserialize(dap::boolean* v) const {
+  if (!json->is_boolean()) {
+    return false;
+  }
+  *v = json->get<bool>();
+  return true;
+}
+
+bool NlohmannDeserializer::deserialize(dap::integer* v) const {
+  if (!json->is_number_integer()) {
+    return false;
+  }
+  *v = json->get<int64_t>();
+  return true;
+}
+
+bool NlohmannDeserializer::deserialize(dap::number* v) const {
+  if (!json->is_number()) {
+    return false;
+  }
+  *v = json->get<double>();
+  return true;
+}
+
+bool NlohmannDeserializer::deserialize(dap::string* v) const {
+  if (!json->is_string()) {
+    return false;
+  }
+  *v = json->get<std::string>();
+  return true;
+}
+
+bool NlohmannDeserializer::deserialize(dap::object* v) const {
+  v->reserve(json->size());
+  for (auto& el : json->items()) {
+    NlohmannDeserializer d(&el.value());
+    dap::any val;
+    if (!d.deserialize(&val)) {
+      return false;
+    }
+    (*v)[el.key()] = val;
+  }
+  return true;
+}
+
+bool NlohmannDeserializer::deserialize(dap::any* v) const {
+  if (json->is_boolean()) {
+    *v = dap::boolean(json->get<bool>());
+  } else if (json->is_number_float()) {
+    *v = dap::number(json->get<double>());
+  } else if (json->is_number_integer()) {
+    *v = dap::integer(json->get<int64_t>());
+  } else if (json->is_string()) {
+    *v = json->get<std::string>();
+  } else if (json->is_object()) {
+    dap::object obj;
+    if (!deserialize(&obj)) {
+      return false;
+    }
+    *v = obj;
+  } else if (json->is_array()) {
+    dap::array<any> arr;
+    if (!deserialize(&arr)) {
+      return false;
+    }
+    *v = arr;
+  } else if (json->is_null()) {
+    *v = null();
+  } else {
+    return false;
+  }
+  return true;
+}
+
+size_t NlohmannDeserializer::count() const {
+  return json->size();
+}
+
+bool NlohmannDeserializer::array(
+    const std::function<bool(dap::Deserializer*)>& cb) const {
+  if (!json->is_array()) {
+    return false;
+  }
+  for (size_t i = 0; i < json->size(); i++) {
+    NlohmannDeserializer d(&(*json)[i]);
+    if (!cb(&d)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool NlohmannDeserializer::field(
+    const std::string& name,
+    const std::function<bool(dap::Deserializer*)>& cb) const {
+  if (!json->is_structured()) {
+    return false;
+  }
+  auto it = json->find(name);
+  if (it == json->end()) {
+    return cb(&NullDeserializer::instance);
+  }
+  auto obj = *it;
+  NlohmannDeserializer d(&obj);
+  return cb(&d);
+}
+
+NlohmannSerializer::NlohmannSerializer()
+    : json(new nlohmann::json()), ownsJson(true) {}
+
+NlohmannSerializer::NlohmannSerializer(nlohmann::json* json)
+    : json(json), ownsJson(false) {}
+
+NlohmannSerializer::~NlohmannSerializer() {
+  if (ownsJson) {
+    delete json;
+  }
+}
+
+std::string NlohmannSerializer::dump() const {
+  return json->dump();
+}
+
+bool NlohmannSerializer::serialize(dap::boolean v) {
+  *json = (bool)v;
+  return true;
+}
+
+bool NlohmannSerializer::serialize(dap::integer v) {
+  *json = (int64_t)v;
+  return true;
+}
+
+bool NlohmannSerializer::serialize(dap::number v) {
+  *json = (double)v;
+  return true;
+}
+
+bool NlohmannSerializer::serialize(const dap::string& v) {
+  *json = v;
+  return true;
+}
+
+bool NlohmannSerializer::serialize(const dap::object& v) {
+  if (!json->is_object()) {
+    *json = nlohmann::json::object();
+  }
+  for (auto& it : v) {
+    NlohmannSerializer s(&(*json)[it.first]);
+    if (!s.serialize(it.second)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool NlohmannSerializer::serialize(const dap::any& v) {
+  if (v.is<dap::boolean>()) {
+    *json = (bool)v.get<dap::boolean>();
+  } else if (v.is<dap::integer>()) {
+    *json = (int64_t)v.get<dap::integer>();
+  } else if (v.is<dap::number>()) {
+    *json = (double)v.get<dap::number>();
+  } else if (v.is<dap::string>()) {
+    *json = v.get<dap::string>();
+  } else if (v.is<dap::object>()) {
+    // reachable if dap::object nested is inside other dap::object
+    return serialize(v.get<dap::object>());
+  } else if (v.is<dap::null>()) {
+  } else {
+    // reachable if array or custom serialized type is nested inside other
+    auto type = get_any_type(v);
+    auto value = get_any_val(v);
+    if (type && value) {
+      return type->serialize(this, value);
+    }
+    return false;
+  }
+  return true;
+}
+
+bool NlohmannSerializer::array(
+    size_t count,
+    const std::function<bool(dap::Serializer*)>& cb) {
+  *json = std::vector<int>();
+  for (size_t i = 0; i < count; i++) {
+    NlohmannSerializer s(&(*json)[i]);
+    if (!cb(&s)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool NlohmannSerializer::object(
+    const std::function<bool(dap::FieldSerializer*)>& cb) {
+  struct FS : public FieldSerializer {
+    nlohmann::json* const json;
+
+    FS(nlohmann::json* json) : json(json) {}
+    bool field(const std::string& name, const SerializeFunc& cb) override {
+      NlohmannSerializer s(&(*json)[name]);
+      auto res = cb(&s);
+      if (s.removed) {
+        json->erase(name);
+      }
+      return res;
+    }
+  };
+
+  *json = nlohmann::json({}, false, nlohmann::json::value_t::object);
+  FS fs{json};
+  return cb(&fs);
+}
+
+void NlohmannSerializer::remove() {
+  removed = true;
+}
+
+}  // namespace json
+}  // namespace dap
diff --git a/Utilities/cmcppdap/src/nlohmann_json_serializer.h b/Utilities/cmcppdap/src/nlohmann_json_serializer.h
new file mode 100644
index 0000000..38e47c9
--- /dev/null
+++ b/Utilities/cmcppdap/src/nlohmann_json_serializer.h
@@ -0,0 +1,133 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef dap_nlohmann_json_serializer_h
+#define dap_nlohmann_json_serializer_h
+
+#include "dap/protocol.h"
+#include "dap/serialization.h"
+#include "dap/types.h"
+
+#include <nlohmann/json_fwd.hpp>
+
+namespace dap {
+namespace json {
+
+struct NlohmannDeserializer : public dap::Deserializer {
+  explicit NlohmannDeserializer(const std::string&);
+  ~NlohmannDeserializer();
+
+  // dap::Deserializer compliance
+  bool deserialize(boolean* v) const override;
+  bool deserialize(integer* v) const override;
+  bool deserialize(number* v) const override;
+  bool deserialize(string* v) const override;
+  bool deserialize(object* v) const override;
+  bool deserialize(any* v) const override;
+  size_t count() const override;
+  bool array(const std::function<bool(dap::Deserializer*)>&) const override;
+  bool field(const std::string& name,
+             const std::function<bool(dap::Deserializer*)>&) const override;
+
+  // Unhide base overloads
+  template <typename T>
+  inline bool field(const std::string& name, T* v) {
+    return dap::Deserializer::field(name, v);
+  }
+
+  template <typename T,
+            typename = std::enable_if<TypeOf<T>::has_custom_serialization>>
+  inline bool deserialize(T* v) const {
+    return dap::Deserializer::deserialize(v);
+  }
+
+  template <typename T>
+  inline bool deserialize(dap::array<T>* v) const {
+    return dap::Deserializer::deserialize(v);
+  }
+
+  template <typename T>
+  inline bool deserialize(dap::optional<T>* v) const {
+    return dap::Deserializer::deserialize(v);
+  }
+
+  template <typename T0, typename... Types>
+  inline bool deserialize(dap::variant<T0, Types...>* v) const {
+    return dap::Deserializer::deserialize(v);
+  }
+
+  template <typename T>
+  inline bool field(const std::string& name, T* v) const {
+    return dap::Deserializer::deserialize(name, v);
+  }
+
+ private:
+  NlohmannDeserializer(const nlohmann::json*);
+  const nlohmann::json* const json;
+  const bool ownsJson;
+};
+
+struct NlohmannSerializer : public dap::Serializer {
+  NlohmannSerializer();
+  ~NlohmannSerializer();
+
+  std::string dump() const;
+
+  // dap::Serializer compliance
+  bool serialize(boolean v) override;
+  bool serialize(integer v) override;
+  bool serialize(number v) override;
+  bool serialize(const string& v) override;
+  bool serialize(const dap::object& v) override;
+  bool serialize(const any& v) override;
+  bool array(size_t count,
+             const std::function<bool(dap::Serializer*)>&) override;
+  bool object(const std::function<bool(dap::FieldSerializer*)>&) override;
+  void remove() override;
+
+  // Unhide base overloads
+  template <typename T,
+            typename = std::enable_if<TypeOf<T>::has_custom_serialization>>
+  inline bool serialize(const T& v) {
+    return dap::Serializer::serialize(v);
+  }
+
+  template <typename T>
+  inline bool serialize(const dap::array<T>& v) {
+    return dap::Serializer::serialize(v);
+  }
+
+  template <typename T>
+  inline bool serialize(const dap::optional<T>& v) {
+    return dap::Serializer::serialize(v);
+  }
+
+  template <typename T0, typename... Types>
+  inline bool serialize(const dap::variant<T0, Types...>& v) {
+    return dap::Serializer::serialize(v);
+  }
+
+  inline bool serialize(const char* v) { return dap::Serializer::serialize(v); }
+
+ private:
+  NlohmannSerializer(nlohmann::json*);
+  nlohmann::json* const json;
+  const bool ownsJson;
+  bool removed = false;
+};
+
+}  // namespace json
+}  // namespace dap
+
+#endif  // dap_nlohmann_json_serializer_h
diff --git a/Utilities/cmcppdap/src/null_json_serializer.cpp b/Utilities/cmcppdap/src/null_json_serializer.cpp
new file mode 100644
index 0000000..5aa5a03
--- /dev/null
+++ b/Utilities/cmcppdap/src/null_json_serializer.cpp
@@ -0,0 +1,23 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "null_json_serializer.h"
+
+namespace dap {
+namespace json {
+
+NullDeserializer NullDeserializer::instance;
+
+}  // namespace json
+}  // namespace dap
diff --git a/Utilities/cmcppdap/src/null_json_serializer.h b/Utilities/cmcppdap/src/null_json_serializer.h
new file mode 100644
index 0000000..c92b99a
--- /dev/null
+++ b/Utilities/cmcppdap/src/null_json_serializer.h
@@ -0,0 +1,47 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef dap_null_json_serializer_h
+#define dap_null_json_serializer_h
+
+#include "dap/protocol.h"
+#include "dap/serialization.h"
+#include "dap/types.h"
+
+namespace dap {
+namespace json {
+
+struct NullDeserializer : public dap::Deserializer {
+  static NullDeserializer instance;
+
+  bool deserialize(dap::boolean*) const override { return false; }
+  bool deserialize(dap::integer*) const override { return false; }
+  bool deserialize(dap::number*) const override { return false; }
+  bool deserialize(dap::string*) const override { return false; }
+  bool deserialize(dap::object*) const override { return false; }
+  bool deserialize(dap::any*) const override { return false; }
+  size_t count() const override { return 0; }
+  bool array(const std::function<bool(dap::Deserializer*)>&) const override {
+    return false;
+  }
+  bool field(const std::string&,
+             const std::function<bool(dap::Deserializer*)>&) const override {
+    return false;
+  }
+};
+
+}  // namespace json
+}  // namespace dap
+
+#endif  // dap_null_json_serializer_h
diff --git a/Utilities/cmcppdap/src/optional_test.cpp b/Utilities/cmcppdap/src/optional_test.cpp
new file mode 100644
index 0000000..b2590fc
--- /dev/null
+++ b/Utilities/cmcppdap/src/optional_test.cpp
@@ -0,0 +1,169 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dap/optional.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include <string>
+
+TEST(Optional, EmptyConstruct) {
+  dap::optional<std::string> opt;
+  ASSERT_FALSE(opt);
+  ASSERT_FALSE(opt.has_value());
+}
+
+TEST(Optional, ValueConstruct) {
+  dap::optional<int> opt(10);
+  ASSERT_TRUE(opt);
+  ASSERT_TRUE(opt.has_value());
+  ASSERT_EQ(opt.value(), 10);
+}
+
+TEST(Optional, CopyConstruct) {
+  dap::optional<std::string> a("meow");
+  dap::optional<std::string> b(a);
+  ASSERT_EQ(a, b);
+  ASSERT_EQ(a.value(), "meow");
+  ASSERT_EQ(b.value(), "meow");
+}
+
+TEST(Optional, CopyCastConstruct) {
+  dap::optional<int> a(10);
+  dap::optional<uint16_t> b(a);
+  ASSERT_EQ(a, b);
+  ASSERT_EQ(b.value(), (uint16_t)10);
+}
+
+TEST(Optional, MoveConstruct) {
+  dap::optional<std::string> a("meow");
+  dap::optional<std::string> b(std::move(a));
+  ASSERT_EQ(b.value(), "meow");
+}
+
+TEST(Optional, MoveCastConstruct) {
+  dap::optional<int> a(10);
+  dap::optional<uint16_t> b(std::move(a));
+  ASSERT_EQ(b.value(), (uint16_t)10);
+}
+
+TEST(Optional, AssignValue) {
+  dap::optional<std::string> a;
+  std::string b = "meow";
+  a = b;
+  ASSERT_EQ(a.value(), "meow");
+  ASSERT_EQ(b, "meow");
+}
+
+TEST(Optional, AssignOptional) {
+  dap::optional<std::string> a;
+  dap::optional<std::string> b("meow");
+  a = b;
+  ASSERT_EQ(a.value(), "meow");
+  ASSERT_EQ(b.value(), "meow");
+}
+
+TEST(Optional, MoveAssignOptional) {
+  dap::optional<std::string> a;
+  dap::optional<std::string> b("meow");
+  a = std::move(b);
+  ASSERT_EQ(a.value(), "meow");
+}
+
+TEST(Optional, StarDeref) {
+  dap::optional<std::string> a("meow");
+  ASSERT_EQ(*a, "meow");
+}
+
+TEST(Optional, StarDerefConst) {
+  const dap::optional<std::string> a("meow");
+  ASSERT_EQ(*a, "meow");
+}
+
+TEST(Optional, ArrowDeref) {
+  struct S {
+    int i;
+  };
+  dap::optional<S> a(S{10});
+  ASSERT_EQ(a->i, 10);
+}
+
+TEST(Optional, ArrowDerefConst) {
+  struct S {
+    int i;
+  };
+  const dap::optional<S> a(S{10});
+  ASSERT_EQ(a->i, 10);
+}
+
+TEST(Optional, Value) {
+  const dap::optional<std::string> a("meow");
+  ASSERT_EQ(a.value(), "meow");
+}
+
+TEST(Optional, ValueDefault) {
+  const dap::optional<std::string> a;
+  const dap::optional<std::string> b("woof");
+  ASSERT_EQ(a.value("meow"), "meow");
+  ASSERT_EQ(b.value("meow"), "woof");
+}
+
+TEST(Optional, CompareLT) {
+  ASSERT_FALSE(dap::optional<int>(5) < dap::optional<int>(3));
+  ASSERT_FALSE(dap::optional<int>(5) < dap::optional<int>(5));
+  ASSERT_TRUE(dap::optional<int>(5) < dap::optional<int>(10));
+  ASSERT_TRUE(dap::optional<int>() < dap::optional<int>(10));
+  ASSERT_FALSE(dap::optional<int>() < dap::optional<int>());
+}
+
+TEST(Optional, CompareLE) {
+  ASSERT_FALSE(dap::optional<int>(5) <= dap::optional<int>(3));
+  ASSERT_TRUE(dap::optional<int>(5) <= dap::optional<int>(5));
+  ASSERT_TRUE(dap::optional<int>(5) <= dap::optional<int>(10));
+  ASSERT_TRUE(dap::optional<int>() <= dap::optional<int>(10));
+  ASSERT_TRUE(dap::optional<int>() <= dap::optional<int>());
+}
+
+TEST(Optional, CompareGT) {
+  ASSERT_TRUE(dap::optional<int>(5) > dap::optional<int>(3));
+  ASSERT_FALSE(dap::optional<int>(5) > dap::optional<int>(5));
+  ASSERT_FALSE(dap::optional<int>(5) > dap::optional<int>(10));
+  ASSERT_FALSE(dap::optional<int>() > dap::optional<int>(10));
+  ASSERT_FALSE(dap::optional<int>() > dap::optional<int>());
+}
+
+TEST(Optional, CompareGE) {
+  ASSERT_TRUE(dap::optional<int>(5) >= dap::optional<int>(3));
+  ASSERT_TRUE(dap::optional<int>(5) >= dap::optional<int>(5));
+  ASSERT_FALSE(dap::optional<int>(5) >= dap::optional<int>(10));
+  ASSERT_FALSE(dap::optional<int>() >= dap::optional<int>(10));
+  ASSERT_TRUE(dap::optional<int>() >= dap::optional<int>());
+}
+
+TEST(Optional, CompareEQ) {
+  ASSERT_FALSE(dap::optional<int>(5) == dap::optional<int>(3));
+  ASSERT_TRUE(dap::optional<int>(5) == dap::optional<int>(5));
+  ASSERT_FALSE(dap::optional<int>(5) == dap::optional<int>(10));
+  ASSERT_FALSE(dap::optional<int>() == dap::optional<int>(10));
+  ASSERT_TRUE(dap::optional<int>() == dap::optional<int>());
+}
+
+TEST(Optional, CompareNEQ) {
+  ASSERT_TRUE(dap::optional<int>(5) != dap::optional<int>(3));
+  ASSERT_FALSE(dap::optional<int>(5) != dap::optional<int>(5));
+  ASSERT_TRUE(dap::optional<int>(5) != dap::optional<int>(10));
+  ASSERT_TRUE(dap::optional<int>() != dap::optional<int>(10));
+  ASSERT_FALSE(dap::optional<int>() != dap::optional<int>());
+}
diff --git a/Utilities/cmcppdap/src/protocol_events.cpp b/Utilities/cmcppdap/src/protocol_events.cpp
new file mode 100644
index 0000000..9deb85f
--- /dev/null
+++ b/Utilities/cmcppdap/src/protocol_events.cpp
@@ -0,0 +1,126 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Generated with protocol_gen.go -- do not edit this file.
+//   go run scripts/protocol_gen/protocol_gen.go
+//
+// DAP version 1.59.0
+
+#include "dap/protocol.h"
+
+namespace dap {
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(BreakpointEvent,
+                              "breakpoint",
+                              DAP_FIELD(breakpoint, "breakpoint"),
+                              DAP_FIELD(reason, "reason"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(CapabilitiesEvent,
+                              "capabilities",
+                              DAP_FIELD(capabilities, "capabilities"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ContinuedEvent,
+                              "continued",
+                              DAP_FIELD(allThreadsContinued,
+                                        "allThreadsContinued"),
+                              DAP_FIELD(threadId, "threadId"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ExitedEvent,
+                              "exited",
+                              DAP_FIELD(exitCode, "exitCode"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(InitializedEvent, "initialized");
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(InvalidatedEvent,
+                              "invalidated",
+                              DAP_FIELD(areas, "areas"),
+                              DAP_FIELD(stackFrameId, "stackFrameId"),
+                              DAP_FIELD(threadId, "threadId"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(LoadedSourceEvent,
+                              "loadedSource",
+                              DAP_FIELD(reason, "reason"),
+                              DAP_FIELD(source, "source"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(MemoryEvent,
+                              "memory",
+                              DAP_FIELD(count, "count"),
+                              DAP_FIELD(memoryReference, "memoryReference"),
+                              DAP_FIELD(offset, "offset"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ModuleEvent,
+                              "module",
+                              DAP_FIELD(module, "module"),
+                              DAP_FIELD(reason, "reason"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(OutputEvent,
+                              "output",
+                              DAP_FIELD(category, "category"),
+                              DAP_FIELD(column, "column"),
+                              DAP_FIELD(data, "data"),
+                              DAP_FIELD(group, "group"),
+                              DAP_FIELD(line, "line"),
+                              DAP_FIELD(output, "output"),
+                              DAP_FIELD(source, "source"),
+                              DAP_FIELD(variablesReference,
+                                        "variablesReference"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ProcessEvent,
+                              "process",
+                              DAP_FIELD(isLocalProcess, "isLocalProcess"),
+                              DAP_FIELD(name, "name"),
+                              DAP_FIELD(pointerSize, "pointerSize"),
+                              DAP_FIELD(startMethod, "startMethod"),
+                              DAP_FIELD(systemProcessId, "systemProcessId"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ProgressEndEvent,
+                              "progressEnd",
+                              DAP_FIELD(message, "message"),
+                              DAP_FIELD(progressId, "progressId"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ProgressStartEvent,
+                              "progressStart",
+                              DAP_FIELD(cancellable, "cancellable"),
+                              DAP_FIELD(message, "message"),
+                              DAP_FIELD(percentage, "percentage"),
+                              DAP_FIELD(progressId, "progressId"),
+                              DAP_FIELD(requestId, "requestId"),
+                              DAP_FIELD(title, "title"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ProgressUpdateEvent,
+                              "progressUpdate",
+                              DAP_FIELD(message, "message"),
+                              DAP_FIELD(percentage, "percentage"),
+                              DAP_FIELD(progressId, "progressId"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(StoppedEvent,
+                              "stopped",
+                              DAP_FIELD(allThreadsStopped, "allThreadsStopped"),
+                              DAP_FIELD(description, "description"),
+                              DAP_FIELD(hitBreakpointIds, "hitBreakpointIds"),
+                              DAP_FIELD(preserveFocusHint, "preserveFocusHint"),
+                              DAP_FIELD(reason, "reason"),
+                              DAP_FIELD(text, "text"),
+                              DAP_FIELD(threadId, "threadId"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(TerminatedEvent,
+                              "terminated",
+                              DAP_FIELD(restart, "restart"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ThreadEvent,
+                              "thread",
+                              DAP_FIELD(reason, "reason"),
+                              DAP_FIELD(threadId, "threadId"));
+
+}  // namespace dap
diff --git a/Utilities/cmcppdap/src/protocol_requests.cpp b/Utilities/cmcppdap/src/protocol_requests.cpp
new file mode 100644
index 0000000..a3b33ec
--- /dev/null
+++ b/Utilities/cmcppdap/src/protocol_requests.cpp
@@ -0,0 +1,281 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Generated with protocol_gen.go -- do not edit this file.
+//   go run scripts/protocol_gen/protocol_gen.go
+//
+// DAP version 1.59.0
+
+#include "dap/protocol.h"
+
+namespace dap {
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(AttachRequest,
+                              "attach",
+                              DAP_FIELD(restart, "__restart"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(BreakpointLocationsRequest,
+                              "breakpointLocations",
+                              DAP_FIELD(column, "column"),
+                              DAP_FIELD(endColumn, "endColumn"),
+                              DAP_FIELD(endLine, "endLine"),
+                              DAP_FIELD(line, "line"),
+                              DAP_FIELD(source, "source"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(CancelRequest,
+                              "cancel",
+                              DAP_FIELD(progressId, "progressId"),
+                              DAP_FIELD(requestId, "requestId"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(CompletionsRequest,
+                              "completions",
+                              DAP_FIELD(column, "column"),
+                              DAP_FIELD(frameId, "frameId"),
+                              DAP_FIELD(line, "line"),
+                              DAP_FIELD(text, "text"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ConfigurationDoneRequest, "configurationDone");
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ContinueRequest,
+                              "continue",
+                              DAP_FIELD(singleThread, "singleThread"),
+                              DAP_FIELD(threadId, "threadId"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(DataBreakpointInfoRequest,
+                              "dataBreakpointInfo",
+                              DAP_FIELD(frameId, "frameId"),
+                              DAP_FIELD(name, "name"),
+                              DAP_FIELD(variablesReference,
+                                        "variablesReference"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(DisassembleRequest,
+                              "disassemble",
+                              DAP_FIELD(instructionCount, "instructionCount"),
+                              DAP_FIELD(instructionOffset, "instructionOffset"),
+                              DAP_FIELD(memoryReference, "memoryReference"),
+                              DAP_FIELD(offset, "offset"),
+                              DAP_FIELD(resolveSymbols, "resolveSymbols"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(DisconnectRequest,
+                              "disconnect",
+                              DAP_FIELD(restart, "restart"),
+                              DAP_FIELD(suspendDebuggee, "suspendDebuggee"),
+                              DAP_FIELD(terminateDebuggee,
+                                        "terminateDebuggee"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(EvaluateRequest,
+                              "evaluate",
+                              DAP_FIELD(context, "context"),
+                              DAP_FIELD(expression, "expression"),
+                              DAP_FIELD(format, "format"),
+                              DAP_FIELD(frameId, "frameId"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ExceptionInfoRequest,
+                              "exceptionInfo",
+                              DAP_FIELD(threadId, "threadId"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(GotoRequest,
+                              "goto",
+                              DAP_FIELD(targetId, "targetId"),
+                              DAP_FIELD(threadId, "threadId"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(GotoTargetsRequest,
+                              "gotoTargets",
+                              DAP_FIELD(column, "column"),
+                              DAP_FIELD(line, "line"),
+                              DAP_FIELD(source, "source"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(
+    InitializeRequest,
+    "initialize",
+    DAP_FIELD(adapterID, "adapterID"),
+    DAP_FIELD(clientID, "clientID"),
+    DAP_FIELD(clientName, "clientName"),
+    DAP_FIELD(columnsStartAt1, "columnsStartAt1"),
+    DAP_FIELD(linesStartAt1, "linesStartAt1"),
+    DAP_FIELD(locale, "locale"),
+    DAP_FIELD(pathFormat, "pathFormat"),
+    DAP_FIELD(supportsArgsCanBeInterpretedByShell,
+              "supportsArgsCanBeInterpretedByShell"),
+    DAP_FIELD(supportsInvalidatedEvent, "supportsInvalidatedEvent"),
+    DAP_FIELD(supportsMemoryEvent, "supportsMemoryEvent"),
+    DAP_FIELD(supportsMemoryReferences, "supportsMemoryReferences"),
+    DAP_FIELD(supportsProgressReporting, "supportsProgressReporting"),
+    DAP_FIELD(supportsRunInTerminalRequest, "supportsRunInTerminalRequest"),
+    DAP_FIELD(supportsStartDebuggingRequest, "supportsStartDebuggingRequest"),
+    DAP_FIELD(supportsVariablePaging, "supportsVariablePaging"),
+    DAP_FIELD(supportsVariableType, "supportsVariableType"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(LaunchRequest,
+                              "launch",
+                              DAP_FIELD(restart, "__restart"),
+                              DAP_FIELD(noDebug, "noDebug"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(LoadedSourcesRequest, "loadedSources");
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ModulesRequest,
+                              "modules",
+                              DAP_FIELD(moduleCount, "moduleCount"),
+                              DAP_FIELD(startModule, "startModule"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(NextRequest,
+                              "next",
+                              DAP_FIELD(granularity, "granularity"),
+                              DAP_FIELD(singleThread, "singleThread"),
+                              DAP_FIELD(threadId, "threadId"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(PauseRequest,
+                              "pause",
+                              DAP_FIELD(threadId, "threadId"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ReadMemoryRequest,
+                              "readMemory",
+                              DAP_FIELD(count, "count"),
+                              DAP_FIELD(memoryReference, "memoryReference"),
+                              DAP_FIELD(offset, "offset"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(RestartFrameRequest,
+                              "restartFrame",
+                              DAP_FIELD(frameId, "frameId"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(RestartRequest,
+                              "restart",
+                              DAP_FIELD(arguments, "arguments"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ReverseContinueRequest,
+                              "reverseContinue",
+                              DAP_FIELD(singleThread, "singleThread"),
+                              DAP_FIELD(threadId, "threadId"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(RunInTerminalRequest,
+                              "runInTerminal",
+                              DAP_FIELD(args, "args"),
+                              DAP_FIELD(argsCanBeInterpretedByShell,
+                                        "argsCanBeInterpretedByShell"),
+                              DAP_FIELD(cwd, "cwd"),
+                              DAP_FIELD(env, "env"),
+                              DAP_FIELD(kind, "kind"),
+                              DAP_FIELD(title, "title"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ScopesRequest,
+                              "scopes",
+                              DAP_FIELD(frameId, "frameId"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(SetBreakpointsRequest,
+                              "setBreakpoints",
+                              DAP_FIELD(breakpoints, "breakpoints"),
+                              DAP_FIELD(lines, "lines"),
+                              DAP_FIELD(source, "source"),
+                              DAP_FIELD(sourceModified, "sourceModified"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(SetDataBreakpointsRequest,
+                              "setDataBreakpoints",
+                              DAP_FIELD(breakpoints, "breakpoints"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(SetExceptionBreakpointsRequest,
+                              "setExceptionBreakpoints",
+                              DAP_FIELD(exceptionOptions, "exceptionOptions"),
+                              DAP_FIELD(filterOptions, "filterOptions"),
+                              DAP_FIELD(filters, "filters"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(SetExpressionRequest,
+                              "setExpression",
+                              DAP_FIELD(expression, "expression"),
+                              DAP_FIELD(format, "format"),
+                              DAP_FIELD(frameId, "frameId"),
+                              DAP_FIELD(value, "value"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(SetFunctionBreakpointsRequest,
+                              "setFunctionBreakpoints",
+                              DAP_FIELD(breakpoints, "breakpoints"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(SetInstructionBreakpointsRequest,
+                              "setInstructionBreakpoints",
+                              DAP_FIELD(breakpoints, "breakpoints"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(SetVariableRequest,
+                              "setVariable",
+                              DAP_FIELD(format, "format"),
+                              DAP_FIELD(name, "name"),
+                              DAP_FIELD(value, "value"),
+                              DAP_FIELD(variablesReference,
+                                        "variablesReference"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(SourceRequest,
+                              "source",
+                              DAP_FIELD(source, "source"),
+                              DAP_FIELD(sourceReference, "sourceReference"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(StackTraceRequest,
+                              "stackTrace",
+                              DAP_FIELD(format, "format"),
+                              DAP_FIELD(levels, "levels"),
+                              DAP_FIELD(startFrame, "startFrame"),
+                              DAP_FIELD(threadId, "threadId"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(StartDebuggingRequest,
+                              "startDebugging",
+                              DAP_FIELD(configuration, "configuration"),
+                              DAP_FIELD(request, "request"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(StepBackRequest,
+                              "stepBack",
+                              DAP_FIELD(granularity, "granularity"),
+                              DAP_FIELD(singleThread, "singleThread"),
+                              DAP_FIELD(threadId, "threadId"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(StepInRequest,
+                              "stepIn",
+                              DAP_FIELD(granularity, "granularity"),
+                              DAP_FIELD(singleThread, "singleThread"),
+                              DAP_FIELD(targetId, "targetId"),
+                              DAP_FIELD(threadId, "threadId"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(StepInTargetsRequest,
+                              "stepInTargets",
+                              DAP_FIELD(frameId, "frameId"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(StepOutRequest,
+                              "stepOut",
+                              DAP_FIELD(granularity, "granularity"),
+                              DAP_FIELD(singleThread, "singleThread"),
+                              DAP_FIELD(threadId, "threadId"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(TerminateRequest,
+                              "terminate",
+                              DAP_FIELD(restart, "restart"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(TerminateThreadsRequest,
+                              "terminateThreads",
+                              DAP_FIELD(threadIds, "threadIds"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ThreadsRequest, "threads");
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(VariablesRequest,
+                              "variables",
+                              DAP_FIELD(count, "count"),
+                              DAP_FIELD(filter, "filter"),
+                              DAP_FIELD(format, "format"),
+                              DAP_FIELD(start, "start"),
+                              DAP_FIELD(variablesReference,
+                                        "variablesReference"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(WriteMemoryRequest,
+                              "writeMemory",
+                              DAP_FIELD(allowPartial, "allowPartial"),
+                              DAP_FIELD(data, "data"),
+                              DAP_FIELD(memoryReference, "memoryReference"),
+                              DAP_FIELD(offset, "offset"));
+
+}  // namespace dap
diff --git a/Utilities/cmcppdap/src/protocol_response.cpp b/Utilities/cmcppdap/src/protocol_response.cpp
new file mode 100644
index 0000000..bab8ebb
--- /dev/null
+++ b/Utilities/cmcppdap/src/protocol_response.cpp
@@ -0,0 +1,243 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Generated with protocol_gen.go -- do not edit this file.
+//   go run scripts/protocol_gen/protocol_gen.go
+//
+// DAP version 1.59.0
+
+#include "dap/protocol.h"
+
+namespace dap {
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(AttachResponse, "");
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(BreakpointLocationsResponse,
+                              "",
+                              DAP_FIELD(breakpoints, "breakpoints"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(CancelResponse, "");
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(CompletionsResponse,
+                              "",
+                              DAP_FIELD(targets, "targets"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ConfigurationDoneResponse, "");
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ContinueResponse,
+                              "",
+                              DAP_FIELD(allThreadsContinued,
+                                        "allThreadsContinued"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(DataBreakpointInfoResponse,
+                              "",
+                              DAP_FIELD(accessTypes, "accessTypes"),
+                              DAP_FIELD(canPersist, "canPersist"),
+                              DAP_FIELD(dataId, "dataId"),
+                              DAP_FIELD(description, "description"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(DisassembleResponse,
+                              "",
+                              DAP_FIELD(instructions, "instructions"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(DisconnectResponse, "");
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ErrorResponse, "", DAP_FIELD(error, "error"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(EvaluateResponse,
+                              "",
+                              DAP_FIELD(indexedVariables, "indexedVariables"),
+                              DAP_FIELD(memoryReference, "memoryReference"),
+                              DAP_FIELD(namedVariables, "namedVariables"),
+                              DAP_FIELD(presentationHint, "presentationHint"),
+                              DAP_FIELD(result, "result"),
+                              DAP_FIELD(type, "type"),
+                              DAP_FIELD(variablesReference,
+                                        "variablesReference"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ExceptionInfoResponse,
+                              "",
+                              DAP_FIELD(breakMode, "breakMode"),
+                              DAP_FIELD(description, "description"),
+                              DAP_FIELD(details, "details"),
+                              DAP_FIELD(exceptionId, "exceptionId"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(GotoResponse, "");
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(GotoTargetsResponse,
+                              "",
+                              DAP_FIELD(targets, "targets"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(
+    InitializeResponse,
+    "",
+    DAP_FIELD(additionalModuleColumns, "additionalModuleColumns"),
+    DAP_FIELD(completionTriggerCharacters, "completionTriggerCharacters"),
+    DAP_FIELD(exceptionBreakpointFilters, "exceptionBreakpointFilters"),
+    DAP_FIELD(supportSuspendDebuggee, "supportSuspendDebuggee"),
+    DAP_FIELD(supportTerminateDebuggee, "supportTerminateDebuggee"),
+    DAP_FIELD(supportedChecksumAlgorithms, "supportedChecksumAlgorithms"),
+    DAP_FIELD(supportsBreakpointLocationsRequest,
+              "supportsBreakpointLocationsRequest"),
+    DAP_FIELD(supportsCancelRequest, "supportsCancelRequest"),
+    DAP_FIELD(supportsClipboardContext, "supportsClipboardContext"),
+    DAP_FIELD(supportsCompletionsRequest, "supportsCompletionsRequest"),
+    DAP_FIELD(supportsConditionalBreakpoints, "supportsConditionalBreakpoints"),
+    DAP_FIELD(supportsConfigurationDoneRequest,
+              "supportsConfigurationDoneRequest"),
+    DAP_FIELD(supportsDataBreakpoints, "supportsDataBreakpoints"),
+    DAP_FIELD(supportsDelayedStackTraceLoading,
+              "supportsDelayedStackTraceLoading"),
+    DAP_FIELD(supportsDisassembleRequest, "supportsDisassembleRequest"),
+    DAP_FIELD(supportsEvaluateForHovers, "supportsEvaluateForHovers"),
+    DAP_FIELD(supportsExceptionFilterOptions, "supportsExceptionFilterOptions"),
+    DAP_FIELD(supportsExceptionInfoRequest, "supportsExceptionInfoRequest"),
+    DAP_FIELD(supportsExceptionOptions, "supportsExceptionOptions"),
+    DAP_FIELD(supportsFunctionBreakpoints, "supportsFunctionBreakpoints"),
+    DAP_FIELD(supportsGotoTargetsRequest, "supportsGotoTargetsRequest"),
+    DAP_FIELD(supportsHitConditionalBreakpoints,
+              "supportsHitConditionalBreakpoints"),
+    DAP_FIELD(supportsInstructionBreakpoints, "supportsInstructionBreakpoints"),
+    DAP_FIELD(supportsLoadedSourcesRequest, "supportsLoadedSourcesRequest"),
+    DAP_FIELD(supportsLogPoints, "supportsLogPoints"),
+    DAP_FIELD(supportsModulesRequest, "supportsModulesRequest"),
+    DAP_FIELD(supportsReadMemoryRequest, "supportsReadMemoryRequest"),
+    DAP_FIELD(supportsRestartFrame, "supportsRestartFrame"),
+    DAP_FIELD(supportsRestartRequest, "supportsRestartRequest"),
+    DAP_FIELD(supportsSetExpression, "supportsSetExpression"),
+    DAP_FIELD(supportsSetVariable, "supportsSetVariable"),
+    DAP_FIELD(supportsSingleThreadExecutionRequests,
+              "supportsSingleThreadExecutionRequests"),
+    DAP_FIELD(supportsStepBack, "supportsStepBack"),
+    DAP_FIELD(supportsStepInTargetsRequest, "supportsStepInTargetsRequest"),
+    DAP_FIELD(supportsSteppingGranularity, "supportsSteppingGranularity"),
+    DAP_FIELD(supportsTerminateRequest, "supportsTerminateRequest"),
+    DAP_FIELD(supportsTerminateThreadsRequest,
+              "supportsTerminateThreadsRequest"),
+    DAP_FIELD(supportsValueFormattingOptions, "supportsValueFormattingOptions"),
+    DAP_FIELD(supportsWriteMemoryRequest, "supportsWriteMemoryRequest"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(LaunchResponse, "");
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(LoadedSourcesResponse,
+                              "",
+                              DAP_FIELD(sources, "sources"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ModulesResponse,
+                              "",
+                              DAP_FIELD(modules, "modules"),
+                              DAP_FIELD(totalModules, "totalModules"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(NextResponse, "");
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(PauseResponse, "");
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ReadMemoryResponse,
+                              "",
+                              DAP_FIELD(address, "address"),
+                              DAP_FIELD(data, "data"),
+                              DAP_FIELD(unreadableBytes, "unreadableBytes"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(RestartFrameResponse, "");
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(RestartResponse, "");
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ReverseContinueResponse, "");
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(RunInTerminalResponse,
+                              "",
+                              DAP_FIELD(processId, "processId"),
+                              DAP_FIELD(shellProcessId, "shellProcessId"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ScopesResponse, "", DAP_FIELD(scopes, "scopes"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(SetBreakpointsResponse,
+                              "",
+                              DAP_FIELD(breakpoints, "breakpoints"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(SetDataBreakpointsResponse,
+                              "",
+                              DAP_FIELD(breakpoints, "breakpoints"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(SetExceptionBreakpointsResponse,
+                              "",
+                              DAP_FIELD(breakpoints, "breakpoints"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(SetExpressionResponse,
+                              "",
+                              DAP_FIELD(indexedVariables, "indexedVariables"),
+                              DAP_FIELD(namedVariables, "namedVariables"),
+                              DAP_FIELD(presentationHint, "presentationHint"),
+                              DAP_FIELD(type, "type"),
+                              DAP_FIELD(value, "value"),
+                              DAP_FIELD(variablesReference,
+                                        "variablesReference"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(SetFunctionBreakpointsResponse,
+                              "",
+                              DAP_FIELD(breakpoints, "breakpoints"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(SetInstructionBreakpointsResponse,
+                              "",
+                              DAP_FIELD(breakpoints, "breakpoints"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(SetVariableResponse,
+                              "",
+                              DAP_FIELD(indexedVariables, "indexedVariables"),
+                              DAP_FIELD(namedVariables, "namedVariables"),
+                              DAP_FIELD(type, "type"),
+                              DAP_FIELD(value, "value"),
+                              DAP_FIELD(variablesReference,
+                                        "variablesReference"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(SourceResponse,
+                              "",
+                              DAP_FIELD(content, "content"),
+                              DAP_FIELD(mimeType, "mimeType"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(StackTraceResponse,
+                              "",
+                              DAP_FIELD(stackFrames, "stackFrames"),
+                              DAP_FIELD(totalFrames, "totalFrames"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(StartDebuggingResponse, "");
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(StepBackResponse, "");
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(StepInResponse, "");
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(StepInTargetsResponse,
+                              "",
+                              DAP_FIELD(targets, "targets"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(StepOutResponse, "");
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(TerminateResponse, "");
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(TerminateThreadsResponse, "");
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ThreadsResponse,
+                              "",
+                              DAP_FIELD(threads, "threads"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(VariablesResponse,
+                              "",
+                              DAP_FIELD(variables, "variables"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(WriteMemoryResponse,
+                              "",
+                              DAP_FIELD(bytesWritten, "bytesWritten"),
+                              DAP_FIELD(offset, "offset"));
+
+}  // namespace dap
diff --git a/Utilities/cmcppdap/src/protocol_types.cpp b/Utilities/cmcppdap/src/protocol_types.cpp
new file mode 100644
index 0000000..d9a9e36
--- /dev/null
+++ b/Utilities/cmcppdap/src/protocol_types.cpp
@@ -0,0 +1,316 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Generated with protocol_gen.go -- do not edit this file.
+//   go run scripts/protocol_gen/protocol_gen.go
+//
+// DAP version 1.59.0
+
+#include "dap/protocol.h"
+
+namespace dap {
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(Checksum,
+                              "",
+                              DAP_FIELD(algorithm, "algorithm"),
+                              DAP_FIELD(checksum, "checksum"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(Source,
+                              "",
+                              DAP_FIELD(adapterData, "adapterData"),
+                              DAP_FIELD(checksums, "checksums"),
+                              DAP_FIELD(name, "name"),
+                              DAP_FIELD(origin, "origin"),
+                              DAP_FIELD(path, "path"),
+                              DAP_FIELD(presentationHint, "presentationHint"),
+                              DAP_FIELD(sourceReference, "sourceReference"),
+                              DAP_FIELD(sources, "sources"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(Breakpoint,
+                              "",
+                              DAP_FIELD(column, "column"),
+                              DAP_FIELD(endColumn, "endColumn"),
+                              DAP_FIELD(endLine, "endLine"),
+                              DAP_FIELD(id, "id"),
+                              DAP_FIELD(instructionReference,
+                                        "instructionReference"),
+                              DAP_FIELD(line, "line"),
+                              DAP_FIELD(message, "message"),
+                              DAP_FIELD(offset, "offset"),
+                              DAP_FIELD(source, "source"),
+                              DAP_FIELD(verified, "verified"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(BreakpointLocation,
+                              "",
+                              DAP_FIELD(column, "column"),
+                              DAP_FIELD(endColumn, "endColumn"),
+                              DAP_FIELD(endLine, "endLine"),
+                              DAP_FIELD(line, "line"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ColumnDescriptor,
+                              "",
+                              DAP_FIELD(attributeName, "attributeName"),
+                              DAP_FIELD(format, "format"),
+                              DAP_FIELD(label, "label"),
+                              DAP_FIELD(type, "type"),
+                              DAP_FIELD(width, "width"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ExceptionBreakpointsFilter,
+                              "",
+                              DAP_FIELD(conditionDescription,
+                                        "conditionDescription"),
+                              DAP_FIELD(def, "default"),
+                              DAP_FIELD(description, "description"),
+                              DAP_FIELD(filter, "filter"),
+                              DAP_FIELD(label, "label"),
+                              DAP_FIELD(supportsCondition,
+                                        "supportsCondition"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(
+    Capabilities,
+    "",
+    DAP_FIELD(additionalModuleColumns, "additionalModuleColumns"),
+    DAP_FIELD(completionTriggerCharacters, "completionTriggerCharacters"),
+    DAP_FIELD(exceptionBreakpointFilters, "exceptionBreakpointFilters"),
+    DAP_FIELD(supportSuspendDebuggee, "supportSuspendDebuggee"),
+    DAP_FIELD(supportTerminateDebuggee, "supportTerminateDebuggee"),
+    DAP_FIELD(supportedChecksumAlgorithms, "supportedChecksumAlgorithms"),
+    DAP_FIELD(supportsBreakpointLocationsRequest,
+              "supportsBreakpointLocationsRequest"),
+    DAP_FIELD(supportsCancelRequest, "supportsCancelRequest"),
+    DAP_FIELD(supportsClipboardContext, "supportsClipboardContext"),
+    DAP_FIELD(supportsCompletionsRequest, "supportsCompletionsRequest"),
+    DAP_FIELD(supportsConditionalBreakpoints, "supportsConditionalBreakpoints"),
+    DAP_FIELD(supportsConfigurationDoneRequest,
+              "supportsConfigurationDoneRequest"),
+    DAP_FIELD(supportsDataBreakpoints, "supportsDataBreakpoints"),
+    DAP_FIELD(supportsDelayedStackTraceLoading,
+              "supportsDelayedStackTraceLoading"),
+    DAP_FIELD(supportsDisassembleRequest, "supportsDisassembleRequest"),
+    DAP_FIELD(supportsEvaluateForHovers, "supportsEvaluateForHovers"),
+    DAP_FIELD(supportsExceptionFilterOptions, "supportsExceptionFilterOptions"),
+    DAP_FIELD(supportsExceptionInfoRequest, "supportsExceptionInfoRequest"),
+    DAP_FIELD(supportsExceptionOptions, "supportsExceptionOptions"),
+    DAP_FIELD(supportsFunctionBreakpoints, "supportsFunctionBreakpoints"),
+    DAP_FIELD(supportsGotoTargetsRequest, "supportsGotoTargetsRequest"),
+    DAP_FIELD(supportsHitConditionalBreakpoints,
+              "supportsHitConditionalBreakpoints"),
+    DAP_FIELD(supportsInstructionBreakpoints, "supportsInstructionBreakpoints"),
+    DAP_FIELD(supportsLoadedSourcesRequest, "supportsLoadedSourcesRequest"),
+    DAP_FIELD(supportsLogPoints, "supportsLogPoints"),
+    DAP_FIELD(supportsModulesRequest, "supportsModulesRequest"),
+    DAP_FIELD(supportsReadMemoryRequest, "supportsReadMemoryRequest"),
+    DAP_FIELD(supportsRestartFrame, "supportsRestartFrame"),
+    DAP_FIELD(supportsRestartRequest, "supportsRestartRequest"),
+    DAP_FIELD(supportsSetExpression, "supportsSetExpression"),
+    DAP_FIELD(supportsSetVariable, "supportsSetVariable"),
+    DAP_FIELD(supportsSingleThreadExecutionRequests,
+              "supportsSingleThreadExecutionRequests"),
+    DAP_FIELD(supportsStepBack, "supportsStepBack"),
+    DAP_FIELD(supportsStepInTargetsRequest, "supportsStepInTargetsRequest"),
+    DAP_FIELD(supportsSteppingGranularity, "supportsSteppingGranularity"),
+    DAP_FIELD(supportsTerminateRequest, "supportsTerminateRequest"),
+    DAP_FIELD(supportsTerminateThreadsRequest,
+              "supportsTerminateThreadsRequest"),
+    DAP_FIELD(supportsValueFormattingOptions, "supportsValueFormattingOptions"),
+    DAP_FIELD(supportsWriteMemoryRequest, "supportsWriteMemoryRequest"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(CompletionItem,
+                              "",
+                              DAP_FIELD(detail, "detail"),
+                              DAP_FIELD(label, "label"),
+                              DAP_FIELD(length, "length"),
+                              DAP_FIELD(selectionLength, "selectionLength"),
+                              DAP_FIELD(selectionStart, "selectionStart"),
+                              DAP_FIELD(sortText, "sortText"),
+                              DAP_FIELD(start, "start"),
+                              DAP_FIELD(text, "text"),
+                              DAP_FIELD(type, "type"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(DisassembledInstruction,
+                              "",
+                              DAP_FIELD(address, "address"),
+                              DAP_FIELD(column, "column"),
+                              DAP_FIELD(endColumn, "endColumn"),
+                              DAP_FIELD(endLine, "endLine"),
+                              DAP_FIELD(instruction, "instruction"),
+                              DAP_FIELD(instructionBytes, "instructionBytes"),
+                              DAP_FIELD(line, "line"),
+                              DAP_FIELD(location, "location"),
+                              DAP_FIELD(symbol, "symbol"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(Message,
+                              "",
+                              DAP_FIELD(format, "format"),
+                              DAP_FIELD(id, "id"),
+                              DAP_FIELD(sendTelemetry, "sendTelemetry"),
+                              DAP_FIELD(showUser, "showUser"),
+                              DAP_FIELD(url, "url"),
+                              DAP_FIELD(urlLabel, "urlLabel"),
+                              DAP_FIELD(variables, "variables"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(VariablePresentationHint,
+                              "",
+                              DAP_FIELD(attributes, "attributes"),
+                              DAP_FIELD(kind, "kind"),
+                              DAP_FIELD(lazy, "lazy"),
+                              DAP_FIELD(visibility, "visibility"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ValueFormat, "", DAP_FIELD(hex, "hex"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ExceptionDetails,
+                              "",
+                              DAP_FIELD(evaluateName, "evaluateName"),
+                              DAP_FIELD(fullTypeName, "fullTypeName"),
+                              DAP_FIELD(innerException, "innerException"),
+                              DAP_FIELD(message, "message"),
+                              DAP_FIELD(stackTrace, "stackTrace"),
+                              DAP_FIELD(typeName, "typeName"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(GotoTarget,
+                              "",
+                              DAP_FIELD(column, "column"),
+                              DAP_FIELD(endColumn, "endColumn"),
+                              DAP_FIELD(endLine, "endLine"),
+                              DAP_FIELD(id, "id"),
+                              DAP_FIELD(instructionPointerReference,
+                                        "instructionPointerReference"),
+                              DAP_FIELD(label, "label"),
+                              DAP_FIELD(line, "line"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(Module,
+                              "",
+                              DAP_FIELD(addressRange, "addressRange"),
+                              DAP_FIELD(dateTimeStamp, "dateTimeStamp"),
+                              DAP_FIELD(id, "id"),
+                              DAP_FIELD(isOptimized, "isOptimized"),
+                              DAP_FIELD(isUserCode, "isUserCode"),
+                              DAP_FIELD(name, "name"),
+                              DAP_FIELD(path, "path"),
+                              DAP_FIELD(symbolFilePath, "symbolFilePath"),
+                              DAP_FIELD(symbolStatus, "symbolStatus"),
+                              DAP_FIELD(version, "version"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(Scope,
+                              "",
+                              DAP_FIELD(column, "column"),
+                              DAP_FIELD(endColumn, "endColumn"),
+                              DAP_FIELD(endLine, "endLine"),
+                              DAP_FIELD(expensive, "expensive"),
+                              DAP_FIELD(indexedVariables, "indexedVariables"),
+                              DAP_FIELD(line, "line"),
+                              DAP_FIELD(name, "name"),
+                              DAP_FIELD(namedVariables, "namedVariables"),
+                              DAP_FIELD(presentationHint, "presentationHint"),
+                              DAP_FIELD(source, "source"),
+                              DAP_FIELD(variablesReference,
+                                        "variablesReference"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(SourceBreakpoint,
+                              "",
+                              DAP_FIELD(column, "column"),
+                              DAP_FIELD(condition, "condition"),
+                              DAP_FIELD(hitCondition, "hitCondition"),
+                              DAP_FIELD(line, "line"),
+                              DAP_FIELD(logMessage, "logMessage"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(DataBreakpoint,
+                              "",
+                              DAP_FIELD(accessType, "accessType"),
+                              DAP_FIELD(condition, "condition"),
+                              DAP_FIELD(dataId, "dataId"),
+                              DAP_FIELD(hitCondition, "hitCondition"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ExceptionPathSegment,
+                              "",
+                              DAP_FIELD(names, "names"),
+                              DAP_FIELD(negate, "negate"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ExceptionOptions,
+                              "",
+                              DAP_FIELD(breakMode, "breakMode"),
+                              DAP_FIELD(path, "path"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(ExceptionFilterOptions,
+                              "",
+                              DAP_FIELD(condition, "condition"),
+                              DAP_FIELD(filterId, "filterId"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(FunctionBreakpoint,
+                              "",
+                              DAP_FIELD(condition, "condition"),
+                              DAP_FIELD(hitCondition, "hitCondition"),
+                              DAP_FIELD(name, "name"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(InstructionBreakpoint,
+                              "",
+                              DAP_FIELD(condition, "condition"),
+                              DAP_FIELD(hitCondition, "hitCondition"),
+                              DAP_FIELD(instructionReference,
+                                        "instructionReference"),
+                              DAP_FIELD(offset, "offset"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(StackFrame,
+                              "",
+                              DAP_FIELD(canRestart, "canRestart"),
+                              DAP_FIELD(column, "column"),
+                              DAP_FIELD(endColumn, "endColumn"),
+                              DAP_FIELD(endLine, "endLine"),
+                              DAP_FIELD(id, "id"),
+                              DAP_FIELD(instructionPointerReference,
+                                        "instructionPointerReference"),
+                              DAP_FIELD(line, "line"),
+                              DAP_FIELD(moduleId, "moduleId"),
+                              DAP_FIELD(name, "name"),
+                              DAP_FIELD(presentationHint, "presentationHint"),
+                              DAP_FIELD(source, "source"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(StackFrameFormat,
+                              "",
+                              DAP_FIELD(includeAll, "includeAll"),
+                              DAP_FIELD(line, "line"),
+                              DAP_FIELD(module, "module"),
+                              DAP_FIELD(parameterNames, "parameterNames"),
+                              DAP_FIELD(parameterTypes, "parameterTypes"),
+                              DAP_FIELD(parameterValues, "parameterValues"),
+                              DAP_FIELD(parameters, "parameters"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(StepInTarget,
+                              "",
+                              DAP_FIELD(column, "column"),
+                              DAP_FIELD(endColumn, "endColumn"),
+                              DAP_FIELD(endLine, "endLine"),
+                              DAP_FIELD(id, "id"),
+                              DAP_FIELD(label, "label"),
+                              DAP_FIELD(line, "line"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(Thread,
+                              "",
+                              DAP_FIELD(id, "id"),
+                              DAP_FIELD(name, "name"));
+
+DAP_IMPLEMENT_STRUCT_TYPEINFO(Variable,
+                              "",
+                              DAP_FIELD(evaluateName, "evaluateName"),
+                              DAP_FIELD(indexedVariables, "indexedVariables"),
+                              DAP_FIELD(memoryReference, "memoryReference"),
+                              DAP_FIELD(name, "name"),
+                              DAP_FIELD(namedVariables, "namedVariables"),
+                              DAP_FIELD(presentationHint, "presentationHint"),
+                              DAP_FIELD(type, "type"),
+                              DAP_FIELD(value, "value"),
+                              DAP_FIELD(variablesReference,
+                                        "variablesReference"));
+
+}  // namespace dap
diff --git a/Utilities/cmcppdap/src/rapid_json_serializer.cpp b/Utilities/cmcppdap/src/rapid_json_serializer.cpp
new file mode 100644
index 0000000..178db99
--- /dev/null
+++ b/Utilities/cmcppdap/src/rapid_json_serializer.cpp
@@ -0,0 +1,289 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "rapid_json_serializer.h"
+
+#include "null_json_serializer.h"
+
+#include <rapidjson/document.h>
+#include <rapidjson/prettywriter.h>
+
+namespace dap {
+namespace json {
+
+RapidDeserializer::RapidDeserializer(const std::string& str)
+    : doc(new rapidjson::Document()) {
+  doc->Parse(str.c_str());
+}
+
+RapidDeserializer::RapidDeserializer(rapidjson::Value* json) : val(json) {}
+
+RapidDeserializer::~RapidDeserializer() {
+  delete doc;
+}
+
+bool RapidDeserializer::deserialize(dap::boolean* v) const {
+  if (!json()->IsBool()) {
+    return false;
+  }
+  *v = json()->GetBool();
+  return true;
+}
+
+bool RapidDeserializer::deserialize(dap::integer* v) const {
+  if (json()->IsInt()) {
+    *v = json()->GetInt();
+    return true;
+  } else if (json()->IsUint()) {
+    *v = static_cast<int64_t>(json()->GetUint());
+    return true;
+  } else if (json()->IsInt64()) {
+    *v = json()->GetInt64();
+    return true;
+  } else if (json()->IsUint64()) {
+    *v = static_cast<int64_t>(json()->GetUint64());
+    return true;
+  }
+  return false;
+}
+
+bool RapidDeserializer::deserialize(dap::number* v) const {
+  if (!json()->IsNumber()) {
+    return false;
+  }
+  *v = json()->GetDouble();
+  return true;
+}
+
+bool RapidDeserializer::deserialize(dap::string* v) const {
+  if (!json()->IsString()) {
+    return false;
+  }
+  *v = json()->GetString();
+  return true;
+}
+
+bool RapidDeserializer::deserialize(dap::object* v) const {
+  v->reserve(json()->MemberCount());
+  for (auto el = json()->MemberBegin(); el != json()->MemberEnd(); el++) {
+    dap::any el_val;
+    RapidDeserializer d(&(el->value));
+    if (!d.deserialize(&el_val)) {
+      return false;
+    }
+    (*v)[el->name.GetString()] = el_val;
+  }
+  return true;
+}
+
+bool RapidDeserializer::deserialize(dap::any* v) const {
+  if (json()->IsBool()) {
+    *v = dap::boolean(json()->GetBool());
+  } else if (json()->IsDouble()) {
+    *v = dap::number(json()->GetDouble());
+  } else if (json()->IsInt()) {
+    *v = dap::integer(json()->GetInt());
+  } else if (json()->IsString()) {
+    *v = dap::string(json()->GetString());
+  } else if (json()->IsNull()) {
+    *v = null();
+  } else if (json()->IsObject()) {
+    dap::object obj;
+    if (!deserialize(&obj)) {
+      return false;
+    }
+    *v = obj;
+  } else if (json()->IsArray()){
+    dap::array<any> arr;
+    if (!deserialize(&arr)){
+      return false;
+    }
+    *v = arr;
+  } else {
+    return false;
+  }
+  return true;
+}
+
+size_t RapidDeserializer::count() const {
+  return json()->Size();
+}
+
+bool RapidDeserializer::array(
+    const std::function<bool(dap::Deserializer*)>& cb) const {
+  if (!json()->IsArray()) {
+    return false;
+  }
+  for (uint32_t i = 0; i < json()->Size(); i++) {
+    RapidDeserializer d(&(*json())[i]);
+    if (!cb(&d)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool RapidDeserializer::field(
+    const std::string& name,
+    const std::function<bool(dap::Deserializer*)>& cb) const {
+  if (!json()->IsObject()) {
+    return false;
+  }
+  auto it = json()->FindMember(name.c_str());
+  if (it == json()->MemberEnd()) {
+    return cb(&NullDeserializer::instance);
+  }
+  RapidDeserializer d(&(it->value));
+  return cb(&d);
+}
+
+RapidSerializer::RapidSerializer()
+    : doc(new rapidjson::Document(rapidjson::kObjectType)),
+      allocator(doc->GetAllocator()) {}
+
+RapidSerializer::RapidSerializer(rapidjson::Value* json,
+                                 rapidjson::Document::AllocatorType& allocator)
+    : val(json), allocator(allocator) {}
+
+RapidSerializer::~RapidSerializer() {
+  delete doc;
+}
+
+std::string RapidSerializer::dump() const {
+  rapidjson::StringBuffer sb;
+  rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(sb);
+  json()->Accept(writer);
+  return sb.GetString();
+}
+
+bool RapidSerializer::serialize(dap::boolean v) {
+  json()->SetBool(v);
+  return true;
+}
+
+bool RapidSerializer::serialize(dap::integer v) {
+  json()->SetInt64(v);
+  return true;
+}
+
+bool RapidSerializer::serialize(dap::number v) {
+  json()->SetDouble(v);
+  return true;
+}
+
+bool RapidSerializer::serialize(const dap::string& v) {
+  json()->SetString(v.data(), static_cast<uint32_t>(v.length()), allocator);
+  return true;
+}
+
+bool RapidSerializer::serialize(const dap::object& v) {
+  if (!json()->IsObject()) {
+    json()->SetObject();
+  }
+  for (auto& it : v) {
+    if (!json()->HasMember(it.first.c_str())) {
+      rapidjson::Value name_value{it.first.c_str(), allocator};
+      json()->AddMember(name_value, rapidjson::Value(), allocator);
+    }
+    rapidjson::Value& member = (*json())[it.first.c_str()];
+    RapidSerializer s(&member, allocator);
+    if (!s.serialize(it.second)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool RapidSerializer::serialize(const dap::any& v) {
+  if (v.is<dap::boolean>()) {
+    json()->SetBool((bool)v.get<dap::boolean>());
+  } else if (v.is<dap::integer>()) {
+    json()->SetInt64(v.get<dap::integer>());
+  } else if (v.is<dap::number>()) {
+    json()->SetDouble((double)v.get<dap::number>());
+  } else if (v.is<dap::string>()) {
+    auto s = v.get<dap::string>();
+    json()->SetString(s.data(), static_cast<uint32_t>(s.length()), allocator);
+  } else if (v.is<dap::object>()) {
+    // reachable if dap::object nested is inside other dap::object
+    return serialize(v.get<dap::object>());
+  } else if (v.is<dap::null>()) {
+  } else {
+    // reachable if array or custom serialized type is nested inside other dap::object
+    auto type = get_any_type(v);
+    auto value = get_any_val(v);
+    if (type && value) {
+      return type->serialize(this, value);
+    }
+    return false;
+  }
+
+  return true;
+}
+
+bool RapidSerializer::array(size_t count,
+                            const std::function<bool(dap::Serializer*)>& cb) {
+  if (!json()->IsArray()) {
+    json()->SetArray();
+  }
+
+  while (count > json()->Size()) {
+    json()->PushBack(rapidjson::Value(), allocator);
+  }
+
+  for (uint32_t i = 0; i < count; i++) {
+    RapidSerializer s(&(*json())[i], allocator);
+    if (!cb(&s)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool RapidSerializer::object(
+    const std::function<bool(dap::FieldSerializer*)>& cb) {
+  struct FS : public FieldSerializer {
+    rapidjson::Value* const json;
+    rapidjson::Document::AllocatorType& allocator;
+
+    FS(rapidjson::Value* json, rapidjson::Document::AllocatorType& allocator)
+        : json(json), allocator(allocator) {}
+    bool field(const std::string& name, const SerializeFunc& cb) override {
+      if (!json->HasMember(name.c_str())) {
+        rapidjson::Value name_value{name.c_str(), allocator};
+        json->AddMember(name_value, rapidjson::Value(), allocator);
+      }
+      rapidjson::Value& member = (*json)[name.c_str()];
+      RapidSerializer s(&member, allocator);
+      auto res = cb(&s);
+      if (s.removed) {
+        json->RemoveMember(name.c_str());
+      }
+      return res;
+    }
+  };
+
+  if (!json()->IsObject()) {
+    json()->SetObject();
+  }
+  FS fs{json(), allocator};
+  return cb(&fs);
+}
+
+void RapidSerializer::remove() {
+  removed = true;
+}
+
+}  // namespace json
+}  // namespace dap
diff --git a/Utilities/cmcppdap/src/rapid_json_serializer.h b/Utilities/cmcppdap/src/rapid_json_serializer.h
new file mode 100644
index 0000000..6e83384
--- /dev/null
+++ b/Utilities/cmcppdap/src/rapid_json_serializer.h
@@ -0,0 +1,138 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef dap_rapid_json_serializer_h
+#define dap_rapid_json_serializer_h
+
+#include "dap/protocol.h"
+#include "dap/serialization.h"
+#include "dap/types.h"
+
+#include <rapidjson/document.h>
+
+namespace dap {
+namespace json {
+
+struct RapidDeserializer : public dap::Deserializer {
+  explicit RapidDeserializer(const std::string&);
+  ~RapidDeserializer();
+
+  // dap::Deserializer compliance
+  bool deserialize(boolean* v) const override;
+  bool deserialize(integer* v) const override;
+  bool deserialize(number* v) const override;
+  bool deserialize(string* v) const override;
+  bool deserialize(object* v) const override;
+  bool deserialize(any* v) const override;
+  size_t count() const override;
+  bool array(const std::function<bool(dap::Deserializer*)>&) const override;
+  bool field(const std::string& name,
+             const std::function<bool(dap::Deserializer*)>&) const override;
+
+  // Unhide base overloads
+  template <typename T>
+  inline bool field(const std::string& name, T* v) {
+    return dap::Deserializer::field(name, v);
+  }
+
+  template <typename T,
+            typename = std::enable_if<TypeOf<T>::has_custom_serialization>>
+  inline bool deserialize(T* v) const {
+    return dap::Deserializer::deserialize(v);
+  }
+
+  template <typename T>
+  inline bool deserialize(dap::array<T>* v) const {
+    return dap::Deserializer::deserialize(v);
+  }
+
+  template <typename T>
+  inline bool deserialize(dap::optional<T>* v) const {
+    return dap::Deserializer::deserialize(v);
+  }
+
+  template <typename T0, typename... Types>
+  inline bool deserialize(dap::variant<T0, Types...>* v) const {
+    return dap::Deserializer::deserialize(v);
+  }
+
+  template <typename T>
+  inline bool field(const std::string& name, T* v) const {
+    return dap::Deserializer::deserialize(name, v);
+  }
+
+  inline rapidjson::Value* json() const { return (val == nullptr) ? doc : val; }
+
+ private:
+  RapidDeserializer(rapidjson::Value*);
+  rapidjson::Document* const doc = nullptr;
+  rapidjson::Value* const val = nullptr;
+};
+
+struct RapidSerializer : public dap::Serializer {
+  RapidSerializer();
+  ~RapidSerializer();
+
+  std::string dump() const;
+
+  // dap::Serializer compliance
+  bool serialize(boolean v) override;
+  bool serialize(integer v) override;
+  bool serialize(number v) override;
+  bool serialize(const string& v) override;
+  bool serialize(const dap::object& v) override;
+  bool serialize(const any& v) override;
+  bool array(size_t count,
+             const std::function<bool(dap::Serializer*)>&) override;
+  bool object(const std::function<bool(dap::FieldSerializer*)>&) override;
+  void remove() override;
+
+  // Unhide base overloads
+  template <typename T,
+            typename = std::enable_if<TypeOf<T>::has_custom_serialization>>
+  inline bool serialize(const T& v) {
+    return dap::Serializer::serialize(v);
+  }
+
+  template <typename T>
+  inline bool serialize(const dap::array<T>& v) {
+    return dap::Serializer::serialize(v);
+  }
+
+  template <typename T>
+  inline bool serialize(const dap::optional<T>& v) {
+    return dap::Serializer::serialize(v);
+  }
+
+  template <typename T0, typename... Types>
+  inline bool serialize(const dap::variant<T0, Types...>& v) {
+    return dap::Serializer::serialize(v);
+  }
+
+  inline bool serialize(const char* v) { return dap::Serializer::serialize(v); }
+
+  inline rapidjson::Value* json() const { return (val == nullptr) ? doc : val; }
+
+ private:
+  RapidSerializer(rapidjson::Value*, rapidjson::Document::AllocatorType&);
+  rapidjson::Document* const doc = nullptr;
+  rapidjson::Value* const val = nullptr;
+  rapidjson::Document::AllocatorType& allocator;
+  bool removed = false;
+};
+
+}  // namespace json
+}  // namespace dap
+
+#endif  // dap_rapid_json_serializer_h
diff --git a/Utilities/cmcppdap/src/rwmutex.h b/Utilities/cmcppdap/src/rwmutex.h
new file mode 100644
index 0000000..9e85891
--- /dev/null
+++ b/Utilities/cmcppdap/src/rwmutex.h
@@ -0,0 +1,172 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef dap_rwmutex_h
+#define dap_rwmutex_h
+
+#include <condition_variable>
+#include <mutex>
+
+namespace dap {
+
+////////////////////////////////////////////////////////////////////////////////
+// RWMutex
+////////////////////////////////////////////////////////////////////////////////
+
+// A RWMutex is a reader/writer mutual exclusion lock.
+// The lock can be held by an arbitrary number of readers or a single writer.
+// Also known as a shared mutex.
+class RWMutex {
+ public:
+  inline RWMutex() = default;
+
+  // lockReader() locks the mutex for reading.
+  // Multiple read locks can be held while there are no writer locks.
+  inline void lockReader();
+
+  // unlockReader() unlocks the mutex for reading.
+  inline void unlockReader();
+
+  // lockWriter() locks the mutex for writing.
+  // If the lock is already locked for reading or writing, lockWriter blocks
+  // until the lock is available.
+  inline void lockWriter();
+
+  // unlockWriter() unlocks the mutex for writing.
+  inline void unlockWriter();
+
+ private:
+  RWMutex(const RWMutex&) = delete;
+  RWMutex& operator=(const RWMutex&) = delete;
+
+  int readLocks = 0;
+  int pendingWriteLocks = 0;
+  std::mutex mutex;
+  std::condition_variable cv;
+};
+
+void RWMutex::lockReader() {
+  std::unique_lock<std::mutex> lock(mutex);
+  readLocks++;
+}
+
+void RWMutex::unlockReader() {
+  std::unique_lock<std::mutex> lock(mutex);
+  readLocks--;
+  if (readLocks == 0 && pendingWriteLocks > 0) {
+    cv.notify_one();
+  }
+}
+
+void RWMutex::lockWriter() {
+  std::unique_lock<std::mutex> lock(mutex);
+  if (readLocks > 0) {
+    pendingWriteLocks++;
+    cv.wait(lock, [&] { return readLocks == 0; });
+    pendingWriteLocks--;
+  }
+  lock.release();  // Keep lock held
+}
+
+void RWMutex::unlockWriter() {
+  if (pendingWriteLocks > 0) {
+    cv.notify_one();
+  }
+  mutex.unlock();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RLock
+////////////////////////////////////////////////////////////////////////////////
+
+// RLock is a RAII read lock helper for a RWMutex.
+class RLock {
+ public:
+  inline RLock(RWMutex& mutex);
+  inline ~RLock();
+
+  inline RLock(RLock&&);
+  inline RLock& operator=(RLock&&);
+
+ private:
+  RLock(const RLock&) = delete;
+  RLock& operator=(const RLock&) = delete;
+
+  RWMutex* m;
+};
+
+RLock::RLock(RWMutex& mutex) : m(&mutex) {
+  m->lockReader();
+}
+
+RLock::~RLock() {
+  if (m != nullptr) {
+    m->unlockReader();
+  }
+}
+
+RLock::RLock(RLock&& other) {
+  m = other.m;
+  other.m = nullptr;
+}
+
+RLock& RLock::operator=(RLock&& other) {
+  m = other.m;
+  other.m = nullptr;
+  return *this;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WLock
+////////////////////////////////////////////////////////////////////////////////
+
+// WLock is a RAII write lock helper for a RWMutex.
+class WLock {
+ public:
+  inline WLock(RWMutex& mutex);
+  inline ~WLock();
+
+  inline WLock(WLock&&);
+  inline WLock& operator=(WLock&&);
+
+ private:
+  WLock(const WLock&) = delete;
+  WLock& operator=(const WLock&) = delete;
+
+  RWMutex* m;
+};
+
+WLock::WLock(RWMutex& mutex) : m(&mutex) {
+  m->lockWriter();
+}
+
+WLock::~WLock() {
+  if (m != nullptr) {
+    m->unlockWriter();
+  }
+}
+
+WLock::WLock(WLock&& other) {
+  m = other.m;
+  other.m = nullptr;
+}
+
+WLock& WLock::operator=(WLock&& other) {
+  m = other.m;
+  other.m = nullptr;
+  return *this;
+}
+}  // namespace dap
+
+#endif
diff --git a/Utilities/cmcppdap/src/rwmutex_test.cpp b/Utilities/cmcppdap/src/rwmutex_test.cpp
new file mode 100644
index 0000000..944ed77
--- /dev/null
+++ b/Utilities/cmcppdap/src/rwmutex_test.cpp
@@ -0,0 +1,113 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "rwmutex.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include <array>
+#include <thread>
+#include <vector>
+
+namespace {
+constexpr const size_t NumThreads = 8;
+}
+
+// Check that WLock behaves like regular mutex.
+TEST(RWMutex, WLock) {
+  dap::RWMutex rwmutex;
+  int counter = 0;
+
+  std::vector<std::thread> threads;
+  for (size_t i = 0; i < NumThreads; i++) {
+    threads.emplace_back([&] {
+      for (int j = 0; j < 1000; j++) {
+        dap::WLock lock(rwmutex);
+        counter++;
+        EXPECT_EQ(counter, 1);
+        counter--;
+      }
+    });
+  }
+
+  for (auto& thread : threads) {
+    thread.join();
+  }
+
+  EXPECT_EQ(counter, 0);
+}
+
+TEST(RWMutex, NoRLockWithWLock) {
+  dap::RWMutex rwmutex;
+
+  std::vector<std::thread> threads;
+  std::array<int, NumThreads> counters = {};
+
+  {  // With WLock held...
+    dap::WLock wlock(rwmutex);
+
+    for (size_t i = 0; i < counters.size(); i++) {
+      int* counter = &counters[i];
+      threads.emplace_back([&rwmutex, counter] {
+        dap::RLock lock(rwmutex);
+        for (int j = 0; j < 1000; j++) {
+          (*counter)++;
+        }
+      });
+    }
+
+    // RLocks should block
+    for (int counter : counters) {
+      EXPECT_EQ(counter, 0);
+    }
+  }
+
+  for (auto& thread : threads) {
+    thread.join();
+  }
+
+  for (int counter : counters) {
+    EXPECT_EQ(counter, 1000);
+  }
+}
+
+TEST(RWMutex, NoWLockWithRLock) {
+  dap::RWMutex rwmutex;
+
+  std::vector<std::thread> threads;
+  size_t counter = 0;
+
+  {  // With RLocks held...
+    dap::RLock rlockA(rwmutex);
+    dap::RLock rlockB(rwmutex);
+    dap::RLock rlockC(rwmutex);
+
+    for (size_t i = 0; i < NumThreads; i++) {
+      threads.emplace_back(std::thread([&] {
+        dap::WLock lock(rwmutex);
+        counter++;
+      }));
+    }
+
+    // ... WLocks should block
+    EXPECT_EQ(counter, 0U);
+  }
+
+  for (auto& thread : threads) {
+    thread.join();
+  }
+
+  EXPECT_EQ(counter, NumThreads);
+}
diff --git a/Utilities/cmcppdap/src/session.cpp b/Utilities/cmcppdap/src/session.cpp
new file mode 100644
index 0000000..d88a697
--- /dev/null
+++ b/Utilities/cmcppdap/src/session.cpp
@@ -0,0 +1,516 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "content_stream.h"
+
+#include "dap/any.h"
+#include "dap/session.h"
+
+#include "chan.h"
+#include "json_serializer.h"
+#include "socket.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <atomic>
+#include <deque>
+#include <memory>
+#include <mutex>
+#include <thread>
+#include <unordered_map>
+#include <vector>
+
+namespace {
+
+class Impl : public dap::Session {
+ public:
+  void onError(const ErrorHandler& handler) override { handlers.put(handler); }
+
+  void registerHandler(const dap::TypeInfo* typeinfo,
+                       const GenericRequestHandler& handler) override {
+    handlers.put(typeinfo, handler);
+  }
+
+  void registerHandler(const dap::TypeInfo* typeinfo,
+                       const GenericEventHandler& handler) override {
+    handlers.put(typeinfo, handler);
+  }
+
+  void registerHandler(const dap::TypeInfo* typeinfo,
+                       const GenericResponseSentHandler& handler) override {
+    handlers.put(typeinfo, handler);
+  }
+
+  std::function<void()> getPayload() override {
+    auto request = reader.read();
+    if (request.size() > 0) {
+      if (auto payload = processMessage(request)) {
+        return payload;
+      }
+    }
+    return {};
+  }
+
+  void connect(const std::shared_ptr<dap::Reader>& r,
+               const std::shared_ptr<dap::Writer>& w) override {
+    if (isBound.exchange(true)) {
+      handlers.error("Session::connect called twice");
+      return;
+    }
+
+    reader = dap::ContentReader(r);
+    writer = dap::ContentWriter(w);
+  }
+
+  void startProcessingMessages(
+      const ClosedHandler& onClose /* = {} */) override {
+    if (isProcessingMessages.exchange(true)) {
+      handlers.error("Session::startProcessingMessages() called twice");
+      return;
+    }
+    recvThread = std::thread([this, onClose] {
+      while (reader.isOpen()) {
+        if (auto payload = getPayload()) {
+          inbox.put(std::move(payload));
+        }
+      }
+      if (onClose) {
+        onClose();
+      }
+    });
+
+    dispatchThread = std::thread([this] {
+      while (auto payload = inbox.take()) {
+        payload.value()();
+      }
+    });
+  }
+
+  bool send(const dap::TypeInfo* requestTypeInfo,
+            const dap::TypeInfo* responseTypeInfo,
+            const void* request,
+            const GenericResponseHandler& responseHandler) override {
+    int seq = nextSeq++;
+
+    handlers.put(seq, responseTypeInfo, responseHandler);
+
+    dap::json::Serializer s;
+    if (!s.object([&](dap::FieldSerializer* fs) {
+          return fs->field("seq", dap::integer(seq)) &&
+                 fs->field("type", "request") &&
+                 fs->field("command", requestTypeInfo->name()) &&
+                 fs->field("arguments", [&](dap::Serializer* s) {
+                   return requestTypeInfo->serialize(s, request);
+                 });
+        })) {
+      return false;
+    }
+    return send(s.dump());
+  }
+
+  bool send(const dap::TypeInfo* typeinfo, const void* event) override {
+    dap::json::Serializer s;
+    if (!s.object([&](dap::FieldSerializer* fs) {
+          return fs->field("seq", dap::integer(nextSeq++)) &&
+                 fs->field("type", "event") &&
+                 fs->field("event", typeinfo->name()) &&
+                 fs->field("body", [&](dap::Serializer* s) {
+                   return typeinfo->serialize(s, event);
+                 });
+        })) {
+      return false;
+    }
+    return send(s.dump());
+  }
+
+  ~Impl() {
+    inbox.close();
+    reader.close();
+    writer.close();
+    if (recvThread.joinable()) {
+      recvThread.join();
+    }
+    if (dispatchThread.joinable()) {
+      dispatchThread.join();
+    }
+  }
+
+ private:
+  using Payload = std::function<void()>;
+
+  class EventHandlers {
+   public:
+    void put(const ErrorHandler& handler) {
+      std::unique_lock<std::mutex> lock(errorMutex);
+      errorHandler = handler;
+    }
+
+    void error(const char* format, ...) {
+      va_list vararg;
+      va_start(vararg, format);
+      std::unique_lock<std::mutex> lock(errorMutex);
+      errorLocked(format, vararg);
+      va_end(vararg);
+    }
+
+    std::pair<const dap::TypeInfo*, GenericRequestHandler> request(
+        const std::string& name) {
+      std::unique_lock<std::mutex> lock(requestMutex);
+      auto it = requestMap.find(name);
+      return (it != requestMap.end()) ? it->second : decltype(it->second){};
+    }
+
+    void put(const dap::TypeInfo* typeinfo,
+             const GenericRequestHandler& handler) {
+      std::unique_lock<std::mutex> lock(requestMutex);
+      auto added =
+          requestMap
+              .emplace(typeinfo->name(), std::make_pair(typeinfo, handler))
+              .second;
+      if (!added) {
+        errorfLocked("Request handler for '%s' already registered",
+                     typeinfo->name().c_str());
+      }
+    }
+
+    std::pair<const dap::TypeInfo*, GenericResponseHandler> response(
+        int64_t seq) {
+      std::unique_lock<std::mutex> lock(responseMutex);
+      auto responseIt = responseMap.find(seq);
+      if (responseIt == responseMap.end()) {
+        errorfLocked("Unknown response with sequence %d", seq);
+        return {};
+      }
+      auto out = std::move(responseIt->second);
+      responseMap.erase(seq);
+      return out;
+    }
+
+    void put(int seq,
+             const dap::TypeInfo* typeinfo,
+             const GenericResponseHandler& handler) {
+      std::unique_lock<std::mutex> lock(responseMutex);
+      auto added =
+          responseMap.emplace(seq, std::make_pair(typeinfo, handler)).second;
+      if (!added) {
+        errorfLocked("Response handler for sequence %d already registered",
+                     seq);
+      }
+    }
+
+    std::pair<const dap::TypeInfo*, GenericEventHandler> event(
+        const std::string& name) {
+      std::unique_lock<std::mutex> lock(eventMutex);
+      auto it = eventMap.find(name);
+      return (it != eventMap.end()) ? it->second : decltype(it->second){};
+    }
+
+    void put(const dap::TypeInfo* typeinfo,
+             const GenericEventHandler& handler) {
+      std::unique_lock<std::mutex> lock(eventMutex);
+      auto added =
+          eventMap.emplace(typeinfo->name(), std::make_pair(typeinfo, handler))
+              .second;
+      if (!added) {
+        errorfLocked("Event handler for '%s' already registered",
+                     typeinfo->name().c_str());
+      }
+    }
+
+    GenericResponseSentHandler responseSent(const dap::TypeInfo* typeinfo) {
+      std::unique_lock<std::mutex> lock(responseSentMutex);
+      auto it = responseSentMap.find(typeinfo);
+      return (it != responseSentMap.end()) ? it->second
+                                           : decltype(it->second){};
+    }
+
+    void put(const dap::TypeInfo* typeinfo,
+             const GenericResponseSentHandler& handler) {
+      std::unique_lock<std::mutex> lock(responseSentMutex);
+      auto added = responseSentMap.emplace(typeinfo, handler).second;
+      if (!added) {
+        errorfLocked("Response sent handler for '%s' already registered",
+                     typeinfo->name().c_str());
+      }
+    }
+
+   private:
+    void errorfLocked(const char* format, ...) {
+      va_list vararg;
+      va_start(vararg, format);
+      errorLocked(format, vararg);
+      va_end(vararg);
+    }
+
+    void errorLocked(const char* format, va_list args) {
+      char buf[2048];
+      vsnprintf(buf, sizeof(buf), format, args);
+      if (errorHandler) {
+        errorHandler(buf);
+      }
+    }
+
+    std::mutex errorMutex;
+    ErrorHandler errorHandler;
+
+    std::mutex requestMutex;
+    std::unordered_map<std::string,
+                       std::pair<const dap::TypeInfo*, GenericRequestHandler>>
+        requestMap;
+
+    std::mutex responseMutex;
+    std::unordered_map<int64_t,
+                       std::pair<const dap::TypeInfo*, GenericResponseHandler>>
+        responseMap;
+
+    std::mutex eventMutex;
+    std::unordered_map<std::string,
+                       std::pair<const dap::TypeInfo*, GenericEventHandler>>
+        eventMap;
+
+    std::mutex responseSentMutex;
+    std::unordered_map<const dap::TypeInfo*, GenericResponseSentHandler>
+        responseSentMap;
+  };  // EventHandlers
+
+  Payload processMessage(const std::string& str) {
+    auto d = dap::json::Deserializer(str);
+    dap::string type;
+    if (!d.field("type", &type)) {
+      handlers.error("Message missing string 'type' field");
+      return {};
+    }
+
+    dap::integer sequence = 0;
+    if (!d.field("seq", &sequence)) {
+      handlers.error("Message missing number 'seq' field");
+      return {};
+    }
+
+    if (type == "request") {
+      return processRequest(&d, sequence);
+    } else if (type == "event") {
+      return processEvent(&d);
+    } else if (type == "response") {
+      processResponse(&d);
+      return {};
+    } else {
+      handlers.error("Unknown message type '%s'", type.c_str());
+    }
+
+    return {};
+  }
+
+  Payload processRequest(dap::json::Deserializer* d, dap::integer sequence) {
+    dap::string command;
+    if (!d->field("command", &command)) {
+      handlers.error("Request missing string 'command' field");
+      return {};
+    }
+
+    const dap::TypeInfo* typeinfo;
+    GenericRequestHandler handler;
+    std::tie(typeinfo, handler) = handlers.request(command);
+    if (!typeinfo) {
+      handlers.error("No request handler registered for command '%s'",
+                     command.c_str());
+      return {};
+    }
+
+    auto data = new uint8_t[typeinfo->size()];
+    typeinfo->construct(data);
+
+    if (!d->field("arguments", [&](dap::Deserializer* d) {
+          return typeinfo->deserialize(d, data);
+        })) {
+      handlers.error("Failed to deserialize request");
+      typeinfo->destruct(data);
+      delete[] data;
+      return {};
+    }
+
+    return [=] {
+      handler(
+          data,
+          [=](const dap::TypeInfo* typeinfo, const void* data) {
+            // onSuccess
+            dap::json::Serializer s;
+            s.object([&](dap::FieldSerializer* fs) {
+              return fs->field("seq", dap::integer(nextSeq++)) &&
+                     fs->field("type", "response") &&
+                     fs->field("request_seq", sequence) &&
+                     fs->field("success", dap::boolean(true)) &&
+                     fs->field("command", command) &&
+                     fs->field("body", [&](dap::Serializer* s) {
+                       return typeinfo->serialize(s, data);
+                     });
+            });
+            send(s.dump());
+
+            if (auto handler = handlers.responseSent(typeinfo)) {
+              handler(data, nullptr);
+            }
+          },
+          [=](const dap::TypeInfo* typeinfo, const dap::Error& error) {
+            // onError
+            dap::json::Serializer s;
+            s.object([&](dap::FieldSerializer* fs) {
+              return fs->field("seq", dap::integer(nextSeq++)) &&
+                     fs->field("type", "response") &&
+                     fs->field("request_seq", sequence) &&
+                     fs->field("success", dap::boolean(false)) &&
+                     fs->field("command", command) &&
+                     fs->field("message", error.message);
+            });
+            send(s.dump());
+
+            if (auto handler = handlers.responseSent(typeinfo)) {
+              handler(nullptr, &error);
+            }
+          });
+      typeinfo->destruct(data);
+      delete[] data;
+    };
+  }
+
+  Payload processEvent(dap::json::Deserializer* d) {
+    dap::string event;
+    if (!d->field("event", &event)) {
+      handlers.error("Event missing string 'event' field");
+      return {};
+    }
+
+    const dap::TypeInfo* typeinfo;
+    GenericEventHandler handler;
+    std::tie(typeinfo, handler) = handlers.event(event);
+    if (!typeinfo) {
+      handlers.error("No event handler registered for event '%s'",
+                     event.c_str());
+      return {};
+    }
+
+    auto data = new uint8_t[typeinfo->size()];
+    typeinfo->construct(data);
+
+    // "body" is an optional field for some events, such as "Terminated Event".
+    bool body_ok = true;
+    d->field("body", [&](dap::Deserializer* d) {
+      if (!typeinfo->deserialize(d, data)) {
+        body_ok = false;
+      }
+      return true;
+    });
+
+    if (!body_ok) {
+      handlers.error("Failed to deserialize event '%s' body", event.c_str());
+      typeinfo->destruct(data);
+      delete[] data;
+      return {};
+    }
+
+    return [=] {
+      handler(data);
+      typeinfo->destruct(data);
+      delete[] data;
+    };
+  }
+
+  void processResponse(const dap::Deserializer* d) {
+    dap::integer requestSeq = 0;
+    if (!d->field("request_seq", &requestSeq)) {
+      handlers.error("Response missing int 'request_seq' field");
+      return;
+    }
+
+    const dap::TypeInfo* typeinfo;
+    GenericResponseHandler handler;
+    std::tie(typeinfo, handler) = handlers.response(requestSeq);
+    if (!typeinfo) {
+      handlers.error("Unknown response with sequence %d", requestSeq);
+      return;
+    }
+
+    dap::boolean success = false;
+    if (!d->field("success", &success)) {
+      handlers.error("Response missing int 'success' field");
+      return;
+    }
+
+    if (success) {
+      auto data = std::unique_ptr<uint8_t[]>(new uint8_t[typeinfo->size()]);
+      typeinfo->construct(data.get());
+
+      // "body" field in Response is an optional field.
+      d->field("body", [&](const dap::Deserializer* d) {
+        return typeinfo->deserialize(d, data.get());
+      });
+
+      handler(data.get(), nullptr);
+      typeinfo->destruct(data.get());
+    } else {
+      std::string message;
+      if (!d->field("message", &message)) {
+        handlers.error("Failed to deserialize message");
+        return;
+      }
+      auto error = dap::Error("%s", message.c_str());
+      handler(nullptr, &error);
+    }
+  }
+
+  bool send(const std::string& s) {
+    std::unique_lock<std::mutex> lock(sendMutex);
+    if (!writer.isOpen()) {
+      handlers.error("Send failed as the writer is closed");
+      return false;
+    }
+    return writer.write(s);
+  }
+
+  std::atomic<bool> isBound = {false};
+  std::atomic<bool> isProcessingMessages = {false};
+  dap::ContentReader reader;
+  dap::ContentWriter writer;
+
+  std::atomic<bool> shutdown = {false};
+  EventHandlers handlers;
+  std::thread recvThread;
+  std::thread dispatchThread;
+  dap::Chan<Payload> inbox;
+  std::atomic<uint32_t> nextSeq = {1};
+  std::mutex sendMutex;
+};
+
+}  // anonymous namespace
+
+namespace dap {
+
+Error::Error(const std::string& message) : message(message) {}
+
+Error::Error(const char* msg, ...) {
+  char buf[2048];
+  va_list vararg;
+  va_start(vararg, msg);
+  vsnprintf(buf, sizeof(buf), msg, vararg);
+  va_end(vararg);
+  message = buf;
+}
+
+Session::~Session() = default;
+
+std::unique_ptr<Session> Session::create() {
+  return std::unique_ptr<Session>(new Impl());
+}
+
+}  // namespace dap
diff --git a/Utilities/cmcppdap/src/session_test.cpp b/Utilities/cmcppdap/src/session_test.cpp
new file mode 100644
index 0000000..361152e
--- /dev/null
+++ b/Utilities/cmcppdap/src/session_test.cpp
@@ -0,0 +1,625 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dap/session.h"
+#include "dap/io.h"
+#include "dap/protocol.h"
+
+#include "chan.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include <array>
+#include <atomic>
+#include <condition_variable>
+#include <mutex>
+#include <thread>
+
+namespace dap {
+
+struct TestResponse : public Response {
+  boolean b;
+  integer i;
+  number n;
+  array<integer> a;
+  object o;
+  string s;
+  optional<integer> o1;
+  optional<integer> o2;
+};
+
+DAP_STRUCT_TYPEINFO(TestResponse,
+                    "test-response",
+                    DAP_FIELD(b, "res_b"),
+                    DAP_FIELD(i, "res_i"),
+                    DAP_FIELD(n, "res_n"),
+                    DAP_FIELD(a, "res_a"),
+                    DAP_FIELD(o, "res_o"),
+                    DAP_FIELD(s, "res_s"),
+                    DAP_FIELD(o1, "res_o1"),
+                    DAP_FIELD(o2, "res_o2"));
+
+struct TestRequest : public Request {
+  using Response = TestResponse;
+
+  boolean b;
+  integer i;
+  number n;
+  array<integer> a;
+  object o;
+  string s;
+  optional<integer> o1;
+  optional<integer> o2;
+};
+
+DAP_STRUCT_TYPEINFO(TestRequest,
+                    "test-request",
+                    DAP_FIELD(b, "req_b"),
+                    DAP_FIELD(i, "req_i"),
+                    DAP_FIELD(n, "req_n"),
+                    DAP_FIELD(a, "req_a"),
+                    DAP_FIELD(o, "req_o"),
+                    DAP_FIELD(s, "req_s"),
+                    DAP_FIELD(o1, "req_o1"),
+                    DAP_FIELD(o2, "req_o2"));
+
+struct TestEvent : public Event {
+  boolean b;
+  integer i;
+  number n;
+  array<integer> a;
+  object o;
+  string s;
+  optional<integer> o1;
+  optional<integer> o2;
+};
+
+DAP_STRUCT_TYPEINFO(TestEvent,
+                    "test-event",
+                    DAP_FIELD(b, "evt_b"),
+                    DAP_FIELD(i, "evt_i"),
+                    DAP_FIELD(n, "evt_n"),
+                    DAP_FIELD(a, "evt_a"),
+                    DAP_FIELD(o, "evt_o"),
+                    DAP_FIELD(s, "evt_s"),
+                    DAP_FIELD(o1, "evt_o1"),
+                    DAP_FIELD(o2, "evt_o2"));
+
+};  // namespace dap
+
+namespace {
+
+dap::TestRequest createRequest() {
+  dap::TestRequest request;
+  request.b = false;
+  request.i = 72;
+  request.n = 9.87;
+  request.a = {2, 5, 7, 8};
+  request.o = {
+      std::make_pair("a", dap::integer(1)),
+      std::make_pair("b", dap::number(2)),
+      std::make_pair("c", dap::string("3")),
+  };
+  request.s = "request";
+  request.o2 = 42;
+  return request;
+}
+
+dap::TestResponse createResponse() {
+  dap::TestResponse response;
+  response.b = true;
+  response.i = 99;
+  response.n = 123.456;
+  response.a = {5, 4, 3, 2, 1};
+  response.o = {
+      std::make_pair("one", dap::integer(1)),
+      std::make_pair("two", dap::number(2)),
+      std::make_pair("three", dap::string("3")),
+  };
+  response.s = "ROGER";
+  response.o1 = 50;
+  return response;
+}
+
+dap::TestEvent createEvent() {
+  dap::TestEvent event;
+  event.b = false;
+  event.i = 72;
+  event.n = 9.87;
+  event.a = {2, 5, 7, 8};
+  event.o = {
+      std::make_pair("a", dap::integer(1)),
+      std::make_pair("b", dap::number(2)),
+      std::make_pair("c", dap::string("3")),
+  };
+  event.s = "event";
+  event.o2 = 42;
+  return event;
+}
+
+}  // anonymous namespace
+
+class SessionTest : public testing::Test {
+ public:
+  void bind() {
+    auto client2server = dap::pipe();
+    auto server2client = dap::pipe();
+    client->bind(server2client, client2server);
+    server->bind(client2server, server2client);
+  }
+
+  std::unique_ptr<dap::Session> client = dap::Session::create();
+  std::unique_ptr<dap::Session> server = dap::Session::create();
+};
+
+TEST_F(SessionTest, Request) {
+  dap::TestRequest received;
+  server->registerHandler([&](const dap::TestRequest& req) {
+    received = req;
+    return createResponse();
+  });
+
+  bind();
+
+  auto request = createRequest();
+  client->send(request).get();
+
+  // Check request was received correctly.
+  ASSERT_EQ(received.b, request.b);
+  ASSERT_EQ(received.i, request.i);
+  ASSERT_EQ(received.n, request.n);
+  ASSERT_EQ(received.a, request.a);
+  ASSERT_EQ(received.o.size(), 3U);
+  ASSERT_EQ(received.o["a"].get<dap::integer>(),
+            request.o["a"].get<dap::integer>());
+  ASSERT_EQ(received.o["b"].get<dap::number>(),
+            request.o["b"].get<dap::number>());
+  ASSERT_EQ(received.o["c"].get<dap::string>(),
+            request.o["c"].get<dap::string>());
+  ASSERT_EQ(received.s, request.s);
+  ASSERT_EQ(received.o1, request.o1);
+  ASSERT_EQ(received.o2, request.o2);
+}
+
+TEST_F(SessionTest, RequestResponseSuccess) {
+  server->registerHandler(
+      [&](const dap::TestRequest&) { return createResponse(); });
+
+  bind();
+
+  auto request = createRequest();
+  auto response = client->send(request);
+
+  auto got = response.get();
+
+  // Check response was received correctly.
+  ASSERT_EQ(got.error, false);
+  ASSERT_EQ(got.response.b, dap::boolean(true));
+  ASSERT_EQ(got.response.i, dap::integer(99));
+  ASSERT_EQ(got.response.n, dap::number(123.456));
+  ASSERT_EQ(got.response.a, dap::array<dap::integer>({5, 4, 3, 2, 1}));
+  ASSERT_EQ(got.response.o.size(), 3U);
+  ASSERT_EQ(got.response.o["one"].get<dap::integer>(), dap::integer(1));
+  ASSERT_EQ(got.response.o["two"].get<dap::number>(), dap::number(2));
+  ASSERT_EQ(got.response.o["three"].get<dap::string>(), dap::string("3"));
+  ASSERT_EQ(got.response.s, "ROGER");
+  ASSERT_EQ(got.response.o1, dap::optional<dap::integer>(50));
+  ASSERT_FALSE(got.response.o2.has_value());
+}
+
+TEST_F(SessionTest, BreakPointRequestResponseSuccess) {
+  server->registerHandler([&](const dap::SetBreakpointsRequest&) {
+    dap::SetBreakpointsResponse response;
+    dap::Breakpoint bp;
+    bp.line = 2;
+    response.breakpoints.emplace_back(std::move(bp));
+    return response;
+  });
+
+  bind();
+
+  auto got = client->send(dap::SetBreakpointsRequest{}).get();
+
+  // Check response was received correctly.
+  ASSERT_EQ(got.error, false);
+  ASSERT_EQ(got.response.breakpoints.size(), 1U);
+}
+
+TEST_F(SessionTest, RequestResponseOrError) {
+  server->registerHandler(
+      [&](const dap::TestRequest&) -> dap::ResponseOrError<dap::TestResponse> {
+        return dap::Error("Oh noes!");
+      });
+
+  bind();
+
+  auto response = client->send(createRequest());
+
+  auto got = response.get();
+
+  // Check response was received correctly.
+  ASSERT_EQ(got.error, true);
+  ASSERT_EQ(got.error.message, "Oh noes!");
+}
+
+TEST_F(SessionTest, RequestResponseError) {
+  server->registerHandler(
+      [&](const dap::TestRequest&) { return dap::Error("Oh noes!"); });
+
+  bind();
+
+  auto response = client->send(createRequest());
+
+  auto got = response.get();
+
+  // Check response was received correctly.
+  ASSERT_EQ(got.error, true);
+  ASSERT_EQ(got.error.message, "Oh noes!");
+}
+
+TEST_F(SessionTest, RequestCallbackResponse) {
+  using ResponseCallback = std::function<void(dap::SetBreakpointsResponse)>;
+
+  server->registerHandler(
+      [&](const dap::SetBreakpointsRequest&, const ResponseCallback& callback) {
+        dap::SetBreakpointsResponse response;
+        dap::Breakpoint bp;
+        bp.line = 2;
+        response.breakpoints.emplace_back(std::move(bp));
+        callback(response);
+      });
+
+  bind();
+
+  auto got = client->send(dap::SetBreakpointsRequest{}).get();
+
+  // Check response was received correctly.
+  ASSERT_EQ(got.error, false);
+  ASSERT_EQ(got.response.breakpoints.size(), 1U);
+}
+
+TEST_F(SessionTest, RequestCallbackResponseOrError) {
+  using ResponseCallback =
+      std::function<void(dap::ResponseOrError<dap::SetBreakpointsResponse>)>;
+
+  server->registerHandler(
+      [&](const dap::SetBreakpointsRequest&, const ResponseCallback& callback) {
+        dap::SetBreakpointsResponse response;
+        dap::Breakpoint bp;
+        bp.line = 2;
+        response.breakpoints.emplace_back(std::move(bp));
+        callback(response);
+      });
+
+  bind();
+
+  auto got = client->send(dap::SetBreakpointsRequest{}).get();
+
+  // Check response was received correctly.
+  ASSERT_EQ(got.error, false);
+  ASSERT_EQ(got.response.breakpoints.size(), 1U);
+}
+
+TEST_F(SessionTest, RequestCallbackError) {
+  using ResponseCallback =
+      std::function<void(dap::ResponseOrError<dap::SetBreakpointsResponse>)>;
+
+  server->registerHandler(
+      [&](const dap::SetBreakpointsRequest&, const ResponseCallback& callback) {
+        callback(dap::Error("Oh noes!"));
+      });
+
+  bind();
+
+  auto got = client->send(dap::SetBreakpointsRequest{}).get();
+
+  // Check response was received correctly.
+  ASSERT_EQ(got.error, true);
+  ASSERT_EQ(got.error.message, "Oh noes!");
+}
+
+TEST_F(SessionTest, RequestCallbackSuccessAfterReturn) {
+  using ResponseCallback =
+      std::function<void(dap::ResponseOrError<dap::SetBreakpointsResponse>)>;
+
+  ResponseCallback callback;
+  std::mutex mutex;
+  std::condition_variable cv;
+
+  server->registerHandler(
+      [&](const dap::SetBreakpointsRequest&, const ResponseCallback& cb) {
+        std::unique_lock<std::mutex> lock(mutex);
+        callback = cb;
+        cv.notify_all();
+      });
+
+  bind();
+
+  auto future = client->send(dap::SetBreakpointsRequest{});
+
+  {
+    dap::SetBreakpointsResponse response;
+    dap::Breakpoint bp;
+    bp.line = 2;
+    response.breakpoints.emplace_back(std::move(bp));
+
+    // Wait for the handler to be called.
+    std::unique_lock<std::mutex> lock(mutex);
+    cv.wait(lock, [&] { return static_cast<bool>(callback); });
+
+    // Issue the callback
+    callback(response);
+  }
+
+  auto got = future.get();
+
+  // Check response was received correctly.
+  ASSERT_EQ(got.error, false);
+  ASSERT_EQ(got.response.breakpoints.size(), 1U);
+}
+
+TEST_F(SessionTest, RequestCallbackErrorAfterReturn) {
+  using ResponseCallback =
+      std::function<void(dap::ResponseOrError<dap::SetBreakpointsResponse>)>;
+
+  ResponseCallback callback;
+  std::mutex mutex;
+  std::condition_variable cv;
+
+  server->registerHandler(
+      [&](const dap::SetBreakpointsRequest&, const ResponseCallback& cb) {
+        std::unique_lock<std::mutex> lock(mutex);
+        callback = cb;
+        cv.notify_all();
+      });
+
+  bind();
+
+  auto future = client->send(dap::SetBreakpointsRequest{});
+
+  {
+    // Wait for the handler to be called.
+    std::unique_lock<std::mutex> lock(mutex);
+    cv.wait(lock, [&] { return static_cast<bool>(callback); });
+
+    // Issue the callback
+    callback(dap::Error("Oh noes!"));
+  }
+
+  auto got = future.get();
+
+  // Check response was received correctly.
+  ASSERT_EQ(got.error, true);
+  ASSERT_EQ(got.error.message, "Oh noes!");
+}
+
+TEST_F(SessionTest, ResponseSentHandlerSuccess) {
+  const auto response = createResponse();
+
+  dap::Chan<dap::ResponseOrError<dap::TestResponse>> chan;
+  server->registerHandler([&](const dap::TestRequest&) { return response; });
+  server->registerSentHandler(
+      [&](const dap::ResponseOrError<dap::TestResponse> r) { chan.put(r); });
+
+  bind();
+
+  client->send(createRequest());
+
+  auto got = chan.take().value();
+  ASSERT_EQ(got.error, false);
+  ASSERT_EQ(got.response.b, dap::boolean(true));
+  ASSERT_EQ(got.response.i, dap::integer(99));
+  ASSERT_EQ(got.response.n, dap::number(123.456));
+  ASSERT_EQ(got.response.a, dap::array<dap::integer>({5, 4, 3, 2, 1}));
+  ASSERT_EQ(got.response.o.size(), 3U);
+  ASSERT_EQ(got.response.o["one"].get<dap::integer>(), dap::integer(1));
+  ASSERT_EQ(got.response.o["two"].get<dap::number>(), dap::number(2));
+  ASSERT_EQ(got.response.o["three"].get<dap::string>(), dap::string("3"));
+  ASSERT_EQ(got.response.s, "ROGER");
+  ASSERT_EQ(got.response.o1, dap::optional<dap::integer>(50));
+  ASSERT_FALSE(got.response.o2.has_value());
+}
+
+TEST_F(SessionTest, ResponseSentHandlerError) {
+  dap::Chan<dap::ResponseOrError<dap::TestResponse>> chan;
+  server->registerHandler(
+      [&](const dap::TestRequest&) { return dap::Error("Oh noes!"); });
+  server->registerSentHandler(
+      [&](const dap::ResponseOrError<dap::TestResponse> r) { chan.put(r); });
+
+  bind();
+
+  client->send(createRequest());
+
+  auto got = chan.take().value();
+  ASSERT_EQ(got.error, true);
+  ASSERT_EQ(got.error.message, "Oh noes!");
+}
+
+TEST_F(SessionTest, Event) {
+  dap::Chan<dap::TestEvent> received;
+  server->registerHandler([&](const dap::TestEvent& e) { received.put(e); });
+
+  bind();
+
+  auto event = createEvent();
+  client->send(event);
+
+  // Check event was received correctly.
+  auto got = received.take().value();
+
+  ASSERT_EQ(got.b, event.b);
+  ASSERT_EQ(got.i, event.i);
+  ASSERT_EQ(got.n, event.n);
+  ASSERT_EQ(got.a, event.a);
+  ASSERT_EQ(got.o.size(), 3U);
+  ASSERT_EQ(got.o["a"].get<dap::integer>(), event.o["a"].get<dap::integer>());
+  ASSERT_EQ(got.o["b"].get<dap::number>(), event.o["b"].get<dap::number>());
+  ASSERT_EQ(got.o["c"].get<dap::string>(), event.o["c"].get<dap::string>());
+  ASSERT_EQ(got.s, event.s);
+  ASSERT_EQ(got.o1, event.o1);
+  ASSERT_EQ(got.o2, event.o2);
+}
+
+TEST_F(SessionTest, RegisterHandlerFunction) {
+  struct S {
+    static dap::TestResponse requestA(const dap::TestRequest&) { return {}; }
+    static dap::Error requestB(const dap::TestRequest&) { return {}; }
+    static dap::ResponseOrError<dap::TestResponse> requestC(
+        const dap::TestRequest&) {
+      return dap::Error();
+    }
+    static void event(const dap::TestEvent&) {}
+    static void sent(const dap::ResponseOrError<dap::TestResponse>&) {}
+  };
+  client->registerHandler(&S::requestA);
+  client->registerHandler(&S::requestB);
+  client->registerHandler(&S::requestC);
+  client->registerHandler(&S::event);
+  client->registerSentHandler(&S::sent);
+}
+
+TEST_F(SessionTest, SendRequestNoBind) {
+  bool errored = false;
+  client->onError([&](const std::string&) { errored = true; });
+  auto res = client->send(createRequest()).get();
+  ASSERT_TRUE(errored);
+  ASSERT_TRUE(res.error);
+}
+
+TEST_F(SessionTest, SendEventNoBind) {
+  bool errored = false;
+  client->onError([&](const std::string&) { errored = true; });
+  client->send(createEvent());
+  ASSERT_TRUE(errored);
+}
+
+TEST_F(SessionTest, SingleThread) {
+  server->registerHandler(
+      [&](const dap::TestRequest&) { return createResponse(); });
+
+  // Explicitly connect and process request on this test thread instead of
+  // calling bind() which inturn starts processing messages immediately on a new
+  // thread.
+  auto client2server = dap::pipe();
+  auto server2client = dap::pipe();
+  client->connect(server2client, client2server);
+  server->connect(client2server, server2client);
+
+  auto request = createRequest();
+  auto response = client->send(request);
+
+  // Process request and response on this thread
+  if (auto payload = server->getPayload()) {
+    payload();
+  }
+  if (auto payload = client->getPayload()) {
+    payload();
+  }
+
+  auto got = response.get();
+  // Check response was received correctly.
+  ASSERT_EQ(got.error, false);
+  ASSERT_EQ(got.response.b, dap::boolean(true));
+  ASSERT_EQ(got.response.i, dap::integer(99));
+  ASSERT_EQ(got.response.n, dap::number(123.456));
+  ASSERT_EQ(got.response.a, dap::array<dap::integer>({5, 4, 3, 2, 1}));
+  ASSERT_EQ(got.response.o.size(), 3U);
+  ASSERT_EQ(got.response.o["one"].get<dap::integer>(), dap::integer(1));
+  ASSERT_EQ(got.response.o["two"].get<dap::number>(), dap::number(2));
+  ASSERT_EQ(got.response.o["three"].get<dap::string>(), dap::string("3"));
+  ASSERT_EQ(got.response.s, "ROGER");
+  ASSERT_EQ(got.response.o1, dap::optional<dap::integer>(50));
+  ASSERT_FALSE(got.response.o2.has_value());
+}
+
+TEST_F(SessionTest, Concurrency) {
+  std::atomic<int> numEventsHandled = {0};
+  std::atomic<bool> done = {false};
+
+  server->registerHandler(
+      [](const dap::TestRequest&) { return dap::TestResponse(); });
+
+  server->registerHandler([&](const dap::TestEvent&) {
+    if (numEventsHandled++ > 10000) {
+      done = true;
+    }
+  });
+
+  bind();
+
+  constexpr int numThreads = 32;
+  std::array<std::thread, numThreads> threads;
+
+  for (int i = 0; i < numThreads; i++) {
+    threads[i] = std::thread([&] {
+      while (!done) {
+        client->send(createEvent());
+        client->send(createRequest());
+      }
+    });
+  }
+
+  for (int i = 0; i < numThreads; i++) {
+    threads[i].join();
+  }
+
+  client.reset();
+  server.reset();
+}
+
+TEST_F(SessionTest, OnClientClosed) {
+  std::mutex mutex;
+  std::condition_variable cv;
+  bool clientClosed = false;
+
+  auto client2server = dap::pipe();
+  auto server2client = dap::pipe();
+
+  client->bind(server2client, client2server);
+  server->bind(client2server, server2client, [&] {
+    std::unique_lock<std::mutex> lock(mutex);
+    clientClosed = true;
+    cv.notify_all();
+  });
+
+  client.reset();
+
+  // Wait for the client closed handler to be called.
+  std::unique_lock<std::mutex> lock(mutex);
+  cv.wait(lock, [&] { return static_cast<bool>(clientClosed); });
+}
+
+TEST_F(SessionTest, OnServerClosed) {
+  std::mutex mutex;
+  std::condition_variable cv;
+  bool serverClosed = false;
+
+  auto client2server = dap::pipe();
+  auto server2client = dap::pipe();
+
+  client->bind(server2client, client2server, [&] {
+    std::unique_lock<std::mutex> lock(mutex);
+    serverClosed = true;
+    cv.notify_all();
+  });
+  server->bind(client2server, server2client);
+
+  server.reset();
+
+  // Wait for the client closed handler to be called.
+  std::unique_lock<std::mutex> lock(mutex);
+  cv.wait(lock, [&] { return static_cast<bool>(serverClosed); });
+}
diff --git a/Utilities/cmcppdap/src/socket.cpp b/Utilities/cmcppdap/src/socket.cpp
new file mode 100644
index 0000000..1211310
--- /dev/null
+++ b/Utilities/cmcppdap/src/socket.cpp
@@ -0,0 +1,333 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "socket.h"
+
+#include "rwmutex.h"
+
+#if defined(_WIN32)
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#else
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#endif
+
+#if defined(_WIN32)
+#include <atomic>
+namespace {
+std::atomic<int> wsaInitCount = {0};
+}  // anonymous namespace
+#else
+#include <fcntl.h>
+#include <unistd.h>
+namespace {
+using SOCKET = int;
+}  // anonymous namespace
+#endif
+
+namespace {
+constexpr SOCKET InvalidSocket = static_cast<SOCKET>(-1);
+void init() {
+#if defined(_WIN32)
+  if (wsaInitCount++ == 0) {
+    WSADATA winsockData;
+    (void)WSAStartup(MAKEWORD(2, 2), &winsockData);
+  }
+#endif
+}
+
+void term() {
+#if defined(_WIN32)
+  if (--wsaInitCount == 0) {
+    WSACleanup();
+  }
+#endif
+}
+
+bool setBlocking(SOCKET s, bool blocking) {
+#if defined(_WIN32)
+  u_long mode = blocking ? 0 : 1;
+  return ioctlsocket(s, FIONBIO, &mode) == NO_ERROR;
+#else
+  auto arg = fcntl(s, F_GETFL, nullptr);
+  if (arg < 0) {
+    return false;
+  }
+  arg = blocking ? (arg & ~O_NONBLOCK) : (arg | O_NONBLOCK);
+  return fcntl(s, F_SETFL, arg) >= 0;
+#endif
+}
+
+bool errored(SOCKET s) {
+  if (s == InvalidSocket) {
+    return true;
+  }
+  char error = 0;
+  socklen_t len = sizeof(error);
+  getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len);
+  return error != 0;
+}
+
+}  // anonymous namespace
+
+class dap::Socket::Shared : public dap::ReaderWriter {
+ public:
+  static std::shared_ptr<Shared> create(const char* address, const char* port) {
+    init();
+
+    addrinfo hints = {};
+    hints.ai_family = AF_INET;
+    hints.ai_socktype = SOCK_STREAM;
+    hints.ai_protocol = IPPROTO_TCP;
+    hints.ai_flags = AI_PASSIVE;
+
+    addrinfo* info = nullptr;
+    getaddrinfo(address, port, &hints, &info);
+
+    if (info) {
+      auto socket =
+          ::socket(info->ai_family, info->ai_socktype, info->ai_protocol);
+      auto out = std::make_shared<Shared>(info, socket);
+      out->setOptions();
+      return out;
+    }
+
+    freeaddrinfo(info);
+    term();
+    return nullptr;
+  }
+
+  Shared(SOCKET socket) : info(nullptr), s(socket) {}
+  Shared(addrinfo* info, SOCKET socket) : info(info), s(socket) {}
+
+  ~Shared() {
+    freeaddrinfo(info);
+    close();
+    term();
+  }
+
+  template <typename FUNCTION>
+  void lock(FUNCTION&& f) {
+    RLock l(mutex);
+    f(s, info);
+  }
+
+  void setOptions() {
+    RLock l(mutex);
+    if (s == InvalidSocket) {
+      return;
+    }
+
+    int enable = 1;
+
+#if !defined(_WIN32)
+    // Prevent sockets lingering after process termination, causing
+    // reconnection issues on the same port.
+    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&enable, sizeof(enable));
+
+    struct {
+      int l_onoff;  /* linger active */
+      int l_linger; /* how many seconds to linger for */
+    } linger = {false, 0};
+    setsockopt(s, SOL_SOCKET, SO_LINGER, (char*)&linger, sizeof(linger));
+#endif  // !defined(_WIN32)
+
+    // Enable TCP_NODELAY.
+    // DAP usually consists of small packet requests, with small packet
+    // responses. When there are many frequent, blocking requests made,
+    // Nagle's algorithm can dramatically limit the request->response rates.
+    setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&enable, sizeof(enable));
+  }
+
+  // dap::ReaderWriter compliance
+  bool isOpen() {
+    {
+      RLock l(mutex);
+      if ((s != InvalidSocket) && !errored(s)) {
+        return true;
+      }
+    }
+    WLock lock(mutex);
+    s = InvalidSocket;
+    return false;
+  }
+
+  void close() {
+    {
+      RLock l(mutex);
+      if (s != InvalidSocket) {
+#if defined(_WIN32)
+        closesocket(s);
+#elif __APPLE__
+        // ::shutdown() *should* be sufficient to unblock ::accept(), but
+        // apparently on macos it can return ENOTCONN and ::accept() continues
+        // to block indefinitely.
+        // Note: There is a race here. Calling ::close() frees the socket ID,
+        // which may be reused before `s` is assigned InvalidSocket.
+        ::shutdown(s, SHUT_RDWR);
+        ::close(s);
+#else
+        // ::shutdown() to unblock ::accept(). We'll actually close the socket
+        // under lock below.
+        ::shutdown(s, SHUT_RDWR);
+#endif
+      }
+    }
+
+    WLock l(mutex);
+    if (s != InvalidSocket) {
+#if !defined(_WIN32) && !defined(__APPLE__)
+      ::close(s);
+#endif
+      s = InvalidSocket;
+    }
+  }
+
+  size_t read(void* buffer, size_t bytes) {
+    RLock lock(mutex);
+    if (s == InvalidSocket) {
+      return 0;
+    }
+    auto len =
+        recv(s, reinterpret_cast<char*>(buffer), static_cast<int>(bytes), 0);
+    return (len < 0) ? 0 : len;
+  }
+
+  bool write(const void* buffer, size_t bytes) {
+    RLock lock(mutex);
+    if (s == InvalidSocket) {
+      return false;
+    }
+    if (bytes == 0) {
+      return true;
+    }
+    return ::send(s, reinterpret_cast<const char*>(buffer),
+                  static_cast<int>(bytes), 0) > 0;
+  }
+
+ private:
+  addrinfo* const info;
+  SOCKET s = InvalidSocket;
+  RWMutex mutex;
+};
+
+namespace dap {
+
+Socket::Socket(const char* address, const char* port)
+    : shared(Shared::create(address, port)) {
+  if (shared) {
+    shared->lock([&](SOCKET socket, const addrinfo* info) {
+      if (bind(socket, info->ai_addr, (int)info->ai_addrlen) != 0) {
+        shared.reset();
+        return;
+      }
+
+      if (listen(socket, 0) != 0) {
+        shared.reset();
+        return;
+      }
+    });
+  }
+}
+
+std::shared_ptr<ReaderWriter> Socket::accept() const {
+  std::shared_ptr<Shared> out;
+  if (shared) {
+    shared->lock([&](SOCKET socket, const addrinfo*) {
+      if (socket != InvalidSocket && !errored(socket)) {
+        init();
+        auto accepted = ::accept(socket, 0, 0);
+        if (accepted != InvalidSocket) {
+          out = std::make_shared<Shared>(accepted);
+          out->setOptions();
+        }
+      }
+    });
+  }
+  return out;
+}
+
+bool Socket::isOpen() const {
+  if (shared) {
+    return shared->isOpen();
+  }
+  return false;
+}
+
+void Socket::close() const {
+  if (shared) {
+    shared->close();
+  }
+}
+
+std::shared_ptr<ReaderWriter> Socket::connect(const char* address,
+                                              const char* port,
+                                              uint32_t timeoutMillis) {
+  auto shared = Shared::create(address, port);
+  if (!shared) {
+    return nullptr;
+  }
+
+  std::shared_ptr<ReaderWriter> out;
+  shared->lock([&](SOCKET socket, const addrinfo* info) {
+    if (socket == InvalidSocket) {
+      return;
+    }
+
+    if (timeoutMillis == 0) {
+      if (::connect(socket, info->ai_addr, (int)info->ai_addrlen) == 0) {
+        out = shared;
+      }
+      return;
+    }
+
+    if (!setBlocking(socket, false)) {
+      return;
+    }
+
+    auto res = ::connect(socket, info->ai_addr, (int)info->ai_addrlen);
+    if (res == 0) {
+      if (setBlocking(socket, true)) {
+        out = shared;
+      }
+    } else {
+      const auto microseconds = timeoutMillis * 1000;
+
+      fd_set fdset;
+      FD_ZERO(&fdset);
+      FD_SET(socket, &fdset);
+
+      timeval tv;
+      tv.tv_sec = microseconds / 1000000;
+      tv.tv_usec = microseconds - static_cast<uint32_t>(tv.tv_sec * 1000000);
+      res = select(static_cast<int>(socket + 1), nullptr, &fdset, nullptr, &tv);
+      if (res > 0 && !errored(socket) && setBlocking(socket, true)) {
+        out = shared;
+      }
+    }
+  });
+
+  if (!out) {
+    return nullptr;
+  }
+
+  return out->isOpen() ? out : nullptr;
+}
+
+}  // namespace dap
diff --git a/Utilities/cmcppdap/src/socket.h b/Utilities/cmcppdap/src/socket.h
new file mode 100644
index 0000000..ec5b0df
--- /dev/null
+++ b/Utilities/cmcppdap/src/socket.h
@@ -0,0 +1,47 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef dap_socket_h
+#define dap_socket_h
+
+#include "dap/io.h"
+
+#include <atomic>
+#include <memory>
+
+namespace dap {
+
+class Socket {
+ public:
+  class Shared;
+
+  // connect() connects to the given TCP address and port.
+  // If timeoutMillis is non-zero and no connection was made before
+  // timeoutMillis milliseconds, then nullptr is returned.
+  static std::shared_ptr<ReaderWriter> connect(const char* address,
+                                               const char* port,
+                                               uint32_t timeoutMillis);
+
+  Socket(const char* address, const char* port);
+  bool isOpen() const;
+  std::shared_ptr<ReaderWriter> accept() const;
+  void close() const;
+
+ private:
+  std::shared_ptr<Shared> shared;
+};
+
+}  // namespace dap
+
+#endif  // dap_socket_h
diff --git a/Utilities/cmcppdap/src/socket_test.cpp b/Utilities/cmcppdap/src/socket_test.cpp
new file mode 100644
index 0000000..186fd9a
--- /dev/null
+++ b/Utilities/cmcppdap/src/socket_test.cpp
@@ -0,0 +1,104 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "socket.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include <chrono>
+#include <thread>
+#include <vector>
+
+// Basic socket send & receive test
+TEST(Socket, SendRecv) {
+  const char* port = "19021";
+
+  auto server = dap::Socket("localhost", port);
+
+  auto client = dap::Socket::connect("localhost", port, 0);
+  ASSERT_TRUE(client != nullptr);
+
+  const std::string expect = "Hello World!";
+  std::string read;
+
+  auto thread = std::thread([&] {
+    auto conn = server.accept();
+    ASSERT_TRUE(conn != nullptr);
+    char c;
+    while (conn->read(&c, 1) != 0) {
+      read += c;
+    }
+  });
+
+  ASSERT_TRUE(client->write(expect.data(), expect.size()));
+
+  client->close();
+  thread.join();
+
+  ASSERT_EQ(read, expect);
+}
+
+// See https://github.com/google/cppdap/issues/37
+TEST(Socket, CloseOnDifferentThread) {
+  const char* port = "19021";
+
+  auto server = dap::Socket("localhost", port);
+
+  auto client = dap::Socket::connect("localhost", port, 0);
+  ASSERT_TRUE(client != nullptr);
+
+  auto conn = server.accept();
+
+  auto thread = std::thread([&] {
+    // Closing client on different thread should unblock client->read().
+    client->close();
+  });
+
+  char c;
+  while (client->read(&c, 1) != 0) {
+  }
+
+  thread.join();
+}
+
+TEST(Socket, ConnectTimeout) {
+  const char* port = "19021";
+  const int timeoutMillis = 200;
+  const int maxAttempts = 1024;
+
+  using namespace std::chrono;
+
+  auto server = dap::Socket("localhost", port);
+
+  std::vector<std::shared_ptr<dap::ReaderWriter>> connections;
+
+  for (int i = 0; i < maxAttempts; i++) {
+    auto start = system_clock::now();
+    auto connection = dap::Socket::connect("localhost", port, timeoutMillis);
+    auto end = system_clock::now();
+
+    if (!connection) {
+      auto timeTakenMillis = duration_cast<milliseconds>(end - start).count();
+      ASSERT_GE(timeTakenMillis + 20,  // +20ms for a bit of timing wiggle room
+                timeoutMillis);
+      return;
+    }
+
+    // Keep hold of the connections to saturate any incoming socket buffers.
+    connections.emplace_back(std::move(connection));
+  }
+
+  FAIL() << "Failed to test timeout after " << maxAttempts << " attempts";
+}
diff --git a/Utilities/cmcppdap/src/string_buffer.h b/Utilities/cmcppdap/src/string_buffer.h
new file mode 100644
index 0000000..cdd6c41
--- /dev/null
+++ b/Utilities/cmcppdap/src/string_buffer.h
@@ -0,0 +1,85 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef dap_string_buffer_h
+#define dap_string_buffer_h
+
+#include "dap/io.h"
+
+#include <algorithm>  // std::min
+#include <cstring>    // memcpy
+#include <memory>     // std::unique_ptr
+#include <string>
+
+namespace dap {
+
+class StringBuffer : public virtual Reader, public virtual Writer {
+ public:
+  static inline std::unique_ptr<StringBuffer> create();
+
+  inline bool write(const std::string& s);
+  inline std::string string() const;
+
+  // Reader / Writer compilance
+  inline bool isOpen() override;
+  inline void close() override;
+  inline size_t read(void* buffer, size_t bytes) override;
+  inline bool write(const void* buffer, size_t bytes) override;
+
+ private:
+  std::string str;
+  bool closed = false;
+};
+
+bool StringBuffer::isOpen() {
+  return !closed;
+}
+void StringBuffer::close() {
+  closed = true;
+}
+
+std::unique_ptr<StringBuffer> StringBuffer::create() {
+  return std::unique_ptr<StringBuffer>(new StringBuffer());
+}
+
+bool StringBuffer::write(const std::string& s) {
+  return write(s.data(), s.size());
+}
+
+std::string StringBuffer::string() const {
+  return str;
+}
+
+size_t StringBuffer::read(void* buffer, size_t bytes) {
+  if (closed || bytes == 0 || str.size() == 0) {
+    return 0;
+  }
+  auto len = std::min(bytes, str.size());
+  memcpy(buffer, str.data(), len);
+  str = std::string(str.begin() + len, str.end());
+  return len;
+}
+
+bool StringBuffer::write(const void* buffer, size_t bytes) {
+  if (closed) {
+    return false;
+  }
+  auto chars = reinterpret_cast<const char*>(buffer);
+  str.append(chars, chars + bytes);
+  return true;
+}
+
+}  // namespace dap
+
+#endif  // dap_string_buffer_h
diff --git a/Utilities/cmcppdap/src/traits_test.cpp b/Utilities/cmcppdap/src/traits_test.cpp
new file mode 100644
index 0000000..aafca04
--- /dev/null
+++ b/Utilities/cmcppdap/src/traits_test.cpp
@@ -0,0 +1,387 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dap/traits.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace dap {
+namespace traits {
+
+namespace {
+struct S {};
+struct E : S {};
+void F1(S) {}
+void F3(int, S, float) {}
+void E1(E) {}
+void E3(int, E, float) {}
+}  // namespace
+
+TEST(ParameterType, Function) {
+  F1({});        // Avoid unused method warning
+  F3(0, {}, 0);  // Avoid unused method warning
+  static_assert(std::is_same<ParameterType<decltype(&F1), 0>, S>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(&F3), 0>, int>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(&F3), 1>, S>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(&F3), 2>, float>::value,
+                "");
+}
+
+TEST(ParameterType, Method) {
+  class C {
+   public:
+    void F1(S) {}
+    void F3(int, S, float) {}
+  };
+  C().F1({});        // Avoid unused method warning
+  C().F3(0, {}, 0);  // Avoid unused method warning
+  static_assert(std::is_same<ParameterType<decltype(&C::F1), 0>, S>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(&C::F3), 0>, int>::value,
+                "");
+  static_assert(std::is_same<ParameterType<decltype(&C::F3), 1>, S>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(&C::F3), 2>, float>::value,
+                "");
+}
+
+TEST(ParameterType, ConstMethod) {
+  class C {
+   public:
+    void F1(S) const {}
+    void F3(int, S, float) const {}
+  };
+  C().F1({});        // Avoid unused method warning
+  C().F3(0, {}, 0);  // Avoid unused method warning
+  static_assert(std::is_same<ParameterType<decltype(&C::F1), 0>, S>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(&C::F3), 0>, int>::value,
+                "");
+  static_assert(std::is_same<ParameterType<decltype(&C::F3), 1>, S>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(&C::F3), 2>, float>::value,
+                "");
+}
+
+TEST(ParameterType, StaticMethod) {
+  class C {
+   public:
+    static void F1(S) {}
+    static void F3(int, S, float) {}
+  };
+  C::F1({});        // Avoid unused method warning
+  C::F3(0, {}, 0);  // Avoid unused method warning
+  static_assert(std::is_same<ParameterType<decltype(&C::F1), 0>, S>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(&C::F3), 0>, int>::value,
+                "");
+  static_assert(std::is_same<ParameterType<decltype(&C::F3), 1>, S>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(&C::F3), 2>, float>::value,
+                "");
+}
+
+TEST(ParameterType, FunctionLike) {
+  using F1 = std::function<void(S)>;
+  using F3 = std::function<void(int, S, float)>;
+  static_assert(std::is_same<ParameterType<F1, 0>, S>::value, "");
+  static_assert(std::is_same<ParameterType<F3, 0>, int>::value, "");
+  static_assert(std::is_same<ParameterType<F3, 1>, S>::value, "");
+  static_assert(std::is_same<ParameterType<F3, 2>, float>::value, "");
+}
+
+TEST(ParameterType, Lambda) {
+  auto l1 = [](S) {};
+  auto l3 = [](int, S, float) {};
+  static_assert(std::is_same<ParameterType<decltype(l1), 0>, S>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(l3), 0>, int>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(l3), 1>, S>::value, "");
+  static_assert(std::is_same<ParameterType<decltype(l3), 2>, float>::value, "");
+}
+
+TEST(HasSignature, Function) {
+  F1({});        // Avoid unused method warning
+  F3(0, {}, 0);  // Avoid unused method warning
+  static_assert(HasSignature<decltype(&F1), decltype(&F1)>::value, "");
+  static_assert(HasSignature<decltype(&F3), decltype(&F3)>::value, "");
+  static_assert(HasSignature<decltype(&F3), decltype(&F3)>::value, "");
+  static_assert(HasSignature<decltype(&F3), decltype(&F3)>::value, "");
+  static_assert(!HasSignature<decltype(&F1), decltype(&F3)>::value, "");
+  static_assert(!HasSignature<decltype(&F3), decltype(&F1)>::value, "");
+  static_assert(!HasSignature<decltype(&F3), decltype(&F1)>::value, "");
+  static_assert(!HasSignature<decltype(&F3), decltype(&F1)>::value, "");
+}
+
+TEST(HasSignature, Method) {
+  class C {
+   public:
+    void F1(S) {}
+    void F3(int, S, float) {}
+  };
+  C().F1({});        // Avoid unused method warning
+  C().F3(0, {}, 0);  // Avoid unused method warning
+
+  static_assert(HasSignature<decltype(&C::F1), decltype(&F1)>::value, "");
+  static_assert(HasSignature<decltype(&C::F3), decltype(&F3)>::value, "");
+  static_assert(HasSignature<decltype(&C::F3), decltype(&F3)>::value, "");
+  static_assert(HasSignature<decltype(&C::F3), decltype(&F3)>::value, "");
+  static_assert(!HasSignature<decltype(&C::F1), decltype(&F3)>::value, "");
+  static_assert(!HasSignature<decltype(&C::F3), decltype(&F1)>::value, "");
+  static_assert(!HasSignature<decltype(&C::F3), decltype(&F1)>::value, "");
+  static_assert(!HasSignature<decltype(&C::F3), decltype(&F1)>::value, "");
+}
+
+TEST(HasSignature, ConstMethod) {
+  class C {
+   public:
+    void F1(S) const {}
+    void F3(int, S, float) const {}
+  };
+  C().F1({});        // Avoid unused method warning
+  C().F3(0, {}, 0);  // Avoid unused method warning
+
+  static_assert(HasSignature<decltype(&C::F1), decltype(&F1)>::value, "");
+  static_assert(HasSignature<decltype(&C::F3), decltype(&F3)>::value, "");
+  static_assert(HasSignature<decltype(&C::F3), decltype(&F3)>::value, "");
+  static_assert(HasSignature<decltype(&C::F3), decltype(&F3)>::value, "");
+  static_assert(!HasSignature<decltype(&C::F1), decltype(&F3)>::value, "");
+  static_assert(!HasSignature<decltype(&C::F3), decltype(&F1)>::value, "");
+  static_assert(!HasSignature<decltype(&C::F3), decltype(&F1)>::value, "");
+  static_assert(!HasSignature<decltype(&C::F3), decltype(&F1)>::value, "");
+}
+
+TEST(HasSignature, StaticMethod) {
+  class C {
+   public:
+    static void F1(S) {}
+    static void F3(int, S, float) {}
+  };
+  C::F1({});        // Avoid unused method warning
+  C::F3(0, {}, 0);  // Avoid unused method warning
+
+  static_assert(HasSignature<decltype(&C::F1), decltype(&F1)>::value, "");
+  static_assert(HasSignature<decltype(&C::F3), decltype(&F3)>::value, "");
+  static_assert(HasSignature<decltype(&C::F3), decltype(&F3)>::value, "");
+  static_assert(HasSignature<decltype(&C::F3), decltype(&F3)>::value, "");
+  static_assert(!HasSignature<decltype(&C::F1), decltype(&F3)>::value, "");
+  static_assert(!HasSignature<decltype(&C::F3), decltype(&F1)>::value, "");
+  static_assert(!HasSignature<decltype(&C::F3), decltype(&F1)>::value, "");
+  static_assert(!HasSignature<decltype(&C::F3), decltype(&F1)>::value, "");
+}
+
+TEST(HasSignature, FunctionLike) {
+  using f1 = std::function<void(S)>;
+  using f3 = std::function<void(int, S, float)>;
+  static_assert(HasSignature<f1, decltype(&F1)>::value, "");
+  static_assert(HasSignature<f3, decltype(&F3)>::value, "");
+  static_assert(HasSignature<f3, decltype(&F3)>::value, "");
+  static_assert(HasSignature<f3, decltype(&F3)>::value, "");
+  static_assert(!HasSignature<f1, decltype(&F3)>::value, "");
+  static_assert(!HasSignature<f3, decltype(&F1)>::value, "");
+  static_assert(!HasSignature<f3, decltype(&F1)>::value, "");
+  static_assert(!HasSignature<f3, decltype(&F1)>::value, "");
+}
+
+TEST(HasSignature, Lambda) {
+  auto l1 = [](S) {};
+  auto l3 = [](int, S, float) {};
+  static_assert(HasSignature<decltype(l1), decltype(&F1)>::value, "");
+  static_assert(HasSignature<decltype(l3), decltype(&F3)>::value, "");
+  static_assert(HasSignature<decltype(l3), decltype(&F3)>::value, "");
+  static_assert(HasSignature<decltype(l3), decltype(&F3)>::value, "");
+  static_assert(!HasSignature<decltype(l1), decltype(&F3)>::value, "");
+  static_assert(!HasSignature<decltype(l3), decltype(&F1)>::value, "");
+  static_assert(!HasSignature<decltype(l3), decltype(&F1)>::value, "");
+  static_assert(!HasSignature<decltype(l3), decltype(&F1)>::value, "");
+}
+
+////
+
+TEST(CompatibleWith, Function) {
+  F1({});        // Avoid unused method warning
+  F3(0, {}, 0);  // Avoid unused method warning
+  E1({});        // Avoid unused method warning
+  E3(0, {}, 0);  // Avoid unused method warning
+  static_assert(CompatibleWith<decltype(&F1), decltype(&F1)>::value, "");
+  static_assert(CompatibleWith<decltype(&F3), decltype(&F3)>::value, "");
+  static_assert(CompatibleWith<decltype(&F3), decltype(&F3)>::value, "");
+  static_assert(CompatibleWith<decltype(&F3), decltype(&F3)>::value, "");
+
+  static_assert(!CompatibleWith<decltype(&F1), decltype(&F3)>::value, "");
+  static_assert(!CompatibleWith<decltype(&F3), decltype(&F1)>::value, "");
+  static_assert(!CompatibleWith<decltype(&F3), decltype(&F1)>::value, "");
+  static_assert(!CompatibleWith<decltype(&F3), decltype(&F1)>::value, "");
+
+  static_assert(CompatibleWith<decltype(&E1), decltype(&F1)>::value, "");
+  static_assert(CompatibleWith<decltype(&E3), decltype(&F3)>::value, "");
+  static_assert(CompatibleWith<decltype(&E3), decltype(&F3)>::value, "");
+  static_assert(CompatibleWith<decltype(&E3), decltype(&F3)>::value, "");
+
+  static_assert(!CompatibleWith<decltype(&F1), decltype(&E1)>::value, "");
+  static_assert(!CompatibleWith<decltype(&F3), decltype(&E3)>::value, "");
+  static_assert(!CompatibleWith<decltype(&F3), decltype(&E3)>::value, "");
+  static_assert(!CompatibleWith<decltype(&F3), decltype(&E3)>::value, "");
+}
+
+TEST(CompatibleWith, Method) {
+  class C {
+   public:
+    void F1(S) {}
+    void F3(int, S, float) {}
+    void E1(E) {}
+    void E3(int, E, float) {}
+  };
+  C().F1({});        // Avoid unused method warning
+  C().F3(0, {}, 0);  // Avoid unused method warning
+  C().E1({});        // Avoid unused method warning
+  C().E3(0, {}, 0);  // Avoid unused method warning
+
+  static_assert(CompatibleWith<decltype(&C::F1), decltype(&F1)>::value, "");
+  static_assert(CompatibleWith<decltype(&C::F3), decltype(&F3)>::value, "");
+  static_assert(CompatibleWith<decltype(&C::F3), decltype(&F3)>::value, "");
+  static_assert(CompatibleWith<decltype(&C::F3), decltype(&F3)>::value, "");
+
+  static_assert(!CompatibleWith<decltype(&C::F1), decltype(&F3)>::value, "");
+  static_assert(!CompatibleWith<decltype(&C::F3), decltype(&F1)>::value, "");
+  static_assert(!CompatibleWith<decltype(&C::F3), decltype(&F1)>::value, "");
+  static_assert(!CompatibleWith<decltype(&C::F3), decltype(&F1)>::value, "");
+
+  static_assert(CompatibleWith<decltype(&C::E1), decltype(&C::F1)>::value, "");
+  static_assert(CompatibleWith<decltype(&C::E3), decltype(&C::F3)>::value, "");
+  static_assert(CompatibleWith<decltype(&C::E3), decltype(&C::F3)>::value, "");
+  static_assert(CompatibleWith<decltype(&C::E3), decltype(&C::F3)>::value, "");
+
+  static_assert(!CompatibleWith<decltype(&C::F1), decltype(&C::E1)>::value, "");
+  static_assert(!CompatibleWith<decltype(&C::F3), decltype(&C::E3)>::value, "");
+  static_assert(!CompatibleWith<decltype(&C::F3), decltype(&C::E3)>::value, "");
+  static_assert(!CompatibleWith<decltype(&C::F3), decltype(&C::E3)>::value, "");
+}
+
+TEST(CompatibleWith, ConstMethod) {
+  class C {
+   public:
+    void F1(S) const {}
+    void F3(int, S, float) const {}
+    void E1(E) const {}
+    void E3(int, E, float) const {}
+  };
+  C().F1({});        // Avoid unused method warning
+  C().F3(0, {}, 0);  // Avoid unused method warning
+  C().E1({});        // Avoid unused method warning
+  C().E3(0, {}, 0);  // Avoid unused method warning
+
+  static_assert(CompatibleWith<decltype(&C::F1), decltype(&F1)>::value, "");
+  static_assert(CompatibleWith<decltype(&C::F3), decltype(&F3)>::value, "");
+  static_assert(CompatibleWith<decltype(&C::F3), decltype(&F3)>::value, "");
+  static_assert(CompatibleWith<decltype(&C::F3), decltype(&F3)>::value, "");
+
+  static_assert(!CompatibleWith<decltype(&C::F1), decltype(&F3)>::value, "");
+  static_assert(!CompatibleWith<decltype(&C::F3), decltype(&F1)>::value, "");
+  static_assert(!CompatibleWith<decltype(&C::F3), decltype(&F1)>::value, "");
+  static_assert(!CompatibleWith<decltype(&C::F3), decltype(&F1)>::value, "");
+
+  static_assert(CompatibleWith<decltype(&C::E1), decltype(&C::F1)>::value, "");
+  static_assert(CompatibleWith<decltype(&C::E3), decltype(&C::F3)>::value, "");
+  static_assert(CompatibleWith<decltype(&C::E3), decltype(&C::F3)>::value, "");
+  static_assert(CompatibleWith<decltype(&C::E3), decltype(&C::F3)>::value, "");
+
+  static_assert(!CompatibleWith<decltype(&C::F1), decltype(&C::E1)>::value, "");
+  static_assert(!CompatibleWith<decltype(&C::F3), decltype(&C::E3)>::value, "");
+  static_assert(!CompatibleWith<decltype(&C::F3), decltype(&C::E3)>::value, "");
+  static_assert(!CompatibleWith<decltype(&C::F3), decltype(&C::E3)>::value, "");
+}
+
+TEST(CompatibleWith, StaticMethod) {
+  class C {
+   public:
+    static void F1(S) {}
+    static void F3(int, S, float) {}
+    static void E1(E) {}
+    static void E3(int, E, float) {}
+  };
+  C::F1({});        // Avoid unused method warning
+  C::F3(0, {}, 0);  // Avoid unused method warning
+  C::E1({});        // Avoid unused method warning
+  C::E3(0, {}, 0);  // Avoid unused method warning
+
+  static_assert(CompatibleWith<decltype(&C::F1), decltype(&F1)>::value, "");
+  static_assert(CompatibleWith<decltype(&C::F3), decltype(&F3)>::value, "");
+  static_assert(CompatibleWith<decltype(&C::F3), decltype(&F3)>::value, "");
+  static_assert(CompatibleWith<decltype(&C::F3), decltype(&F3)>::value, "");
+
+  static_assert(!CompatibleWith<decltype(&C::F1), decltype(&F3)>::value, "");
+  static_assert(!CompatibleWith<decltype(&C::F3), decltype(&F1)>::value, "");
+  static_assert(!CompatibleWith<decltype(&C::F3), decltype(&F1)>::value, "");
+  static_assert(!CompatibleWith<decltype(&C::F3), decltype(&F1)>::value, "");
+
+  static_assert(CompatibleWith<decltype(&C::E1), decltype(&C::F1)>::value, "");
+  static_assert(CompatibleWith<decltype(&C::E3), decltype(&C::F3)>::value, "");
+  static_assert(CompatibleWith<decltype(&C::E3), decltype(&C::F3)>::value, "");
+  static_assert(CompatibleWith<decltype(&C::E3), decltype(&C::F3)>::value, "");
+
+  static_assert(!CompatibleWith<decltype(&C::F1), decltype(&C::E1)>::value, "");
+  static_assert(!CompatibleWith<decltype(&C::F3), decltype(&C::E3)>::value, "");
+  static_assert(!CompatibleWith<decltype(&C::F3), decltype(&C::E3)>::value, "");
+  static_assert(!CompatibleWith<decltype(&C::F3), decltype(&C::E3)>::value, "");
+}
+
+TEST(CompatibleWith, FunctionLike) {
+  using f1 = std::function<void(S)>;
+  using f3 = std::function<void(int, S, float)>;
+  using e1 = std::function<void(E)>;
+  using e3 = std::function<void(int, E, float)>;
+  static_assert(CompatibleWith<f1, decltype(&F1)>::value, "");
+  static_assert(CompatibleWith<f3, decltype(&F3)>::value, "");
+  static_assert(CompatibleWith<f3, decltype(&F3)>::value, "");
+  static_assert(CompatibleWith<f3, decltype(&F3)>::value, "");
+
+  static_assert(!CompatibleWith<f1, decltype(&F3)>::value, "");
+  static_assert(!CompatibleWith<f3, decltype(&F1)>::value, "");
+  static_assert(!CompatibleWith<f3, decltype(&F1)>::value, "");
+  static_assert(!CompatibleWith<f3, decltype(&F1)>::value, "");
+
+  static_assert(CompatibleWith<e1, f1>::value, "");
+  static_assert(CompatibleWith<e3, f3>::value, "");
+  static_assert(CompatibleWith<e3, f3>::value, "");
+  static_assert(CompatibleWith<e3, f3>::value, "");
+
+  static_assert(!CompatibleWith<f1, e1>::value, "");
+  static_assert(!CompatibleWith<f3, e3>::value, "");
+  static_assert(!CompatibleWith<f3, e3>::value, "");
+  static_assert(!CompatibleWith<f3, e3>::value, "");
+}
+
+TEST(CompatibleWith, Lambda) {
+  auto f1 = [](S) {};
+  auto f3 = [](int, S, float) {};
+  auto e1 = [](E) {};
+  auto e3 = [](int, E, float) {};
+  static_assert(CompatibleWith<decltype(f1), decltype(&F1)>::value, "");
+  static_assert(CompatibleWith<decltype(f3), decltype(&F3)>::value, "");
+  static_assert(CompatibleWith<decltype(f3), decltype(&F3)>::value, "");
+  static_assert(CompatibleWith<decltype(f3), decltype(&F3)>::value, "");
+
+  static_assert(!CompatibleWith<decltype(f1), decltype(&F3)>::value, "");
+  static_assert(!CompatibleWith<decltype(f3), decltype(&F1)>::value, "");
+  static_assert(!CompatibleWith<decltype(f3), decltype(&F1)>::value, "");
+  static_assert(!CompatibleWith<decltype(f3), decltype(&F1)>::value, "");
+
+  static_assert(CompatibleWith<decltype(e1), decltype(f1)>::value, "");
+  static_assert(CompatibleWith<decltype(e3), decltype(f3)>::value, "");
+  static_assert(CompatibleWith<decltype(e3), decltype(f3)>::value, "");
+  static_assert(CompatibleWith<decltype(e3), decltype(f3)>::value, "");
+
+  static_assert(!CompatibleWith<decltype(f1), decltype(e1)>::value, "");
+  static_assert(!CompatibleWith<decltype(f3), decltype(e3)>::value, "");
+  static_assert(!CompatibleWith<decltype(f3), decltype(e3)>::value, "");
+  static_assert(!CompatibleWith<decltype(f3), decltype(e3)>::value, "");
+}
+
+}  // namespace traits
+}  // namespace dap
diff --git a/Utilities/cmcppdap/src/typeinfo.cpp b/Utilities/cmcppdap/src/typeinfo.cpp
new file mode 100644
index 0000000..dda481f
--- /dev/null
+++ b/Utilities/cmcppdap/src/typeinfo.cpp
@@ -0,0 +1,21 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dap/typeinfo.h"
+
+namespace dap {
+
+TypeInfo::~TypeInfo() = default;
+
+}  // namespace dap
diff --git a/Utilities/cmcppdap/src/typeinfo_test.cpp b/Utilities/cmcppdap/src/typeinfo_test.cpp
new file mode 100644
index 0000000..23d5793
--- /dev/null
+++ b/Utilities/cmcppdap/src/typeinfo_test.cpp
@@ -0,0 +1,65 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dap/typeof.h"
+#include "dap/types.h"
+#include "json_serializer.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace dap {
+
+struct BaseStruct {
+  dap::integer i;
+  dap::number n;
+};
+
+DAP_STRUCT_TYPEINFO(BaseStruct,
+                    "BaseStruct",
+                    DAP_FIELD(i, "i"),
+                    DAP_FIELD(n, "n"));
+
+struct DerivedStruct : public BaseStruct {
+  dap::string s;
+  dap::boolean b;
+};
+
+DAP_STRUCT_TYPEINFO_EXT(DerivedStruct,
+                        BaseStruct,
+                        "DerivedStruct",
+                        DAP_FIELD(s, "s"),
+                        DAP_FIELD(b, "b"));
+
+}  // namespace dap
+
+TEST(TypeInfo, Derived) {
+  dap::DerivedStruct in;
+  in.s = "hello world";
+  in.b = true;
+  in.i = 42;
+  in.n = 3.14;
+
+  dap::json::Serializer s;
+  ASSERT_TRUE(s.serialize(in));
+
+  dap::DerivedStruct out;
+  dap::json::Deserializer d(s.dump());
+  ASSERT_TRUE(d.deserialize(&out)) << "Failed to deserialize\n" << s.dump();
+
+  ASSERT_EQ(out.s, "hello world");
+  ASSERT_EQ(out.b, true);
+  ASSERT_EQ(out.i, 42);
+  ASSERT_EQ(out.n, 3.14);
+}
diff --git a/Utilities/cmcppdap/src/typeof.cpp b/Utilities/cmcppdap/src/typeof.cpp
new file mode 100644
index 0000000..055421c
--- /dev/null
+++ b/Utilities/cmcppdap/src/typeof.cpp
@@ -0,0 +1,144 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dap/typeof.h"
+
+#include <atomic>
+#include <memory>
+#include <vector>
+
+namespace {
+
+// TypeInfos owns all the dap::TypeInfo instances.
+struct TypeInfos {
+  // get() returns the TypeInfos singleton pointer.
+  // TypeInfos is constructed with an internal reference count of 1.
+  static TypeInfos* get();
+
+  // reference() increments the TypeInfos reference count.
+  inline void reference() {
+    assert(refcount.load() > 0);
+    refcount++;
+  }
+
+  // release() decrements the TypeInfos reference count.
+  // If the reference count becomes 0, then the TypeInfos is destructed.
+  inline void release() {
+    if (--refcount == 0) {
+      this->~TypeInfos();
+    }
+  }
+
+  struct NullTI : public dap::TypeInfo {
+    using null = dap::null;
+    inline std::string name() const override { return "null"; }
+    inline size_t size() const override { return sizeof(null); }
+    inline size_t alignment() const override { return alignof(null); }
+    inline void construct(void* ptr) const override { new (ptr) null(); }
+    inline void copyConstruct(void* dst, const void* src) const override {
+      new (dst) null(*reinterpret_cast<const null*>(src));
+    }
+    inline void destruct(void* ptr) const override {
+      reinterpret_cast<null*>(ptr)->~null();
+    }
+    inline bool deserialize(const dap::Deserializer*, void*) const override {
+      return true;
+    }
+    inline bool serialize(dap::Serializer*, const void*) const override {
+      return true;
+    }
+  };
+
+  dap::BasicTypeInfo<dap::boolean> boolean = {"boolean"};
+  dap::BasicTypeInfo<dap::string> string = {"string"};
+  dap::BasicTypeInfo<dap::integer> integer = {"integer"};
+  dap::BasicTypeInfo<dap::number> number = {"number"};
+  dap::BasicTypeInfo<dap::object> object = {"object"};
+  dap::BasicTypeInfo<dap::any> any = {"any"};
+  NullTI null;
+  std::vector<std::unique_ptr<dap::TypeInfo>> types;
+
+ private:
+  TypeInfos() = default;
+  ~TypeInfos() = default;
+  std::atomic<uint64_t> refcount = {1};
+};
+
+// aligned_storage() is a replacement for std::aligned_storage that isn't busted
+// on older versions of MSVC.
+template <size_t SIZE, size_t ALIGNMENT>
+struct aligned_storage {
+  struct alignas(ALIGNMENT) type {
+    unsigned char data[SIZE];
+  };
+};
+
+TypeInfos* TypeInfos::get() {
+  static aligned_storage<sizeof(TypeInfos), alignof(TypeInfos)>::type memory;
+
+  struct Instance {
+    TypeInfos* ptr() { return reinterpret_cast<TypeInfos*>(memory.data); }
+    Instance() { new (ptr()) TypeInfos(); }
+    ~Instance() { ptr()->release(); }
+  };
+
+  static Instance instance;
+  return instance.ptr();
+}
+
+}  // namespace
+
+namespace dap {
+
+const TypeInfo* TypeOf<boolean>::type() {
+  return &TypeInfos::get()->boolean;
+}
+
+const TypeInfo* TypeOf<string>::type() {
+  return &TypeInfos::get()->string;
+}
+
+const TypeInfo* TypeOf<integer>::type() {
+  return &TypeInfos::get()->integer;
+}
+
+const TypeInfo* TypeOf<number>::type() {
+  return &TypeInfos::get()->number;
+}
+
+const TypeInfo* TypeOf<object>::type() {
+  return &TypeInfos::get()->object;
+}
+
+const TypeInfo* TypeOf<any>::type() {
+  return &TypeInfos::get()->any;
+}
+
+const TypeInfo* TypeOf<null>::type() {
+  return &TypeInfos::get()->null;
+}
+
+void TypeInfo::deleteOnExit(TypeInfo* ti) {
+  TypeInfos::get()->types.emplace_back(std::unique_ptr<TypeInfo>(ti));
+}
+
+void initialize() {
+  TypeInfos::get()->reference();
+}
+
+void terminate() {
+  TypeInfos::get()->release();
+}
+
+}  // namespace dap
diff --git a/Utilities/cmcppdap/src/variant_test.cpp b/Utilities/cmcppdap/src/variant_test.cpp
new file mode 100644
index 0000000..5a1f4eb
--- /dev/null
+++ b/Utilities/cmcppdap/src/variant_test.cpp
@@ -0,0 +1,94 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dap/variant.h"
+#include "dap/typeof.h"
+#include "dap/types.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace dap {
+
+struct VariantTestObject {
+  dap::integer i;
+  dap::number n;
+};
+
+DAP_STRUCT_TYPEINFO(VariantTestObject,
+                    "VariantTestObject",
+                    DAP_FIELD(i, "i"),
+                    DAP_FIELD(n, "n"));
+
+}  // namespace dap
+
+TEST(Variant, EmptyConstruct) {
+  dap::variant<dap::integer, dap::boolean, dap::VariantTestObject> variant;
+  ASSERT_TRUE(variant.is<dap::integer>());
+  ASSERT_FALSE(variant.is<dap::boolean>());
+  ASSERT_FALSE(variant.is<dap::VariantTestObject>());
+}
+
+TEST(Variant, Boolean) {
+  dap::variant<dap::integer, dap::boolean, dap::VariantTestObject> variant(
+      dap::boolean(true));
+  ASSERT_TRUE(variant.is<dap::boolean>());
+  ASSERT_EQ(variant.get<dap::boolean>(), dap::boolean(true));
+}
+
+TEST(Variant, Integer) {
+  dap::variant<dap::integer, dap::boolean, dap::VariantTestObject> variant(
+      dap::integer(10));
+  ASSERT_TRUE(variant.is<dap::integer>());
+  ASSERT_EQ(variant.get<dap::integer>(), dap::integer(10));
+}
+
+TEST(Variant, TestObject) {
+  dap::variant<dap::integer, dap::boolean, dap::VariantTestObject> variant(
+      dap::VariantTestObject{5, 3.0});
+  ASSERT_TRUE(variant.is<dap::VariantTestObject>());
+  ASSERT_EQ(variant.get<dap::VariantTestObject>().i, 5);
+  ASSERT_EQ(variant.get<dap::VariantTestObject>().n, 3.0);
+}
+
+TEST(Variant, Assign) {
+  dap::variant<dap::integer, dap::boolean, dap::VariantTestObject> variant(
+      dap::integer(10));
+  variant = dap::integer(10);
+  ASSERT_TRUE(variant.is<dap::integer>());
+  ASSERT_FALSE(variant.is<dap::boolean>());
+  ASSERT_FALSE(variant.is<dap::VariantTestObject>());
+  ASSERT_EQ(variant.get<dap::integer>(), dap::integer(10));
+  variant = dap::boolean(true);
+  ASSERT_FALSE(variant.is<dap::integer>());
+  ASSERT_TRUE(variant.is<dap::boolean>());
+  ASSERT_FALSE(variant.is<dap::VariantTestObject>());
+  ASSERT_EQ(variant.get<dap::boolean>(), dap::boolean(true));
+  variant = dap::VariantTestObject{5, 3.0};
+  ASSERT_FALSE(variant.is<dap::integer>());
+  ASSERT_FALSE(variant.is<dap::boolean>());
+  ASSERT_TRUE(variant.is<dap::VariantTestObject>());
+  ASSERT_EQ(variant.get<dap::VariantTestObject>().i, 5);
+  ASSERT_EQ(variant.get<dap::VariantTestObject>().n, 3.0);
+}
+
+TEST(Variant, Accepts) {
+  using variant =
+      dap::variant<dap::integer, dap::boolean, dap::VariantTestObject>;
+  ASSERT_TRUE(variant::accepts<dap::integer>());
+  ASSERT_TRUE(variant::accepts<dap::boolean>());
+  ASSERT_TRUE(variant::accepts<dap::VariantTestObject>());
+  ASSERT_FALSE(variant::accepts<dap::number>());
+  ASSERT_FALSE(variant::accepts<dap::string>());
+}
diff --git a/Utilities/cmcurl/CMake/CMakeConfigurableFile.in b/Utilities/cmcurl/CMake/CMakeConfigurableFile.in
index b93e753..a3d2bc4 100644
--- a/Utilities/cmcurl/CMake/CMakeConfigurableFile.in
+++ b/Utilities/cmcurl/CMake/CMakeConfigurableFile.in
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/CMake/CurlSymbolHiding.cmake b/Utilities/cmcurl/CMake/CurlSymbolHiding.cmake
index 75215a1..142e919 100644
--- a/Utilities/cmcurl/CMake/CurlSymbolHiding.cmake
+++ b/Utilities/cmcurl/CMake/CurlSymbolHiding.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/CMake/CurlTests.c b/Utilities/cmcurl/CMake/CurlTests.c
index 6a9fdea..3dbba3c 100644
--- a/Utilities/cmcurl/CMake/CurlTests.c
+++ b/Utilities/cmcurl/CMake/CurlTests.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/CMake/FindBearSSL.cmake b/Utilities/cmcurl/CMake/FindBearSSL.cmake
index 88d5e87..56a064e 100644
--- a/Utilities/cmcurl/CMake/FindBearSSL.cmake
+++ b/Utilities/cmcurl/CMake/FindBearSSL.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/CMake/FindBrotli.cmake b/Utilities/cmcurl/CMake/FindBrotli.cmake
index 833e181..11ab7f8 100644
--- a/Utilities/cmcurl/CMake/FindBrotli.cmake
+++ b/Utilities/cmcurl/CMake/FindBrotli.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
@@ -28,7 +28,7 @@
 find_library(BROTLICOMMON_LIBRARY NAMES brotlicommon)
 find_library(BROTLIDEC_LIBRARY NAMES brotlidec)
 
-find_package_handle_standard_args(BROTLI
+find_package_handle_standard_args(Brotli
     FOUND_VAR
       BROTLI_FOUND
     REQUIRED_VARS
@@ -36,7 +36,7 @@
       BROTLICOMMON_LIBRARY
       BROTLI_INCLUDE_DIR
     FAIL_MESSAGE
-      "Could NOT find BROTLI"
+      "Could NOT find Brotli"
 )
 
 set(BROTLI_INCLUDE_DIRS ${BROTLI_INCLUDE_DIR})
diff --git a/Utilities/cmcurl/CMake/FindCARES.cmake b/Utilities/cmcurl/CMake/FindCARES.cmake
index 99cf31d..fa75891 100644
--- a/Utilities/cmcurl/CMake/FindCARES.cmake
+++ b/Utilities/cmcurl/CMake/FindCARES.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/CMake/FindGSS.cmake b/Utilities/cmcurl/CMake/FindGSS.cmake
index ec2bd57..b244e61 100644
--- a/Utilities/cmcurl/CMake/FindGSS.cmake
+++ b/Utilities/cmcurl/CMake/FindGSS.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
@@ -181,14 +181,14 @@
         set(GSS_FLAVOUR "MIT")
       else()
         # prevent compiling the header - just check if we can include it
-        set(CMAKE_REQUIRED_DEFINITIONS "${CMAKE_REQUIRED_DEFINITIONS} -D__ROKEN_H__")
+        list(APPEND CMAKE_REQUIRED_DEFINITIONS -D__ROKEN_H__)
         check_include_file( "roken.h" _GSS_HAVE_ROKEN_H)
 
         check_include_file( "heimdal/roken.h" _GSS_HAVE_HEIMDAL_ROKEN_H)
         if(_GSS_HAVE_ROKEN_H OR _GSS_HAVE_HEIMDAL_ROKEN_H)
           set(GSS_FLAVOUR "Heimdal")
         endif()
-        set(CMAKE_REQUIRED_DEFINITIONS "")
+        list(REMOVE_ITEM CMAKE_REQUIRED_DEFINITIONS -D__ROKEN_H__)
       endif()
     else()
       # I'm not convinced if this is the right way but this is what autotools do at the moment
diff --git a/Utilities/cmcurl/CMake/FindLibPSL.cmake b/Utilities/cmcurl/CMake/FindLibPSL.cmake
index 66abdd7..e3bd68d 100644
--- a/Utilities/cmcurl/CMake/FindLibPSL.cmake
+++ b/Utilities/cmcurl/CMake/FindLibPSL.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/CMake/FindLibSSH2.cmake b/Utilities/cmcurl/CMake/FindLibSSH2.cmake
index 0ec7f7e..a0c251a 100644
--- a/Utilities/cmcurl/CMake/FindLibSSH2.cmake
+++ b/Utilities/cmcurl/CMake/FindLibSSH2.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/CMake/FindMSH3.cmake b/Utilities/cmcurl/CMake/FindMSH3.cmake
index 96477e2..7d9c6b6 100644
--- a/Utilities/cmcurl/CMake/FindMSH3.cmake
+++ b/Utilities/cmcurl/CMake/FindMSH3.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/CMake/FindMbedTLS.cmake b/Utilities/cmcurl/CMake/FindMbedTLS.cmake
index fcd6717..814bd97 100644
--- a/Utilities/cmcurl/CMake/FindMbedTLS.cmake
+++ b/Utilities/cmcurl/CMake/FindMbedTLS.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/CMake/FindNGHTTP2.cmake b/Utilities/cmcurl/CMake/FindNGHTTP2.cmake
index 6d70c4a..3957646 100644
--- a/Utilities/cmcurl/CMake/FindNGHTTP2.cmake
+++ b/Utilities/cmcurl/CMake/FindNGHTTP2.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/CMake/FindNGHTTP3.cmake b/Utilities/cmcurl/CMake/FindNGHTTP3.cmake
index 8d8ebc1..9b13e6c 100644
--- a/Utilities/cmcurl/CMake/FindNGHTTP3.cmake
+++ b/Utilities/cmcurl/CMake/FindNGHTTP3.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/CMake/FindNGTCP2.cmake b/Utilities/cmcurl/CMake/FindNGTCP2.cmake
index 61e54c2..ff0d49e 100644
--- a/Utilities/cmcurl/CMake/FindNGTCP2.cmake
+++ b/Utilities/cmcurl/CMake/FindNGTCP2.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
@@ -71,7 +71,7 @@
 if(NGTCP2_FIND_COMPONENTS)
   set(NGTCP2_CRYPTO_BACKEND "")
   foreach(component IN LISTS NGTCP2_FIND_COMPONENTS)
-    if(component MATCHES "^(BoringSSL|OpenSSL|GnuTLS)")
+    if(component MATCHES "^(BoringSSL|OpenSSL|wolfSSL|GnuTLS)")
       if(NGTCP2_CRYPTO_BACKEND)
         message(FATAL_ERROR "NGTCP2: Only one crypto library can be selected")
       endif()
diff --git a/Utilities/cmcurl/CMake/FindNSS.cmake b/Utilities/cmcurl/CMake/FindNSS.cmake
index 6742dda..ccddf42 100644
--- a/Utilities/cmcurl/CMake/FindNSS.cmake
+++ b/Utilities/cmcurl/CMake/FindNSS.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/CMake/FindQUICHE.cmake b/Utilities/cmcurl/CMake/FindQUICHE.cmake
index fc47027..0488463 100644
--- a/Utilities/cmcurl/CMake/FindQUICHE.cmake
+++ b/Utilities/cmcurl/CMake/FindQUICHE.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/CMake/FindWolfSSL.cmake b/Utilities/cmcurl/CMake/FindWolfSSL.cmake
index 986f01e..d67c0eb 100644
--- a/Utilities/cmcurl/CMake/FindWolfSSL.cmake
+++ b/Utilities/cmcurl/CMake/FindWolfSSL.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/CMake/FindZstd.cmake b/Utilities/cmcurl/CMake/FindZstd.cmake
index 2d65404..973e6ad 100644
--- a/Utilities/cmcurl/CMake/FindZstd.cmake
+++ b/Utilities/cmcurl/CMake/FindZstd.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/CMake/Macros.cmake b/Utilities/cmcurl/CMake/Macros.cmake
index 4d7380e..e12bf30 100644
--- a/Utilities/cmcurl/CMake/Macros.cmake
+++ b/Utilities/cmcurl/CMake/Macros.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/CMake/OtherTests.cmake b/Utilities/cmcurl/CMake/OtherTests.cmake
index ed8d28a..fa1e458 100644
--- a/Utilities/cmcurl/CMake/OtherTests.cmake
+++ b/Utilities/cmcurl/CMake/OtherTests.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/CMake/Platforms/WindowsCache.cmake b/Utilities/cmcurl/CMake/Platforms/WindowsCache.cmake
index 3cb4ffe..3771237 100644
--- a/Utilities/cmcurl/CMake/Platforms/WindowsCache.cmake
+++ b/Utilities/cmcurl/CMake/Platforms/WindowsCache.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
@@ -29,7 +29,6 @@
 
     set(HAVE_ARPA_INET_H 0)
     set(HAVE_FCNTL_H 1)
-    set(HAVE_INTTYPES_H 0)
     set(HAVE_IO_H 1)
     set(HAVE_NETDB_H 0)
     set(HAVE_NETINET_IN_H 0)
@@ -37,7 +36,6 @@
     set(HAVE_PWD_H 0)
     set(HAVE_SETJMP_H 1)
     set(HAVE_SIGNAL_H 1)
-    set(HAVE_STDINT_H 0)
     set(HAVE_STDLIB_H 1)
     set(HAVE_STRINGS_H 0)
     set(HAVE_STRING_H 1)
diff --git a/Utilities/cmcurl/CMake/Utilities.cmake b/Utilities/cmcurl/CMake/Utilities.cmake
index 78bfd6f..9ff38e3 100644
--- a/Utilities/cmcurl/CMake/Utilities.cmake
+++ b/Utilities/cmcurl/CMake/Utilities.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/CMake/cmake_uninstall.cmake.in b/Utilities/cmcurl/CMake/cmake_uninstall.cmake.in
index 2549d41..3e0742d 100644
--- a/Utilities/cmcurl/CMake/cmake_uninstall.cmake.in
+++ b/Utilities/cmcurl/CMake/cmake_uninstall.cmake.in
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/CMake/curl-config.cmake.in b/Utilities/cmcurl/CMake/curl-config.cmake.in
index 496a92d..dbe4ed2 100644
--- a/Utilities/cmcurl/CMake/curl-config.cmake.in
+++ b/Utilities/cmcurl/CMake/curl-config.cmake.in
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/CMakeLists.txt b/Utilities/cmcurl/CMakeLists.txt
index 49016c8..dcade3d 100644
--- a/Utilities/cmcurl/CMakeLists.txt
+++ b/Utilities/cmcurl/CMakeLists.txt
@@ -150,7 +150,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
@@ -197,7 +197,7 @@
 #   HAVE_RAND_EGD: `RAND_egd` present in OpenSSL
 #   HAVE_BORINGSSL: OpenSSL is BoringSSL
 #   HAVE_PK11_CREATEMANAGEDGENERICOBJECTL: `PK11_CreateManagedGenericObject` present in NSS
-#   HAVE_SSL_CTX_SET_QUIC_METHOD: `SSL_CTX_set_quic_method` present in OpenSSL
+#   HAVE_SSL_CTX_SET_QUIC_METHOD: `SSL_CTX_set_quic_method` present in OpenSSL/wolfSSL
 #   HAVE_QUICHE_CONN_SET_QLOG_FD: `quiche_conn_set_qlog_fd` present in QUICHE
 #   HAVE_ZSTD_CREATEDSTREAM: `ZSTD_createDStream` present in Zstd
 #
@@ -253,7 +253,7 @@
   set(CURL_TARGET_WINDOWS_VERSION "" CACHE STRING "Minimum target Windows version as hex string")
   if(CURL_TARGET_WINDOWS_VERSION)
     add_definitions(-D_WIN32_WINNT=${CURL_TARGET_WINDOWS_VERSION})
-    set(CMAKE_REQUIRED_DEFINITIONS "${CMAKE_REQUIRED_DEFINITIONS} -D_WIN32_WINNT=${CURL_TARGET_WINDOWS_VERSION}")
+    list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_WIN32_WINNT=${CURL_TARGET_WINDOWS_VERSION})
     set(CURL_TEST_DEFINES "${CURL_TEST_DEFINES} -D_WIN32_WINNT=${CURL_TARGET_WINDOWS_VERSION}")
   endif()
   endif()
@@ -488,7 +488,7 @@
 
 # On windows preload settings
 if(WIN32)
-  set(CMAKE_REQUIRED_DEFINITIONS "${CMAKE_REQUIRED_DEFINITIONS} -D_WINSOCKAPI_=")
+  list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_WINSOCKAPI_=)
   include(${CMAKE_CURRENT_SOURCE_DIR}/CMake/Platforms/WindowsCache.cmake)
 endif()
 
@@ -514,24 +514,6 @@
 endif()
 
 if(0) # This code not needed for building within CMake.
-# This check below for use of deprecated symbols is only temporary and is to
-# be removed again after a year's service. Remove after November 25, 2022.
-set(CURL_RECONFIG_REQUIRED 0)
-foreach(_LIB GSSAPI OPENLDAP LIBSSH LIBSSH2 BEARSSL MBEDTLS NSS OPENSSL
-        SCHANNEL SECTRANSP WOLFSSL)
-  if(CMAKE_USE_${_LIB})
-    set(CURL_RECONFIG_REQUIRED 1)
-    message(SEND_ERROR "The option CMAKE_USE_${_LIB} was renamed to CURL_USE_${_LIB}.")
-  endif()
-endforeach()
-if(CMAKE_USE_WINSSL)
-  set(CURL_RECONFIG_REQUIRED 1)
-  message(SEND_ERROR "The option CMAKE_USE_WINSSL was renamed to CURL_USE_SCHANNEL.")
-endif()
-if(CURL_RECONFIG_REQUIRED)
-  message(FATAL_ERROR "Reconfig required")
-endif()
-
 # check SSL libraries
 # TODO support GnuTLS
 option(CURL_ENABLE_SSL "Enable SSL support" ON)
@@ -541,7 +523,7 @@
 endif()
 if(WIN32)
   cmake_dependent_option(CURL_USE_SCHANNEL "enable Windows native SSL/TLS" OFF CURL_ENABLE_SSL OFF)
-  cmake_dependent_option(CURL_WINDOWS_SSPI "Use windows libraries to allow NTLM authentication without openssl" ON
+  cmake_dependent_option(CURL_WINDOWS_SSPI "Use windows libraries to allow NTLM authentication without OpenSSL" ON
     CURL_USE_SCHANNEL OFF)
 endif()
 cmake_dependent_option(CURL_USE_MBEDTLS "Enable mbedTLS for SSL/TLS" OFF CURL_ENABLE_SSL OFF)
@@ -577,7 +559,6 @@
 endif()
 if(CURL_WINDOWS_SSPI)
   set(USE_WINDOWS_SSPI ON)
-  set(CMAKE_REQUIRED_DEFINITIONS "${CMAKE_REQUIRED_DEFINITIONS} -DSECURITY_WIN32")
 endif()
 
 if(CURL_USE_SECTRANSP)
@@ -602,6 +583,62 @@
   list(APPEND CURL_LIBS "-framework CoreFoundation")
 endif()
 
+# Keep compression lib detection before TLS detection, which
+# might depend on it.
+
+set(HAVE_LIBZ OFF)
+set(USE_ZLIB OFF)
+find_package(ZLIB)
+if(ZLIB_FOUND)
+  set(HAVE_LIBZ ON)
+  set(USE_ZLIB ON)
+
+  # Depend on ZLIB via imported targets if supported by the running
+  # version of CMake.  This allows our dependents to get our dependencies
+  # transitively.
+  if(NOT CMAKE_VERSION VERSION_LESS 3.4)
+    if(CMAKE_USE_SYSTEM_ZLIB)
+      list(APPEND CURL_LIBS ZLIB::ZLIB)
+    else()
+      list(APPEND CURL_LIBS cmzlib)
+    endif()
+  else()
+    list(APPEND CURL_LIBS ${ZLIB_LIBRARIES})
+    include_directories(${ZLIB_INCLUDE_DIRS})
+    list(APPEND CMAKE_REQUIRED_INCLUDES ${ZLIB_INCLUDE_DIRS})
+  endif()
+endif()
+
+option(CURL_BROTLI "Set to ON to enable building curl with brotli support." OFF)
+set(HAVE_BROTLI OFF)
+if(CURL_BROTLI)
+  find_package(Brotli QUIET)
+  if(BROTLI_FOUND)
+    set(HAVE_BROTLI ON)
+    list(APPEND CURL_LIBS ${BROTLI_LIBRARIES})
+    include_directories(${BROTLI_INCLUDE_DIRS})
+    list(APPEND CMAKE_REQUIRED_INCLUDES ${BROTLI_INCLUDE_DIRS})
+  endif()
+endif()
+
+option(CURL_ZSTD "Set to ON to enable building curl with zstd support." OFF)
+set(HAVE_ZSTD OFF)
+if(CURL_ZSTD)
+  find_package(Zstd REQUIRED)
+  if (NOT DEFINED HAVE_ZSTD_CREATEDSTREAM)
+    cmake_push_check_state()
+    set(CMAKE_REQUIRED_INCLUDES ${Zstd_INCLUDE_DIRS})
+    set(CMAKE_REQUIRED_LIBRARIES ${Zstd_LIBRARIES})
+    check_symbol_exists(ZSTD_createDStream "zstd.h" HAVE_ZSTD_CREATEDSTREAM)
+    cmake_pop_check_state()
+  endif()
+  if(Zstd_FOUND AND HAVE_ZSTD_CREATEDSTREAM)
+    set(HAVE_ZSTD ON)
+    list(APPEND CURL_LIBS ${Zstd_LIBRARIES})
+    include_directories(${Zstd_INCLUDE_DIRS})
+  endif()
+endif()
+
 if(CURL_USE_OPENSSL)
   find_package(OpenSSL)
   if(NOT OpenSSL_FOUND)
@@ -630,8 +667,6 @@
   if(CURL_CA_PATH)
     add_definitions(-DCURL_CA_PATH="${CURL_CA_PATH}")
   endif()
-
-  add_definitions(-DOPENSSL_SUPPRESS_DEPRECATED)
 endif()
 
 if(CURL_USE_MBEDTLS)
@@ -681,23 +716,39 @@
 endif()
 
 function(CheckQuicSupportInOpenSSL)
-  # Be sure that the OpenSSL library actually supports QUIC.
+  # Be sure that the OpenSSL/wolfSSL library actually supports QUIC.
   if(NOT DEFINED HAVE_SSL_CTX_SET_QUIC_METHOD)
     cmake_push_check_state()
-    set(CMAKE_REQUIRED_INCLUDES   "${OPENSSL_INCLUDE_DIR}")
-    set(CMAKE_REQUIRED_LIBRARIES  "${OPENSSL_LIBRARIES}")
-    check_symbol_exists(SSL_CTX_set_quic_method "openssl/ssl.h" HAVE_SSL_CTX_SET_QUIC_METHOD)
+    if(USE_WOLFSSL)
+      set(CMAKE_REQUIRED_INCLUDES   "${WolfSSL_INCLUDE_DIRS}")
+      set(CMAKE_REQUIRED_LIBRARIES  "${WolfSSL_LIBRARIES}")
+      if(HAVE_LIBZ)
+        list(APPEND CMAKE_REQUIRED_INCLUDES  "${ZLIB_INCLUDE_DIRS}")
+        list(APPEND CMAKE_REQUIRED_LIBRARIES "${ZLIB_LIBRARIES}")
+      endif()
+      if(WIN32)
+        list(APPEND CMAKE_REQUIRED_LIBRARIES "crypt32")
+      endif()
+      list(APPEND CMAKE_REQUIRED_DEFINITIONS -DHAVE_UINTPTR_T)  # to pull in stdint.h (as of wolfSSL v5.5.4)
+      check_symbol_exists(wolfSSL_set_quic_method "wolfssl/options.h;wolfssl/openssl/ssl.h" HAVE_SSL_CTX_SET_QUIC_METHOD)
+    else()
+      set(CMAKE_REQUIRED_INCLUDES   "${OPENSSL_INCLUDE_DIR}")
+      set(CMAKE_REQUIRED_LIBRARIES  "${OPENSSL_LIBRARIES}")
+      check_symbol_exists(SSL_CTX_set_quic_method "openssl/ssl.h" HAVE_SSL_CTX_SET_QUIC_METHOD)
+    endif()
     cmake_pop_check_state()
   endif()
   if(NOT HAVE_SSL_CTX_SET_QUIC_METHOD)
-    message(FATAL_ERROR "QUIC support is missing in OpenSSL/BoringSSL. Try setting -DOPENSSL_ROOT_DIR")
+    message(FATAL_ERROR "QUIC support is missing in OpenSSL/BoringSSL/wolfSSL. Try setting -DOPENSSL_ROOT_DIR")
   endif()
 endfunction()
 
 option(USE_NGTCP2 "Use ngtcp2 and nghttp3 libraries for HTTP/3 support" OFF)
 if(USE_NGTCP2)
-  if(USE_OPENSSL)
-    if(HAVE_BORINGSSL)
+  if(USE_OPENSSL OR USE_WOLFSSL)
+    if(USE_WOLFSSL)
+      find_package(NGTCP2 REQUIRED wolfSSL)
+    elseif(HAVE_BORINGSSL)
       find_package(NGTCP2 REQUIRED BoringSSL)
     else()
       find_package(NGTCP2 REQUIRED OpenSSL)
@@ -707,7 +758,7 @@
     # TODO add GnuTLS support as vtls library.
     find_package(NGTCP2 REQUIRED GnuTLS)
   else()
-    message(FATAL_ERROR "ngtcp2 requires OpenSSL or GnuTLS")
+    message(FATAL_ERROR "ngtcp2 requires OpenSSL, wolfSSL or GnuTLS")
   endif()
   set(USE_NGTCP2 ON)
   include_directories(${NGTCP2_INCLUDE_DIRS})
@@ -755,6 +806,8 @@
       check_library_exists_concat("wldap32" cldap_open HAVE_WLDAP32)
       if(NOT HAVE_WLDAP32)
         set(USE_WIN32_LDAP OFF)
+      elseif(NOT CURL_DISABLE_LDAPS)
+        set(HAVE_LDAP_SSL ON)
       endif()
     endif()
   endif()
@@ -826,7 +879,7 @@
           return 0;
         }"
       )
-      set(CMAKE_REQUIRED_DEFINITIONS "${CMAKE_REQUIRED_DEFINITIONS} -DLDAP_DEPRECATED=1")
+      list(APPEND CMAKE_REQUIRED_DEFINITIONS -DLDAP_DEPRECATED=1)
       list(APPEND CMAKE_REQUIRED_LIBRARIES ${CMAKE_LDAP_LIB})
       if(HAVE_LIBLBER)
         list(APPEND CMAKE_REQUIRED_LIBRARIES ${CMAKE_LBER_LIB})
@@ -870,56 +923,6 @@
   endif()
 endif()
 
-set(HAVE_LIBZ OFF)
-set(USE_ZLIB OFF)
-#optional_dependency(ZLIB)
-find_package(ZLIB)
-if(ZLIB_FOUND)
-  set(HAVE_LIBZ ON)
-  set(USE_ZLIB ON)
-
-  # Depend on ZLIB via imported targets if supported by the running
-  # version of CMake.  This allows our dependents to get our dependencies
-  # transitively.
-  if(NOT CMAKE_VERSION VERSION_LESS 3.4)
-    list(APPEND CURL_LIBS ZLIB::ZLIB)
-  else()
-    list(APPEND CURL_LIBS ${ZLIB_LIBRARIES})
-    include_directories(${ZLIB_INCLUDE_DIRS})
-    list(APPEND CMAKE_REQUIRED_INCLUDES ${ZLIB_INCLUDE_DIRS})
-  endif()
-endif()
-
-option(CURL_BROTLI "Set to ON to enable building curl with brotli support." OFF)
-set(HAVE_BROTLI OFF)
-if(CURL_BROTLI)
-  find_package(Brotli QUIET)
-  if(BROTLI_FOUND)
-    set(HAVE_BROTLI ON)
-    list(APPEND CURL_LIBS ${BROTLI_LIBRARIES})
-    include_directories(${BROTLI_INCLUDE_DIRS})
-    list(APPEND CMAKE_REQUIRED_INCLUDES ${BROTLI_INCLUDE_DIRS})
-  endif()
-endif()
-
-option(CURL_ZSTD "Set to ON to enable building curl with zstd support." OFF)
-set(HAVE_ZSTD OFF)
-if(CURL_ZSTD)
-  find_package(Zstd REQUIRED)
-  if (NOT DEFINED HAVE_ZSTD_CREATEDSTREAM)
-    cmake_push_check_state()
-    set(CMAKE_REQUIRED_INCLUDES ${Zstd_INCLUDE_DIRS})
-    set(CMAKE_REQUIRED_LIBRARIES ${Zstd_LIBRARIES})
-    check_symbol_exists(ZSTD_createDStream "zstd.h" HAVE_ZSTD_CREATEDSTREAM)
-    cmake_pop_check_state()
-  endif()
-  if(Zstd_FOUND AND HAVE_ZSTD_CREATEDSTREAM)
-    set(HAVE_ZSTD ON)
-    list(APPEND CURL_LIBS ${Zstd_LIBRARIES})
-    include_directories(${Zstd_INCLUDE_DIRS})
-  endif()
-endif()
-
 #libpsl
 option(CURL_USE_LIBPSL "Use libPSL" ON)
 mark_as_advanced(CURL_USE_LIBPSL)
@@ -1056,7 +1059,9 @@
   unset(CURL_CA_BUNDLE CACHE)
 elseif("${CURL_CA_BUNDLE}" STREQUAL "auto")
   unset(CURL_CA_BUNDLE CACHE)
-  set(CURL_CA_BUNDLE_AUTODETECT TRUE)
+  if(NOT CMAKE_CROSSCOMPILING)
+    set(CURL_CA_BUNDLE_AUTODETECT TRUE)
+  endif()
 else()
   set(CURL_CA_BUNDLE_SET TRUE)
 endif()
@@ -1067,7 +1072,7 @@
   unset(CURL_CA_PATH CACHE)
 elseif("${CURL_CA_PATH}" STREQUAL "auto")
   unset(CURL_CA_PATH CACHE)
-  if(NOT USE_NSS)
+  if(NOT CMAKE_CROSSCOMPILING AND NOT USE_NSS)
     set(CURL_CA_PATH_AUTODETECT TRUE)
   endif()
 else()
@@ -1177,7 +1182,6 @@
 check_include_file_concat("utime.h"          HAVE_UTIME_H)
 
 check_include_file_concat("stddef.h"         HAVE_STDDEF_H)
-check_include_file_concat("stdint.h"        HAVE_STDINT_H)
 check_include_file_concat("sys/utsname.h"   HAVE_SYS_UTSNAME_H)
 
 check_type_size(size_t  SIZEOF_SIZE_T)
@@ -1204,6 +1208,7 @@
 check_symbol_exists(socketpair    "${CURL_INCLUDES}" HAVE_SOCKETPAIR)
 check_symbol_exists(recv          "${CURL_INCLUDES}" HAVE_RECV)
 check_symbol_exists(send          "${CURL_INCLUDES}" HAVE_SEND)
+check_symbol_exists(sendmsg       "${CURL_INCLUDES}" HAVE_SENDMSG)
 check_symbol_exists(select        "${CURL_INCLUDES}" HAVE_SELECT)
 check_symbol_exists(strdup        "${CURL_INCLUDES}" HAVE_STRDUP)
 check_symbol_exists(strtok_r      "${CURL_INCLUDES}" HAVE_STRTOK_R)
@@ -1228,7 +1233,6 @@
 
 check_symbol_exists(signal         "${CURL_INCLUDES}" HAVE_SIGNAL)
 check_symbol_exists(strtoll        "${CURL_INCLUDES}" HAVE_STRTOLL)
-check_symbol_exists(_strtoi64      "${CURL_INCLUDES}" HAVE__STRTOI64)
 check_symbol_exists(strerror_r     "${CURL_INCLUDES}" HAVE_STRERROR_R)
 check_symbol_exists(siginterrupt   "${CURL_INCLUDES}" HAVE_SIGINTERRUPT)
 check_symbol_exists(getaddrinfo    "${CURL_INCLUDES}" HAVE_GETADDRINFO)
@@ -1248,7 +1252,7 @@
 
 if(NOT MSVC OR (MSVC_VERSION GREATER_EQUAL 1900))
   # earlier MSVC compilers had faulty snprintf implementations
-  check_symbol_exists(snprintf       "${CURL_INCLUDES}" HAVE_SNPRINTF)
+  check_symbol_exists(snprintf       "stdio.h" HAVE_SNPRINTF)
 endif()
 check_function_exists(mach_absolute_time HAVE_MACH_ABSOLUTE_TIME)
 check_symbol_exists(inet_ntop      "${CURL_INCLUDES}" HAVE_INET_NTOP)
@@ -1435,8 +1439,6 @@
   set(CURL_PULL_SYS_SOCKET_H ${HAVE_SYS_SOCKET_H})
   set(CURL_PULL_SYS_POLL_H ${HAVE_SYS_POLL_H})
 endif()
-set(CURL_PULL_STDINT_H ${HAVE_STDINT_H})
-set(CURL_PULL_INTTYPES_H ${HAVE_INTTYPES_H})
 
 include(CMake/OtherTests.cmake)
 
@@ -1451,7 +1453,7 @@
 
   # Check if crypto functions in wincrypt.h are actually available
   if(HAVE_WINCRYPT_H)
-    check_symbol_exists(CryptAcquireContext "${CURL_INCLUDES}" USE_WINCRYPT)
+    check_symbol_exists(CryptAcquireContext "windows.h;wincrypt.h" USE_WINCRYPT)
   endif()
   if(USE_WINCRYPT)
     set(USE_WIN32_CRYPTO ON)
diff --git a/Utilities/cmcurl/COPYING b/Utilities/cmcurl/COPYING
index 90f05ad..d1eab3e 100644
--- a/Utilities/cmcurl/COPYING
+++ b/Utilities/cmcurl/COPYING
@@ -1,6 +1,6 @@
 COPYRIGHT AND PERMISSION NOTICE
 
-Copyright (c) 1996 - 2022, Daniel Stenberg, <daniel@haxx.se>, and many
+Copyright (c) 1996 - 2023, Daniel Stenberg, <daniel@haxx.se>, and many
 contributors, see the THANKS file.
 
 All rights reserved.
diff --git a/Utilities/cmcurl/curltest.c b/Utilities/cmcurl/curltest.c
index f80e758..cb87fce 100644
--- a/Utilities/cmcurl/curltest.c
+++ b/Utilities/cmcurl/curltest.c
@@ -10,21 +10,24 @@
   CURLcode r;
   char proxy[1024];
   int proxy_type = 0;
+  const char* env_HTTP_PROXY = getenv("HTTP_PROXY");
 
-  if (getenv("HTTP_PROXY")) {
+  if (env_HTTP_PROXY) {
+    const char* env_HTTP_PROXY_PORT = getenv("HTTP_PROXY_PORT");
+    const char* env_HTTP_PROXY_TYPE = getenv("HTTP_PROXY_TYPE");
     proxy_type = 1;
-    if (getenv("HTTP_PROXY_PORT")) {
-      sprintf(proxy, "%s:%s", getenv("HTTP_PROXY"), getenv("HTTP_PROXY_PORT"));
+    if (env_HTTP_PROXY_PORT) {
+      sprintf(proxy, "%s:%s", env_HTTP_PROXY, env_HTTP_PROXY_PORT);
     } else {
-      sprintf(proxy, "%s", getenv("HTTP_PROXY"));
+      sprintf(proxy, "%s", env_HTTP_PROXY);
     }
-    if (getenv("HTTP_PROXY_TYPE")) {
+    if (env_HTTP_PROXY_TYPE) {
       /* HTTP/SOCKS4/SOCKS5 */
-      if (strcmp(getenv("HTTP_PROXY_TYPE"), "HTTP") == 0) {
+      if (strcmp(env_HTTP_PROXY_TYPE, "HTTP") == 0) {
         proxy_type = 1;
-      } else if (strcmp(getenv("HTTP_PROXY_TYPE"), "SOCKS4") == 0) {
+      } else if (strcmp(env_HTTP_PROXY_TYPE, "SOCKS4") == 0) {
         proxy_type = 2;
-      } else if (strcmp(getenv("HTTP_PROXY_TYPE"), "SOCKS5") == 0) {
+      } else if (strcmp(env_HTTP_PROXY_TYPE, "SOCKS5") == 0) {
         proxy_type = 3;
       }
     }
diff --git a/Utilities/cmcurl/include/curl/curl.h b/Utilities/cmcurl/include/curl/curl.h
index b354757..4c6288d 100644
--- a/Utilities/cmcurl/include/curl/curl.h
+++ b/Utilities/cmcurl/include/curl/curl.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -34,11 +34,12 @@
 #endif
 
 /* Compile-time deprecation macros. */
-#if defined(__GNUC__) && (__GNUC__ >= 6) &&                             \
+#if defined(__GNUC__) &&                                                \
+  ((__GNUC__ > 12) || ((__GNUC__ == 12) && (__GNUC_MINOR__ >= 1 ))) &&  \
   !defined(__INTEL_COMPILER) &&                                         \
   !defined(CURL_DISABLE_DEPRECATION) && !defined(BUILDING_LIBCURL)
-#define CURL_DEPRECATED(version, message) \
-    __attribute__((deprecated("since " # version ". " message)))
+#define CURL_DEPRECATED(version, message)                       \
+  __attribute__((deprecated("since " # version ". " message)))
 #define CURL_IGNORE_DEPRECATION(statements) \
       _Pragma("GCC diagnostic push") \
       _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") \
@@ -167,7 +168,7 @@
   CURLSSLBACKEND_SECURETRANSPORT = 9,
   CURLSSLBACKEND_AXTLS                  CURL_DEPRECATED(7.61.0, "") = 10,
   CURLSSLBACKEND_MBEDTLS = 11,
-  CURLSSLBACKEND_MESALINK = 12,
+  CURLSSLBACKEND_MESALINK               CURL_DEPRECATED(7.82.0, "") = 12,
   CURLSSLBACKEND_BEARSSL = 13,
   CURLSSLBACKEND_RUSTLS = 14
 } curl_sslbackend;
@@ -248,7 +249,7 @@
 
 #ifndef CURL_MAX_READ_SIZE
   /* The maximum receive buffer size configurable via CURLOPT_BUFFERSIZE. */
-#define CURL_MAX_READ_SIZE 524288
+#define CURL_MAX_READ_SIZE (10*1024*1024)
 #endif
 
 #ifndef CURL_MAX_WRITE_SIZE
@@ -2259,8 +2260,13 @@
   CURL_HTTP_VERSION_2TLS, /* use version 2 for HTTPS, version 1.1 for HTTP */
   CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE,  /* please use HTTP 2 without HTTP/1.1
                                            Upgrade */
-  CURL_HTTP_VERSION_3 = 30, /* Makes use of explicit HTTP/3 without fallback.
-                               Use CURLOPT_ALTSVC to enable HTTP/3 upgrade */
+  CURL_HTTP_VERSION_3 = 30, /* Use HTTP/3, fallback to HTTP/2 or HTTP/1 if
+                               needed. For HTTPS only. For HTTP, this option
+                               makes libcurl return error. */
+  CURL_HTTP_VERSION_3ONLY = 31, /* Use HTTP/3 without fallback. For HTTPS
+                                   only. For HTTP, this makes libcurl
+                                   return error. */
+
   CURL_HTTP_VERSION_LAST /* *ILLEGAL* http version */
 };
 
@@ -2953,6 +2959,7 @@
   CURL_LOCK_DATA_SSL_SESSION,
   CURL_LOCK_DATA_CONNECT,
   CURL_LOCK_DATA_PSL,
+  CURL_LOCK_DATA_HSTS,
   CURL_LOCK_DATA_LAST
 } curl_lock_data;
 
diff --git a/Utilities/cmcurl/include/curl/curlver.h b/Utilities/cmcurl/include/curl/curlver.h
index 94db4cd..6f2affa 100644
--- a/Utilities/cmcurl/include/curl/curlver.h
+++ b/Utilities/cmcurl/include/curl/curlver.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -28,17 +28,17 @@
    a script at release-time. This was made its own header file in 7.11.2 */
 
 /* This is the global package copyright */
-#define LIBCURL_COPYRIGHT "1996 - 2022 Daniel Stenberg, <daniel@haxx.se>."
+#define LIBCURL_COPYRIGHT "Daniel Stenberg, <daniel@haxx.se>."
 
 /* This is the version number of the libcurl package from which this header
    file origins: */
-#define LIBCURL_VERSION "7.87.0"
+#define LIBCURL_VERSION "8.0.1"
 
 /* The numeric version number is also available "in parts" by using these
    defines: */
-#define LIBCURL_VERSION_MAJOR 7
-#define LIBCURL_VERSION_MINOR 87
-#define LIBCURL_VERSION_PATCH 0
+#define LIBCURL_VERSION_MAJOR 8
+#define LIBCURL_VERSION_MINOR 0
+#define LIBCURL_VERSION_PATCH 1
 
 /* This is the numeric version of the libcurl version number, meant for easier
    parsing and comparisons by programs. The LIBCURL_VERSION_NUM define will
@@ -59,7 +59,7 @@
    CURL_VERSION_BITS() macro since curl's own configure script greps for it
    and needs it to contain the full number.
 */
-#define LIBCURL_VERSION_NUM 0x075700
+#define LIBCURL_VERSION_NUM 0x080001
 
 /*
  * This is the date and time when the full source package was created. The
diff --git a/Utilities/cmcurl/include/curl/easy.h b/Utilities/cmcurl/include/curl/easy.h
index 98ee888..394668a 100644
--- a/Utilities/cmcurl/include/curl/easy.h
+++ b/Utilities/cmcurl/include/curl/easy.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/include/curl/header.h b/Utilities/cmcurl/include/curl/header.h
index 1598c6f..8df11e1 100644
--- a/Utilities/cmcurl/include/curl/header.h
+++ b/Utilities/cmcurl/include/curl/header.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2018 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/include/curl/mprintf.h b/Utilities/cmcurl/include/curl/mprintf.h
index 06ef5c6..e652a65 100644
--- a/Utilities/cmcurl/include/curl/mprintf.h
+++ b/Utilities/cmcurl/include/curl/mprintf.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/include/curl/multi.h b/Utilities/cmcurl/include/curl/multi.h
index c956d28..30a3d93 100644
--- a/Utilities/cmcurl/include/curl/multi.h
+++ b/Utilities/cmcurl/include/curl/multi.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/include/curl/options.h b/Utilities/cmcurl/include/curl/options.h
index a792687..1ed76a9 100644
--- a/Utilities/cmcurl/include/curl/options.h
+++ b/Utilities/cmcurl/include/curl/options.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2018 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/include/curl/stdcheaders.h b/Utilities/cmcurl/include/curl/stdcheaders.h
index 82e1b5f..7451aa3 100644
--- a/Utilities/cmcurl/include/curl/stdcheaders.h
+++ b/Utilities/cmcurl/include/curl/stdcheaders.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/include/curl/system.h b/Utilities/cmcurl/include/curl/system.h
index 11db51e..def7739 100644
--- a/Utilities/cmcurl/include/curl/system.h
+++ b/Utilities/cmcurl/include/curl/system.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -227,16 +227,14 @@
 #  define CURL_TYPEOF_CURL_SOCKLEN_T unsigned int
 
 #elif defined(__OS400__)
-#  if defined(__ILEC400__)
-#    define CURL_TYPEOF_CURL_OFF_T     long long
-#    define CURL_FORMAT_CURL_OFF_T     "lld"
-#    define CURL_FORMAT_CURL_OFF_TU    "llu"
-#    define CURL_SUFFIX_CURL_OFF_T     LL
-#    define CURL_SUFFIX_CURL_OFF_TU    ULL
-#    define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t
-#    define CURL_PULL_SYS_TYPES_H      1
-#    define CURL_PULL_SYS_SOCKET_H     1
-#  endif
+#  define CURL_TYPEOF_CURL_OFF_T     long long
+#  define CURL_FORMAT_CURL_OFF_T     "lld"
+#  define CURL_FORMAT_CURL_OFF_TU    "llu"
+#  define CURL_SUFFIX_CURL_OFF_T     LL
+#  define CURL_SUFFIX_CURL_OFF_TU    ULL
+#  define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t
+#  define CURL_PULL_SYS_TYPES_H      1
+#  define CURL_PULL_SYS_SOCKET_H     1
 
 #elif defined(__MVS__)
 #  if defined(__IBMC__) || defined(__IBMCPP__)
diff --git a/Utilities/cmcurl/include/curl/typecheck-gcc.h b/Utilities/cmcurl/include/curl/typecheck-gcc.h
index bf655bb..bc8d7a7 100644
--- a/Utilities/cmcurl/include/curl/typecheck-gcc.h
+++ b/Utilities/cmcurl/include/curl/typecheck-gcc.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -42,9 +42,8 @@
  */
 #define curl_easy_setopt(handle, option, value)                         \
   __extension__({                                                       \
-      CURL_IGNORE_DEPRECATION(__typeof__(option) _curl_opt = option;)   \
+      CURLoption _curl_opt = (option);                                  \
       if(__builtin_constant_p(_curl_opt)) {                             \
-        (void) option;                                                  \
         CURL_IGNORE_DEPRECATION(                                        \
           if(curlcheck_long_option(_curl_opt))                          \
             if(!curlcheck_long(value))                                  \
@@ -120,9 +119,8 @@
 /* wraps curl_easy_getinfo() with typechecking */
 #define curl_easy_getinfo(handle, info, arg)                            \
   __extension__({                                                       \
-      CURL_IGNORE_DEPRECATION(__typeof__(info) _curl_info = info;)      \
+      CURLINFO _curl_info = (info);                                     \
       if(__builtin_constant_p(_curl_info)) {                            \
-        (void) info;                                                    \
         CURL_IGNORE_DEPRECATION(                                        \
           if(curlcheck_string_info(_curl_info))                         \
             if(!curlcheck_arr((arg), char *))                           \
diff --git a/Utilities/cmcurl/include/curl/urlapi.h b/Utilities/cmcurl/include/curl/urlapi.h
index e15c213..b3504b6 100644
--- a/Utilities/cmcurl/include/curl/urlapi.h
+++ b/Utilities/cmcurl/include/curl/urlapi.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2018 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -62,6 +62,7 @@
   CURLUE_BAD_SCHEME,          /* 27 */
   CURLUE_BAD_SLASHES,         /* 28 */
   CURLUE_BAD_USER,            /* 29 */
+  CURLUE_LACKS_IDN,           /* 30 */
   CURLUE_LAST
 } CURLUcode;
 
@@ -95,6 +96,7 @@
 #define CURLU_NO_AUTHORITY (1<<10)      /* Allow empty authority when the
                                            scheme is unknown. */
 #define CURLU_ALLOW_SPACE (1<<11)       /* Allow spaces in the URL */
+#define CURLU_PUNYCODE (1<<12)          /* get the host name in pynycode */
 
 typedef struct Curl_URL CURLU;
 
@@ -115,14 +117,14 @@
  * curl_url_dup() duplicates a CURLU handle and returns a new copy. The new
  * handle must also be freed with curl_url_cleanup().
  */
-CURL_EXTERN CURLU *curl_url_dup(CURLU *in);
+CURL_EXTERN CURLU *curl_url_dup(const CURLU *in);
 
 /*
  * curl_url_get() extracts a specific part of the URL from a CURLU
  * handle. Returns error code. The returned pointer MUST be freed with
  * curl_free() afterwards.
  */
-CURL_EXTERN CURLUcode curl_url_get(CURLU *handle, CURLUPart what,
+CURL_EXTERN CURLUcode curl_url_get(const CURLU *handle, CURLUPart what,
                                    char **part, unsigned int flags);
 
 /*
diff --git a/Utilities/cmcurl/include/curl/websockets.h b/Utilities/cmcurl/include/curl/websockets.h
index 4d57f91..fd6a916 100644
--- a/Utilities/cmcurl/include/curl/websockets.h
+++ b/Utilities/cmcurl/include/curl/websockets.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -33,6 +33,7 @@
   int flags;            /* See the CURLWS_* defines */
   curl_off_t offset;    /* the offset of this data into the frame */
   curl_off_t bytesleft; /* number of pending bytes left of the payload */
+  size_t len;           /* size of the current data chunk */
 };
 
 /* flag bits */
diff --git a/Utilities/cmcurl/lib/CMakeLists.txt b/Utilities/cmcurl/lib/CMakeLists.txt
index 074ab9d..c3eb842 100644
--- a/Utilities/cmcurl/lib/CMakeLists.txt
+++ b/Utilities/cmcurl/lib/CMakeLists.txt
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
@@ -47,29 +47,6 @@
   list(APPEND CSOURCES libcurl.rc)
 endif()
 
-# SET(CSOURCES
-# #  memdebug.c -not used
-# # nwlib.c - Not used
-# # strtok.c - specify later
-# # strtoofft.c - specify later
-# )
-
-# #OPTION(CURL_MALLOC_DEBUG "Debug mallocs in Curl" OFF)
-# MARK_AS_ADVANCED(CURL_MALLOC_DEBUG)
-# IF(CURL_MALLOC_DEBUG)
-# SET(CSOURCES ${CSOURCES}
-# memdebug.c
-# )
-# ENDIF(CURL_MALLOC_DEBUG)
-
-# # only build compat strtoofft if we need to
-# IF(NOT HAVE_STRTOLL AND NOT HAVE__STRTOI64)
-# SET(CSOURCES ${CSOURCES}
-# strtoofft.c
-# )
-# ENDIF(NOT HAVE_STRTOLL AND NOT HAVE__STRTOI64)
-
-
 # The rest of the build
 
 include_directories(${CMAKE_CURRENT_BINARY_DIR}/../include)
@@ -136,11 +113,12 @@
 if(0) # This code not needed for building within CMake.
 if(CMAKE_SYSTEM_NAME STREQUAL "AIX" OR
   CMAKE_SYSTEM_NAME STREQUAL "Linux" OR
+  CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR
   CMAKE_SYSTEM_NAME STREQUAL "GNU/kFreeBSD" OR
 
   # FreeBSD comes with the a.out and elf flavours
   # but a.out was supported up to version 3.x and
-  # elf from 3.x. I cannot imagine someone runnig
+  # elf from 3.x. I cannot imagine someone running
   # CMake on those ancient systems
   CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR
 
diff --git a/Utilities/cmcurl/lib/Makefile.inc b/Utilities/cmcurl/lib/Makefile.inc
index 9eafa93..663190a 100644
--- a/Utilities/cmcurl/lib/Makefile.inc
+++ b/Utilities/cmcurl/lib/Makefile.inc
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 #
 # This software is licensed as described in the file COPYING, which
 # you should have received as part of this distribution. The terms
@@ -79,16 +79,17 @@
   vtls/x509asn1.h
 
 LIB_VQUIC_CFILES = \
-  vquic/msh3.c   \
-  vquic/ngtcp2.c   \
-  vquic/quiche.c   \
+  vquic/curl_msh3.c   \
+  vquic/curl_ngtcp2.c   \
+  vquic/curl_quiche.c   \
   vquic/vquic.c
 
 LIB_VQUIC_HFILES = \
-  vquic/msh3.h   \
-  vquic/ngtcp2.h   \
-  vquic/quiche.h   \
-  vquic/vquic.h
+  vquic/curl_msh3.h   \
+  vquic/curl_ngtcp2.h   \
+  vquic/curl_quiche.h   \
+  vquic/vquic.h    \
+  vquic/vquic_int.h
 
 LIB_VSSH_CFILES =  \
   vssh/libssh.c    \
@@ -106,6 +107,8 @@
   base64.c           \
   bufref.c           \
   c-hyper.c          \
+  cf-https-connect.c \
+  cf-socket.c        \
   cfilters.c         \
   conncache.c        \
   connect.c          \
@@ -118,6 +121,7 @@
   curl_get_line.c    \
   curl_gethostname.c \
   curl_gssapi.c      \
+  curl_log.c         \
   curl_memrchr.c     \
   curl_multibyte.c   \
   curl_ntlm_core.c   \
@@ -219,7 +223,6 @@
   version.c          \
   version_win32.c    \
   warnless.c         \
-  wildcard.c         \
   ws.c
 
 LIB_HFILES =         \
@@ -229,6 +232,8 @@
   asyn.h             \
   bufref.h           \
   c-hyper.h          \
+  cf-https-connect.h \
+  cf-socket.h        \
   cfilters.h         \
   conncache.h        \
   connect.h          \
@@ -246,6 +251,7 @@
   curl_hmac.h        \
   curl_krb5.h        \
   curl_ldap.h        \
+  curl_log.h         \
   curl_md4.h         \
   curl_md5.h         \
   curl_memory.h      \
@@ -312,7 +318,6 @@
   pop3.h             \
   progress.h         \
   psl.h              \
-  quic.h             \
   rand.h             \
   rename.h           \
   rtsp.h             \
@@ -346,7 +351,6 @@
   urldata.h          \
   version_win32.h    \
   warnless.h         \
-  wildcard.h         \
   ws.h
 
 LIB_RCFILES = libcurl.rc
diff --git a/Utilities/cmcurl/lib/altsvc.c b/Utilities/cmcurl/lib/altsvc.c
index ec18e38..31a7abc 100644
--- a/Utilities/cmcurl/lib/altsvc.c
+++ b/Utilities/cmcurl/lib/altsvc.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2019 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/altsvc.h b/Utilities/cmcurl/lib/altsvc.h
index 2751d27..7fea143 100644
--- a/Utilities/cmcurl/lib/altsvc.h
+++ b/Utilities/cmcurl/lib/altsvc.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2019 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/amigaos.c b/Utilities/cmcurl/lib/amigaos.c
index e8c2fc0..b0a9500 100644
--- a/Utilities/cmcurl/lib/amigaos.c
+++ b/Utilities/cmcurl/lib/amigaos.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/amigaos.h b/Utilities/cmcurl/lib/amigaos.h
index 9abfb59..7997ede 100644
--- a/Utilities/cmcurl/lib/amigaos.h
+++ b/Utilities/cmcurl/lib/amigaos.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/arpa_telnet.h b/Utilities/cmcurl/lib/arpa_telnet.h
index 523f7f5..de13738 100644
--- a/Utilities/cmcurl/lib/arpa_telnet.h
+++ b/Utilities/cmcurl/lib/arpa_telnet.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/asyn-ares.c b/Utilities/cmcurl/lib/asyn-ares.c
index 4436da3..19fe853 100644
--- a/Utilities/cmcurl/lib/asyn-ares.c
+++ b/Utilities/cmcurl/lib/asyn-ares.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/asyn-thread.c b/Utilities/cmcurl/lib/asyn-thread.c
index 705f0f6..4d7f860 100644
--- a/Utilities/cmcurl/lib/asyn-thread.c
+++ b/Utilities/cmcurl/lib/asyn-thread.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/asyn.h b/Utilities/cmcurl/lib/asyn.h
index 1aab21a..7e207c4 100644
--- a/Utilities/cmcurl/lib/asyn.h
+++ b/Utilities/cmcurl/lib/asyn.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/base64.c b/Utilities/cmcurl/lib/base64.c
index bacd627..e1b7b72 100644
--- a/Utilities/cmcurl/lib/base64.c
+++ b/Utilities/cmcurl/lib/base64.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/bufref.c b/Utilities/cmcurl/lib/bufref.c
index 91b0374..ce686b6 100644
--- a/Utilities/cmcurl/lib/bufref.c
+++ b/Utilities/cmcurl/lib/bufref.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2021 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/bufref.h b/Utilities/cmcurl/lib/bufref.h
index 96b818b..dd424f1 100644
--- a/Utilities/cmcurl/lib/bufref.h
+++ b/Utilities/cmcurl/lib/bufref.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2021 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/c-hyper.c b/Utilities/cmcurl/lib/c-hyper.c
index 65f5581..9c7632d 100644
--- a/Utilities/cmcurl/lib/c-hyper.c
+++ b/Utilities/cmcurl/lib/c-hyper.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -485,7 +485,7 @@
     if(k->upgr101 == UPGR101_WS) {
       if(http_status == 101) {
         /* verify the response */
-        result = Curl_ws_accept(data);
+        result = Curl_ws_accept(data, NULL, 0);
         if(result)
           return result;
       }
@@ -1128,6 +1128,16 @@
       goto error;
   }
 
+#ifdef HAVE_LIBZ
+  /* we only consider transfer-encoding magic if libz support is built-in */
+  result = Curl_transferencode(data);
+  if(result)
+    goto error;
+  result = Curl_hyper_header(data, headers, data->state.aptr.te);
+  if(result)
+    goto error;
+#endif
+
   if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) &&
      data->set.str[STRING_ENCODING]) {
     Curl_safefree(data->state.aptr.accept_encoding);
@@ -1144,16 +1154,6 @@
   else
     Curl_safefree(data->state.aptr.accept_encoding);
 
-#ifdef HAVE_LIBZ
-  /* we only consider transfer-encoding magic if libz support is built-in */
-  result = Curl_transferencode(data);
-  if(result)
-    goto error;
-  result = Curl_hyper_header(data, headers, data->state.aptr.te);
-  if(result)
-    goto error;
-#endif
-
   result = cookies(data, conn, headers);
   if(result)
     goto error;
diff --git a/Utilities/cmcurl/lib/c-hyper.h b/Utilities/cmcurl/lib/c-hyper.h
index 70507ad..4218cda 100644
--- a/Utilities/cmcurl/lib/c-hyper.h
+++ b/Utilities/cmcurl/lib/c-hyper.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/cf-https-connect.c b/Utilities/cmcurl/lib/cf-https-connect.c
new file mode 100644
index 0000000..ed70ad0
--- /dev/null
+++ b/Utilities/cmcurl/lib/cf-https-connect.c
@@ -0,0 +1,569 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER)
+
+#include "urldata.h"
+#include <curl/curl.h>
+#include "curl_log.h"
+#include "cfilters.h"
+#include "connect.h"
+#include "multiif.h"
+#include "cf-https-connect.h"
+#include "http2.h"
+#include "vquic/vquic.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+
+typedef enum {
+  CF_HC_INIT,
+  CF_HC_CONNECT,
+  CF_HC_SUCCESS,
+  CF_HC_FAILURE
+} cf_hc_state;
+
+struct cf_hc_baller {
+  const char *name;
+  struct Curl_cfilter *cf;
+  CURLcode result;
+  struct curltime started;
+  int reply_ms;
+  bool enabled;
+};
+
+static void cf_hc_baller_reset(struct cf_hc_baller *b,
+                               struct Curl_easy *data)
+{
+  if(b->cf) {
+    Curl_conn_cf_close(b->cf, data);
+    Curl_conn_cf_discard_chain(&b->cf, data);
+    b->cf = NULL;
+  }
+  b->result = CURLE_OK;
+  b->reply_ms = -1;
+}
+
+static bool cf_hc_baller_is_active(struct cf_hc_baller *b)
+{
+  return b->enabled && b->cf && !b->result;
+}
+
+static bool cf_hc_baller_has_started(struct cf_hc_baller *b)
+{
+  return !!b->cf;
+}
+
+static int cf_hc_baller_reply_ms(struct cf_hc_baller *b,
+                                 struct Curl_easy *data)
+{
+  if(b->reply_ms < 0)
+    b->cf->cft->query(b->cf, data, CF_QUERY_CONNECT_REPLY_MS,
+                      &b->reply_ms, NULL);
+  return b->reply_ms;
+}
+
+static bool cf_hc_baller_data_pending(struct cf_hc_baller *b,
+                                      const struct Curl_easy *data)
+{
+  return b->cf && !b->result && b->cf->cft->has_data_pending(b->cf, data);
+}
+
+struct cf_hc_ctx {
+  cf_hc_state state;
+  const struct Curl_dns_entry *remotehost;
+  struct curltime started;  /* when connect started */
+  CURLcode result;          /* overall result */
+  struct cf_hc_baller h3_baller;
+  struct cf_hc_baller h21_baller;
+  int soft_eyeballs_timeout_ms;
+  int hard_eyeballs_timeout_ms;
+};
+
+static void cf_hc_baller_init(struct cf_hc_baller *b,
+                              struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
+                              const char *name,
+                              int transport)
+{
+  struct cf_hc_ctx *ctx = cf->ctx;
+  struct Curl_cfilter *save = cf->next;
+
+  b->name = name;
+  cf->next = NULL;
+  b->started = Curl_now();
+  b->result = Curl_cf_setup_insert_after(cf, data, ctx->remotehost,
+                                         transport, CURL_CF_SSL_ENABLE);
+  b->cf = cf->next;
+  cf->next = save;
+}
+
+static CURLcode cf_hc_baller_connect(struct cf_hc_baller *b,
+                                     struct Curl_cfilter *cf,
+                                     struct Curl_easy *data,
+                                     bool *done)
+{
+  struct Curl_cfilter *save = cf->next;
+
+  cf->next = b->cf;
+  b->result = Curl_conn_cf_connect(cf->next, data, FALSE, done);
+  b->cf = cf->next; /* it might mutate */
+  cf->next = save;
+  return b->result;
+}
+
+static void cf_hc_reset(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_hc_ctx *ctx = cf->ctx;
+
+  if(ctx) {
+    cf_hc_baller_reset(&ctx->h3_baller, data);
+    cf_hc_baller_reset(&ctx->h21_baller, data);
+    ctx->state = CF_HC_INIT;
+    ctx->result = CURLE_OK;
+    ctx->hard_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout;
+    ctx->soft_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout / 2;
+  }
+}
+
+static CURLcode baller_connected(struct Curl_cfilter *cf,
+                                 struct Curl_easy *data,
+                                 struct cf_hc_baller *winner)
+{
+  struct cf_hc_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OK;
+
+  DEBUGASSERT(winner->cf);
+  if(winner != &ctx->h3_baller)
+    cf_hc_baller_reset(&ctx->h3_baller, data);
+  if(winner != &ctx->h21_baller)
+    cf_hc_baller_reset(&ctx->h21_baller, data);
+
+  DEBUGF(LOG_CF(data, cf, "connect+handshake %s: %dms, 1st data: %dms",
+                winner->name, (int)Curl_timediff(Curl_now(), winner->started),
+                cf_hc_baller_reply_ms(winner, data)));
+  cf->next = winner->cf;
+  winner->cf = NULL;
+
+  switch(cf->conn->alpn) {
+  case CURL_HTTP_VERSION_3:
+    infof(data, "using HTTP/3");
+    break;
+  case CURL_HTTP_VERSION_2:
+#ifdef USE_NGHTTP2
+    /* Using nghttp2, we add the filter "below" us, so when the conn
+     * closes, we tear it down for a fresh reconnect */
+    result = Curl_http2_switch_at(cf, data);
+    if(result) {
+      ctx->state = CF_HC_FAILURE;
+      ctx->result = result;
+      return result;
+    }
+#endif
+    infof(data, "using HTTP/2");
+    break;
+  case CURL_HTTP_VERSION_1_1:
+    infof(data, "using HTTP/1.1");
+    break;
+  default:
+    infof(data, "using HTTP/1.x");
+    break;
+  }
+  ctx->state = CF_HC_SUCCESS;
+  cf->connected = TRUE;
+  Curl_conn_cf_cntrl(cf->next, data, TRUE,
+                     CF_CTRL_CONN_INFO_UPDATE, 0, NULL);
+  return result;
+}
+
+
+static bool time_to_start_h21(struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
+                              struct curltime now)
+{
+  struct cf_hc_ctx *ctx = cf->ctx;
+  timediff_t elapsed_ms;
+
+  if(!ctx->h21_baller.enabled || cf_hc_baller_has_started(&ctx->h21_baller))
+    return FALSE;
+
+  if(!ctx->h3_baller.enabled || !cf_hc_baller_is_active(&ctx->h3_baller))
+    return TRUE;
+
+  elapsed_ms = Curl_timediff(now, ctx->started);
+  if(elapsed_ms >= ctx->hard_eyeballs_timeout_ms) {
+    DEBUGF(LOG_CF(data, cf, "hard timeout of %dms reached, starting h21",
+                  ctx->hard_eyeballs_timeout_ms));
+    return TRUE;
+  }
+
+  if(elapsed_ms >= ctx->soft_eyeballs_timeout_ms) {
+    if(cf_hc_baller_reply_ms(&ctx->h3_baller, data) < 0) {
+      DEBUGF(LOG_CF(data, cf, "soft timeout of %dms reached, h3 has not "
+                    "seen any data, starting h21",
+                    ctx->soft_eyeballs_timeout_ms));
+      return TRUE;
+    }
+    /* set the effective hard timeout again */
+    Curl_expire(data, ctx->hard_eyeballs_timeout_ms - elapsed_ms,
+                EXPIRE_ALPN_EYEBALLS);
+  }
+  return FALSE;
+}
+
+static CURLcode cf_hc_connect(struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
+                              bool blocking, bool *done)
+{
+  struct cf_hc_ctx *ctx = cf->ctx;
+  struct curltime now;
+  CURLcode result = CURLE_OK;
+
+  (void)blocking;
+  if(cf->connected) {
+    *done = TRUE;
+    return CURLE_OK;
+  }
+
+  *done = FALSE;
+  now = Curl_now();
+  switch(ctx->state) {
+  case CF_HC_INIT:
+    DEBUGASSERT(!ctx->h3_baller.cf);
+    DEBUGASSERT(!ctx->h21_baller.cf);
+    DEBUGASSERT(!cf->next);
+    DEBUGF(LOG_CF(data, cf, "connect, init"));
+    ctx->started = now;
+    if(ctx->h3_baller.enabled) {
+      cf_hc_baller_init(&ctx->h3_baller, cf, data, "h3", TRNSPRT_QUIC);
+      if(ctx->h21_baller.enabled)
+        Curl_expire(data, ctx->soft_eyeballs_timeout_ms, EXPIRE_ALPN_EYEBALLS);
+    }
+    else if(ctx->h21_baller.enabled)
+      cf_hc_baller_init(&ctx->h21_baller, cf, data, "h21",
+                       cf->conn->transport);
+    ctx->state = CF_HC_CONNECT;
+    /* FALLTHROUGH */
+
+  case CF_HC_CONNECT:
+    if(cf_hc_baller_is_active(&ctx->h3_baller)) {
+      result = cf_hc_baller_connect(&ctx->h3_baller, cf, data, done);
+      if(!result && *done) {
+        result = baller_connected(cf, data, &ctx->h3_baller);
+        goto out;
+      }
+    }
+
+    if(time_to_start_h21(cf, data, now)) {
+      cf_hc_baller_init(&ctx->h21_baller, cf, data, "h21",
+                        cf->conn->transport);
+    }
+
+    if(cf_hc_baller_is_active(&ctx->h21_baller)) {
+      DEBUGF(LOG_CF(data, cf, "connect, check h21"));
+      result = cf_hc_baller_connect(&ctx->h21_baller, cf, data, done);
+      if(!result && *done) {
+        result = baller_connected(cf, data, &ctx->h21_baller);
+        goto out;
+      }
+    }
+
+    if((!ctx->h3_baller.enabled || ctx->h3_baller.result) &&
+       (!ctx->h21_baller.enabled || ctx->h21_baller.result)) {
+      /* both failed or disabled. we give up */
+      DEBUGF(LOG_CF(data, cf, "connect, all failed"));
+      result = ctx->result = ctx->h3_baller.enabled?
+                              ctx->h3_baller.result : ctx->h21_baller.result;
+      ctx->state = CF_HC_FAILURE;
+      goto out;
+    }
+    result = CURLE_OK;
+    *done = FALSE;
+    break;
+
+  case CF_HC_FAILURE:
+    result = ctx->result;
+    cf->connected = FALSE;
+    *done = FALSE;
+    break;
+
+  case CF_HC_SUCCESS:
+    result = CURLE_OK;
+    cf->connected = TRUE;
+    *done = TRUE;
+    break;
+  }
+
+out:
+  DEBUGF(LOG_CF(data, cf, "connect -> %d, done=%d", result, *done));
+  return result;
+}
+
+static int cf_hc_get_select_socks(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  curl_socket_t *socks)
+{
+  struct cf_hc_ctx *ctx = cf->ctx;
+  size_t i, j, s;
+  int brc, rc = GETSOCK_BLANK;
+  curl_socket_t bsocks[MAX_SOCKSPEREASYHANDLE];
+  struct cf_hc_baller *ballers[2];
+
+  if(cf->connected)
+    return cf->next->cft->get_select_socks(cf->next, data, socks);
+
+  ballers[0] = &ctx->h3_baller;
+  ballers[1] = &ctx->h21_baller;
+  for(i = s = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) {
+    struct cf_hc_baller *b = ballers[i];
+    if(!cf_hc_baller_is_active(b))
+      continue;
+    brc = Curl_conn_cf_get_select_socks(b->cf, data, bsocks);
+    DEBUGF(LOG_CF(data, cf, "get_selected_socks(%s) -> %x", b->name, brc));
+    if(!brc)
+      continue;
+    for(j = 0; j < MAX_SOCKSPEREASYHANDLE && s < MAX_SOCKSPEREASYHANDLE; ++j) {
+      if((brc & GETSOCK_WRITESOCK(j)) || (brc & GETSOCK_READSOCK(j))) {
+        socks[s] = bsocks[j];
+        if(brc & GETSOCK_WRITESOCK(j))
+          rc |= GETSOCK_WRITESOCK(s);
+        if(brc & GETSOCK_READSOCK(j))
+          rc |= GETSOCK_READSOCK(s);
+        s++;
+      }
+    }
+  }
+  DEBUGF(LOG_CF(data, cf, "get_selected_socks -> %x", rc));
+  return rc;
+}
+
+static bool cf_hc_data_pending(struct Curl_cfilter *cf,
+                               const struct Curl_easy *data)
+{
+  struct cf_hc_ctx *ctx = cf->ctx;
+
+  if(cf->connected)
+    return cf->next->cft->has_data_pending(cf->next, data);
+
+  DEBUGF(LOG_CF((struct Curl_easy *)data, cf, "data_pending"));
+  return cf_hc_baller_data_pending(&ctx->h3_baller, data)
+         || cf_hc_baller_data_pending(&ctx->h21_baller, data);
+}
+
+static struct curltime get_max_baller_time(struct Curl_cfilter *cf,
+                                          struct Curl_easy *data,
+                                          int query)
+{
+  struct cf_hc_ctx *ctx = cf->ctx;
+  struct Curl_cfilter *cfb;
+  struct curltime t, tmax;
+
+  memset(&tmax, 0, sizeof(tmax));
+  memset(&t, 0, sizeof(t));
+  cfb = ctx->h21_baller.enabled? ctx->h21_baller.cf : NULL;
+  if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) {
+    if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0)
+      tmax = t;
+  }
+  memset(&t, 0, sizeof(t));
+  cfb = ctx->h3_baller.enabled? ctx->h3_baller.cf : NULL;
+  if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) {
+    if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0)
+      tmax = t;
+  }
+  return tmax;
+}
+
+static CURLcode cf_hc_query(struct Curl_cfilter *cf,
+                            struct Curl_easy *data,
+                            int query, int *pres1, void *pres2)
+{
+  if(!cf->connected) {
+    switch(query) {
+    case CF_QUERY_TIMER_CONNECT: {
+      struct curltime *when = pres2;
+      *when = get_max_baller_time(cf, data, CF_QUERY_TIMER_CONNECT);
+      return CURLE_OK;
+    }
+    case CF_QUERY_TIMER_APPCONNECT: {
+      struct curltime *when = pres2;
+      *when = get_max_baller_time(cf, data, CF_QUERY_TIMER_APPCONNECT);
+      return CURLE_OK;
+    }
+    default:
+      break;
+    }
+  }
+  return cf->next?
+    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
+    CURLE_UNKNOWN_OPTION;
+}
+
+static void cf_hc_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  DEBUGF(LOG_CF(data, cf, "close"));
+  cf_hc_reset(cf, data);
+  cf->connected = FALSE;
+
+  if(cf->next) {
+    cf->next->cft->close(cf->next, data);
+    Curl_conn_cf_discard_chain(&cf->next, data);
+  }
+}
+
+static void cf_hc_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_hc_ctx *ctx = cf->ctx;
+
+  (void)data;
+  DEBUGF(LOG_CF(data, cf, "destroy"));
+  cf_hc_reset(cf, data);
+  Curl_safefree(ctx);
+}
+
+struct Curl_cftype Curl_cft_http_connect = {
+  "HTTPS-CONNECT",
+  0,
+  CURL_LOG_DEFAULT,
+  cf_hc_destroy,
+  cf_hc_connect,
+  cf_hc_close,
+  Curl_cf_def_get_host,
+  cf_hc_get_select_socks,
+  cf_hc_data_pending,
+  Curl_cf_def_send,
+  Curl_cf_def_recv,
+  Curl_cf_def_cntrl,
+  Curl_cf_def_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  cf_hc_query,
+};
+
+static CURLcode cf_hc_create(struct Curl_cfilter **pcf,
+                             struct Curl_easy *data,
+                             const struct Curl_dns_entry *remotehost,
+                             bool try_h3, bool try_h21)
+{
+  struct Curl_cfilter *cf = NULL;
+  struct cf_hc_ctx *ctx;
+  CURLcode result = CURLE_OK;
+
+  (void)data;
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+  ctx->remotehost = remotehost;
+  ctx->h3_baller.enabled = try_h3;
+  ctx->h21_baller.enabled = try_h21;
+
+  result = Curl_cf_create(&cf, &Curl_cft_http_connect, ctx);
+  if(result)
+    goto out;
+  ctx = NULL;
+  cf_hc_reset(cf, data);
+
+out:
+  *pcf = result? NULL : cf;
+  free(ctx);
+  return result;
+}
+
+CURLcode Curl_cf_http_connect_add(struct Curl_easy *data,
+                                  struct connectdata *conn,
+                                  int sockindex,
+                                  const struct Curl_dns_entry *remotehost,
+                                  bool try_h3, bool try_h21)
+{
+  struct Curl_cfilter *cf;
+  CURLcode result = CURLE_OK;
+
+  DEBUGASSERT(data);
+  result = cf_hc_create(&cf, data, remotehost, try_h3, try_h21);
+  if(result)
+    goto out;
+  Curl_conn_cf_add(data, conn, sockindex, cf);
+out:
+  return result;
+}
+
+CURLcode
+Curl_cf_http_connect_insert_after(struct Curl_cfilter *cf_at,
+                                  struct Curl_easy *data,
+                                  const struct Curl_dns_entry *remotehost,
+                                  bool try_h3, bool try_h21)
+{
+  struct Curl_cfilter *cf;
+  CURLcode result;
+
+  DEBUGASSERT(data);
+  result = cf_hc_create(&cf, data, remotehost, try_h3, try_h21);
+  if(result)
+    goto out;
+  Curl_conn_cf_insert_after(cf_at, cf);
+out:
+  return result;
+}
+
+CURLcode Curl_cf_https_setup(struct Curl_easy *data,
+                             struct connectdata *conn,
+                             int sockindex,
+                             const struct Curl_dns_entry *remotehost)
+{
+  bool try_h3 = FALSE, try_h21 = TRUE; /* defaults, for now */
+  CURLcode result = CURLE_OK;
+
+  (void)sockindex;
+  (void)remotehost;
+
+  if(!conn->bits.tls_enable_alpn)
+    goto out;
+
+  if(data->state.httpwant == CURL_HTTP_VERSION_3ONLY) {
+    result = Curl_conn_may_http3(data, conn);
+    if(result) /* can't do it */
+      goto out;
+    try_h3 = TRUE;
+    try_h21 = FALSE;
+  }
+  else if(data->state.httpwant >= CURL_HTTP_VERSION_3) {
+    /* We assume that silently not even trying H3 is ok here */
+    /* TODO: should we fail instead? */
+    try_h3 = (Curl_conn_may_http3(data, conn) == CURLE_OK);
+    try_h21 = TRUE;
+  }
+
+  result = Curl_cf_http_connect_add(data, conn, sockindex, remotehost,
+                                    try_h3, try_h21);
+out:
+  return result;
+}
+
+#endif /* !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) */
diff --git a/Utilities/cmcurl/lib/cf-https-connect.h b/Utilities/cmcurl/lib/cf-https-connect.h
new file mode 100644
index 0000000..6a39527
--- /dev/null
+++ b/Utilities/cmcurl/lib/cf-https-connect.h
@@ -0,0 +1,58 @@
+#ifndef HEADER_CURL_CF_HTTP_H
+#define HEADER_CURL_CF_HTTP_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER)
+
+struct Curl_cfilter;
+struct Curl_easy;
+struct connectdata;
+struct Curl_cftype;
+struct Curl_dns_entry;
+
+extern struct Curl_cftype Curl_cft_http_connect;
+
+CURLcode Curl_cf_http_connect_add(struct Curl_easy *data,
+                                  struct connectdata *conn,
+                                  int sockindex,
+                                  const struct Curl_dns_entry *remotehost,
+                                  bool try_h3, bool try_h21);
+
+CURLcode
+Curl_cf_http_connect_insert_after(struct Curl_cfilter *cf_at,
+                                  struct Curl_easy *data,
+                                  const struct Curl_dns_entry *remotehost,
+                                  bool try_h3, bool try_h21);
+
+
+CURLcode Curl_cf_https_setup(struct Curl_easy *data,
+                             struct connectdata *conn,
+                             int sockindex,
+                             const struct Curl_dns_entry *remotehost);
+
+
+#endif /* !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) */
+#endif /* HEADER_CURL_CF_HTTP_H */
diff --git a/Utilities/cmcurl/lib/cf-socket.c b/Utilities/cmcurl/lib/cf-socket.c
new file mode 100644
index 0000000..6d9ace4
--- /dev/null
+++ b/Utilities/cmcurl/lib/cf-socket.c
@@ -0,0 +1,1921 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h> /* <netinet/tcp.h> may need it */
+#endif
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h> /* for sockaddr_un */
+#endif
+#ifdef HAVE_LINUX_TCP_H
+#include <linux/tcp.h>
+#elif defined(HAVE_NETINET_TCP_H)
+#include <netinet/tcp.h>
+#endif
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+
+#ifdef __VMS
+#include <in.h>
+#include <inet.h>
+#endif
+
+#include "urldata.h"
+#include "sendf.h"
+#include "if2ip.h"
+#include "strerror.h"
+#include "cfilters.h"
+#include "cf-socket.h"
+#include "connect.h"
+#include "select.h"
+#include "url.h" /* for Curl_safefree() */
+#include "multiif.h"
+#include "sockaddr.h" /* required for Curl_sockaddr_storage */
+#include "inet_ntop.h"
+#include "inet_pton.h"
+#include "progress.h"
+#include "warnless.h"
+#include "conncache.h"
+#include "multihandle.h"
+#include "share.h"
+#include "version_win32.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+
+static void tcpnodelay(struct Curl_easy *data, curl_socket_t sockfd)
+{
+#if defined(TCP_NODELAY)
+  curl_socklen_t onoff = (curl_socklen_t) 1;
+  int level = IPPROTO_TCP;
+#if !defined(CURL_DISABLE_VERBOSE_STRINGS)
+  char buffer[STRERROR_LEN];
+#else
+  (void) data;
+#endif
+
+  if(setsockopt(sockfd, level, TCP_NODELAY, (void *)&onoff,
+                sizeof(onoff)) < 0)
+    infof(data, "Could not set TCP_NODELAY: %s",
+          Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
+#else
+  (void)data;
+  (void)sockfd;
+#endif
+}
+
+#ifdef SO_NOSIGPIPE
+/* The preferred method on Mac OS X (10.2 and later) to prevent SIGPIPEs when
+   sending data to a dead peer (instead of relying on the 4th argument to send
+   being MSG_NOSIGNAL). Possibly also existing and in use on other BSD
+   systems? */
+static void nosigpipe(struct Curl_easy *data,
+                      curl_socket_t sockfd)
+{
+  int onoff = 1;
+  if(setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&onoff,
+                sizeof(onoff)) < 0) {
+#if !defined(CURL_DISABLE_VERBOSE_STRINGS)
+    char buffer[STRERROR_LEN];
+    infof(data, "Could not set SO_NOSIGPIPE: %s",
+          Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
+#endif
+  }
+}
+#else
+#define nosigpipe(x,y) Curl_nop_stmt
+#endif
+
+#if defined(__DragonFly__) || defined(HAVE_WINSOCK2_H)
+/* DragonFlyBSD and Windows use millisecond units */
+#define KEEPALIVE_FACTOR(x) (x *= 1000)
+#else
+#define KEEPALIVE_FACTOR(x)
+#endif
+
+#if defined(HAVE_WINSOCK2_H) && !defined(SIO_KEEPALIVE_VALS)
+#define SIO_KEEPALIVE_VALS    _WSAIOW(IOC_VENDOR,4)
+
+struct tcp_keepalive {
+  u_long onoff;
+  u_long keepalivetime;
+  u_long keepaliveinterval;
+};
+#endif
+
+static void
+tcpkeepalive(struct Curl_easy *data,
+             curl_socket_t sockfd)
+{
+  int optval = data->set.tcp_keepalive?1:0;
+
+  /* only set IDLE and INTVL if setting KEEPALIVE is successful */
+  if(setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE,
+        (void *)&optval, sizeof(optval)) < 0) {
+    infof(data, "Failed to set SO_KEEPALIVE on fd %d", sockfd);
+  }
+  else {
+#if defined(SIO_KEEPALIVE_VALS)
+    struct tcp_keepalive vals;
+    DWORD dummy;
+    vals.onoff = 1;
+    optval = curlx_sltosi(data->set.tcp_keepidle);
+    KEEPALIVE_FACTOR(optval);
+    vals.keepalivetime = optval;
+    optval = curlx_sltosi(data->set.tcp_keepintvl);
+    KEEPALIVE_FACTOR(optval);
+    vals.keepaliveinterval = optval;
+    if(WSAIoctl(sockfd, SIO_KEEPALIVE_VALS, (LPVOID) &vals, sizeof(vals),
+                NULL, 0, &dummy, NULL, NULL) != 0) {
+      infof(data, "Failed to set SIO_KEEPALIVE_VALS on fd %d: %d",
+            (int)sockfd, WSAGetLastError());
+    }
+#else
+#ifdef TCP_KEEPIDLE
+    optval = curlx_sltosi(data->set.tcp_keepidle);
+    KEEPALIVE_FACTOR(optval);
+    if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE,
+          (void *)&optval, sizeof(optval)) < 0) {
+      infof(data, "Failed to set TCP_KEEPIDLE on fd %d", sockfd);
+    }
+#elif defined(TCP_KEEPALIVE)
+    /* Mac OS X style */
+    optval = curlx_sltosi(data->set.tcp_keepidle);
+    KEEPALIVE_FACTOR(optval);
+    if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE,
+      (void *)&optval, sizeof(optval)) < 0) {
+      infof(data, "Failed to set TCP_KEEPALIVE on fd %d", sockfd);
+    }
+#endif
+#ifdef TCP_KEEPINTVL
+    optval = curlx_sltosi(data->set.tcp_keepintvl);
+    KEEPALIVE_FACTOR(optval);
+    if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL,
+          (void *)&optval, sizeof(optval)) < 0) {
+      infof(data, "Failed to set TCP_KEEPINTVL on fd %d", sockfd);
+    }
+#endif
+#endif
+  }
+}
+
+void Curl_sock_assign_addr(struct Curl_sockaddr_ex *dest,
+                           const struct Curl_addrinfo *ai,
+                           int transport)
+{
+  /*
+   * The Curl_sockaddr_ex structure is basically libcurl's external API
+   * curl_sockaddr structure with enough space available to directly hold
+   * any protocol-specific address structures. The variable declared here
+   * will be used to pass / receive data to/from the fopensocket callback
+   * if this has been set, before that, it is initialized from parameters.
+   */
+  dest->family = ai->ai_family;
+  switch(transport) {
+  case TRNSPRT_TCP:
+    dest->socktype = SOCK_STREAM;
+    dest->protocol = IPPROTO_TCP;
+    break;
+  case TRNSPRT_UNIX:
+    dest->socktype = SOCK_STREAM;
+    dest->protocol = IPPROTO_IP;
+    break;
+  default: /* UDP and QUIC */
+    dest->socktype = SOCK_DGRAM;
+    dest->protocol = IPPROTO_UDP;
+    break;
+  }
+  dest->addrlen = ai->ai_addrlen;
+
+  if(dest->addrlen > sizeof(struct Curl_sockaddr_storage))
+     dest->addrlen = sizeof(struct Curl_sockaddr_storage);
+  memcpy(&dest->sa_addr, ai->ai_addr, dest->addrlen);
+}
+
+static CURLcode socket_open(struct Curl_easy *data,
+                            struct Curl_sockaddr_ex *addr,
+                            curl_socket_t *sockfd)
+{
+  DEBUGASSERT(data);
+  DEBUGASSERT(data->conn);
+  if(data->set.fopensocket) {
+   /*
+    * If the opensocket callback is set, all the destination address
+    * information is passed to the callback. Depending on this information the
+    * callback may opt to abort the connection, this is indicated returning
+    * CURL_SOCKET_BAD; otherwise it will return a not-connected socket. When
+    * the callback returns a valid socket the destination address information
+    * might have been changed and this 'new' address will actually be used
+    * here to connect.
+    */
+    Curl_set_in_callback(data, true);
+    *sockfd = data->set.fopensocket(data->set.opensocket_client,
+                                    CURLSOCKTYPE_IPCXN,
+                                    (struct curl_sockaddr *)addr);
+    Curl_set_in_callback(data, false);
+  }
+  else {
+    /* opensocket callback not set, so simply create the socket now */
+    *sockfd = socket(addr->family, addr->socktype, addr->protocol);
+  }
+
+  if(*sockfd == CURL_SOCKET_BAD)
+    /* no socket, no connection */
+    return CURLE_COULDNT_CONNECT;
+
+#if defined(ENABLE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID)
+  if(data->conn->scope_id && (addr->family == AF_INET6)) {
+    struct sockaddr_in6 * const sa6 = (void *)&addr->sa_addr;
+    sa6->sin6_scope_id = data->conn->scope_id;
+  }
+#endif
+  return CURLE_OK;
+}
+
+/*
+ * Create a socket based on info from 'conn' and 'ai'.
+ *
+ * 'addr' should be a pointer to the correct struct to get data back, or NULL.
+ * 'sockfd' must be a pointer to a socket descriptor.
+ *
+ * If the open socket callback is set, used that!
+ *
+ */
+CURLcode Curl_socket_open(struct Curl_easy *data,
+                            const struct Curl_addrinfo *ai,
+                            struct Curl_sockaddr_ex *addr,
+                            int transport,
+                            curl_socket_t *sockfd)
+{
+  struct Curl_sockaddr_ex dummy;
+
+  if(!addr)
+    /* if the caller doesn't want info back, use a local temp copy */
+    addr = &dummy;
+
+  Curl_sock_assign_addr(addr, ai, transport);
+  return socket_open(data, addr, sockfd);
+}
+
+static int socket_close(struct Curl_easy *data, struct connectdata *conn,
+                        int use_callback, curl_socket_t sock)
+{
+  if(use_callback && conn && conn->fclosesocket) {
+    int rc;
+    Curl_multi_closed(data, sock);
+    Curl_set_in_callback(data, true);
+    rc = conn->fclosesocket(conn->closesocket_client, sock);
+    Curl_set_in_callback(data, false);
+    return rc;
+  }
+
+  if(conn)
+    /* tell the multi-socket code about this */
+    Curl_multi_closed(data, sock);
+
+  sclose(sock);
+
+  return 0;
+}
+
+/*
+ * Close a socket.
+ *
+ * 'conn' can be NULL, beware!
+ */
+int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn,
+                      curl_socket_t sock)
+{
+  return socket_close(data, conn, FALSE, sock);
+}
+
+#ifdef USE_WINSOCK
+/* When you run a program that uses the Windows Sockets API, you may
+   experience slow performance when you copy data to a TCP server.
+
+   https://support.microsoft.com/kb/823764
+
+   Work-around: Make the Socket Send Buffer Size Larger Than the Program Send
+   Buffer Size
+
+   The problem described in this knowledge-base is applied only to pre-Vista
+   Windows.  Following function trying to detect OS version and skips
+   SO_SNDBUF adjustment for Windows Vista and above.
+*/
+#define DETECT_OS_NONE 0
+#define DETECT_OS_PREVISTA 1
+#define DETECT_OS_VISTA_OR_LATER 2
+
+void Curl_sndbufset(curl_socket_t sockfd)
+{
+  int val = CURL_MAX_WRITE_SIZE + 32;
+  int curval = 0;
+  int curlen = sizeof(curval);
+
+  static int detectOsState = DETECT_OS_NONE;
+
+  if(detectOsState == DETECT_OS_NONE) {
+    if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT,
+                                    VERSION_GREATER_THAN_EQUAL))
+      detectOsState = DETECT_OS_VISTA_OR_LATER;
+    else
+      detectOsState = DETECT_OS_PREVISTA;
+  }
+
+  if(detectOsState == DETECT_OS_VISTA_OR_LATER)
+    return;
+
+  if(getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char *)&curval, &curlen) == 0)
+    if(curval > val)
+      return;
+
+  setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&val, sizeof(val));
+}
+#endif
+
+static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
+                          curl_socket_t sockfd, int af, unsigned int scope)
+{
+  struct Curl_sockaddr_storage sa;
+  struct sockaddr *sock = (struct sockaddr *)&sa;  /* bind to this address */
+  curl_socklen_t sizeof_sa = 0; /* size of the data sock points to */
+  struct sockaddr_in *si4 = (struct sockaddr_in *)&sa;
+#ifdef ENABLE_IPV6
+  struct sockaddr_in6 *si6 = (struct sockaddr_in6 *)&sa;
+#endif
+
+  struct Curl_dns_entry *h = NULL;
+  unsigned short port = data->set.localport; /* use this port number, 0 for
+                                                "random" */
+  /* how many port numbers to try to bind to, increasing one at a time */
+  int portnum = data->set.localportrange;
+  const char *dev = data->set.str[STRING_DEVICE];
+  int error;
+#ifdef IP_BIND_ADDRESS_NO_PORT
+  int on = 1;
+#endif
+#ifndef ENABLE_IPV6
+  (void)scope;
+#endif
+
+  /*************************************************************
+   * Select device to bind socket to
+   *************************************************************/
+  if(!dev && !port)
+    /* no local kind of binding was requested */
+    return CURLE_OK;
+
+  memset(&sa, 0, sizeof(struct Curl_sockaddr_storage));
+
+  if(dev && (strlen(dev)<255) ) {
+    char myhost[256] = "";
+    int done = 0; /* -1 for error, 1 for address found */
+    bool is_interface = FALSE;
+    bool is_host = FALSE;
+    static const char *if_prefix = "if!";
+    static const char *host_prefix = "host!";
+
+    if(strncmp(if_prefix, dev, strlen(if_prefix)) == 0) {
+      dev += strlen(if_prefix);
+      is_interface = TRUE;
+    }
+    else if(strncmp(host_prefix, dev, strlen(host_prefix)) == 0) {
+      dev += strlen(host_prefix);
+      is_host = TRUE;
+    }
+
+    /* interface */
+    if(!is_host) {
+#ifdef SO_BINDTODEVICE
+      /* I am not sure any other OSs than Linux that provide this feature,
+       * and at the least I cannot test. --Ben
+       *
+       * This feature allows one to tightly bind the local socket to a
+       * particular interface.  This will force even requests to other
+       * local interfaces to go out the external interface.
+       *
+       *
+       * Only bind to the interface when specified as interface, not just
+       * as a hostname or ip address.
+       *
+       * interface might be a VRF, eg: vrf-blue, which means it cannot be
+       * converted to an IP address and would fail Curl_if2ip. Simply try
+       * to use it straight away.
+       */
+      if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE,
+                    dev, (curl_socklen_t)strlen(dev) + 1) == 0) {
+        /* This is typically "errno 1, error: Operation not permitted" if
+         * you're not running as root or another suitable privileged
+         * user.
+         * If it succeeds it means the parameter was a valid interface and
+         * not an IP address. Return immediately.
+         */
+        return CURLE_OK;
+      }
+#endif
+
+      switch(Curl_if2ip(af,
+#ifdef ENABLE_IPV6
+                        scope, conn->scope_id,
+#endif
+                        dev, myhost, sizeof(myhost))) {
+        case IF2IP_NOT_FOUND:
+          if(is_interface) {
+            /* Do not fall back to treating it as a host name */
+            failf(data, "Couldn't bind to interface '%s'", dev);
+            return CURLE_INTERFACE_FAILED;
+          }
+          break;
+        case IF2IP_AF_NOT_SUPPORTED:
+          /* Signal the caller to try another address family if available */
+          return CURLE_UNSUPPORTED_PROTOCOL;
+        case IF2IP_FOUND:
+          is_interface = TRUE;
+          /*
+           * We now have the numerical IP address in the 'myhost' buffer
+           */
+          infof(data, "Local Interface %s is ip %s using address family %i",
+                dev, myhost, af);
+          done = 1;
+          break;
+      }
+    }
+    if(!is_interface) {
+      /*
+       * This was not an interface, resolve the name as a host name
+       * or IP number
+       *
+       * Temporarily force name resolution to use only the address type
+       * of the connection. The resolve functions should really be changed
+       * to take a type parameter instead.
+       */
+      unsigned char ipver = conn->ip_version;
+      int rc;
+
+      if(af == AF_INET)
+        conn->ip_version = CURL_IPRESOLVE_V4;
+#ifdef ENABLE_IPV6
+      else if(af == AF_INET6)
+        conn->ip_version = CURL_IPRESOLVE_V6;
+#endif
+
+      rc = Curl_resolv(data, dev, 80, FALSE, &h);
+      if(rc == CURLRESOLV_PENDING)
+        (void)Curl_resolver_wait_resolv(data, &h);
+      conn->ip_version = ipver;
+
+      if(h) {
+        /* convert the resolved address, sizeof myhost >= INET_ADDRSTRLEN */
+        Curl_printable_address(h->addr, myhost, sizeof(myhost));
+        infof(data, "Name '%s' family %i resolved to '%s' family %i",
+              dev, af, myhost, h->addr->ai_family);
+        Curl_resolv_unlock(data, h);
+        if(af != h->addr->ai_family) {
+          /* bad IP version combo, signal the caller to try another address
+             family if available */
+          return CURLE_UNSUPPORTED_PROTOCOL;
+        }
+        done = 1;
+      }
+      else {
+        /*
+         * provided dev was no interface (or interfaces are not supported
+         * e.g. solaris) no ip address and no domain we fail here
+         */
+        done = -1;
+      }
+    }
+
+    if(done > 0) {
+#ifdef ENABLE_IPV6
+      /* IPv6 address */
+      if(af == AF_INET6) {
+#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
+        char *scope_ptr = strchr(myhost, '%');
+        if(scope_ptr)
+          *(scope_ptr++) = '\0';
+#endif
+        if(Curl_inet_pton(AF_INET6, myhost, &si6->sin6_addr) > 0) {
+          si6->sin6_family = AF_INET6;
+          si6->sin6_port = htons(port);
+#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
+          if(scope_ptr) {
+            /* The "myhost" string either comes from Curl_if2ip or from
+               Curl_printable_address. The latter returns only numeric scope
+               IDs and the former returns none at all.  So the scope ID, if
+               present, is known to be numeric */
+            unsigned long scope_id = strtoul(scope_ptr, NULL, 10);
+            if(scope_id > UINT_MAX)
+              return CURLE_UNSUPPORTED_PROTOCOL;
+
+            si6->sin6_scope_id = (unsigned int)scope_id;
+          }
+#endif
+        }
+        sizeof_sa = sizeof(struct sockaddr_in6);
+      }
+      else
+#endif
+      /* IPv4 address */
+      if((af == AF_INET) &&
+         (Curl_inet_pton(AF_INET, myhost, &si4->sin_addr) > 0)) {
+        si4->sin_family = AF_INET;
+        si4->sin_port = htons(port);
+        sizeof_sa = sizeof(struct sockaddr_in);
+      }
+    }
+
+    if(done < 1) {
+      /* errorbuf is set false so failf will overwrite any message already in
+         the error buffer, so the user receives this error message instead of a
+         generic resolve error. */
+      data->state.errorbuf = FALSE;
+      failf(data, "Couldn't bind to '%s'", dev);
+      return CURLE_INTERFACE_FAILED;
+    }
+  }
+  else {
+    /* no device was given, prepare sa to match af's needs */
+#ifdef ENABLE_IPV6
+    if(af == AF_INET6) {
+      si6->sin6_family = AF_INET6;
+      si6->sin6_port = htons(port);
+      sizeof_sa = sizeof(struct sockaddr_in6);
+    }
+    else
+#endif
+    if(af == AF_INET) {
+      si4->sin_family = AF_INET;
+      si4->sin_port = htons(port);
+      sizeof_sa = sizeof(struct sockaddr_in);
+    }
+  }
+#ifdef IP_BIND_ADDRESS_NO_PORT
+  (void)setsockopt(sockfd, SOL_IP, IP_BIND_ADDRESS_NO_PORT, &on, sizeof(on));
+#endif
+  for(;;) {
+    if(bind(sockfd, sock, sizeof_sa) >= 0) {
+      /* we succeeded to bind */
+      struct Curl_sockaddr_storage add;
+      curl_socklen_t size = sizeof(add);
+      memset(&add, 0, sizeof(struct Curl_sockaddr_storage));
+      if(getsockname(sockfd, (struct sockaddr *) &add, &size) < 0) {
+        char buffer[STRERROR_LEN];
+        data->state.os_errno = error = SOCKERRNO;
+        failf(data, "getsockname() failed with errno %d: %s",
+              error, Curl_strerror(error, buffer, sizeof(buffer)));
+        return CURLE_INTERFACE_FAILED;
+      }
+      infof(data, "Local port: %hu", port);
+      conn->bits.bound = TRUE;
+      return CURLE_OK;
+    }
+
+    if(--portnum > 0) {
+      port++; /* try next port */
+      if(port == 0)
+        break;
+      infof(data, "Bind to local port %hu failed, trying next", port - 1);
+      /* We re-use/clobber the port variable here below */
+      if(sock->sa_family == AF_INET)
+        si4->sin_port = ntohs(port);
+#ifdef ENABLE_IPV6
+      else
+        si6->sin6_port = ntohs(port);
+#endif
+    }
+    else
+      break;
+  }
+  {
+    char buffer[STRERROR_LEN];
+    data->state.os_errno = error = SOCKERRNO;
+    failf(data, "bind failed with errno %d: %s",
+          error, Curl_strerror(error, buffer, sizeof(buffer)));
+  }
+
+  return CURLE_INTERFACE_FAILED;
+}
+
+/*
+ * verifyconnect() returns TRUE if the connect really has happened.
+ */
+static bool verifyconnect(curl_socket_t sockfd, int *error)
+{
+  bool rc = TRUE;
+#ifdef SO_ERROR
+  int err = 0;
+  curl_socklen_t errSize = sizeof(err);
+
+#ifdef WIN32
+  /*
+   * In October 2003 we effectively nullified this function on Windows due to
+   * problems with it using all CPU in multi-threaded cases.
+   *
+   * In May 2004, we bring it back to offer more info back on connect failures.
+   * Gisle Vanem could reproduce the former problems with this function, but
+   * could avoid them by adding this SleepEx() call below:
+   *
+   *    "I don't have Rational Quantify, but the hint from his post was
+   *    ntdll::NtRemoveIoCompletion(). So I'd assume the SleepEx (or maybe
+   *    just Sleep(0) would be enough?) would release whatever
+   *    mutex/critical-section the ntdll call is waiting on.
+   *
+   *    Someone got to verify this on Win-NT 4.0, 2000."
+   */
+
+#ifdef _WIN32_WCE
+  Sleep(0);
+#else
+  SleepEx(0, FALSE);
+#endif
+
+#endif
+
+  if(0 != getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&err, &errSize))
+    err = SOCKERRNO;
+#ifdef _WIN32_WCE
+  /* Old WinCE versions don't support SO_ERROR */
+  if(WSAENOPROTOOPT == err) {
+    SET_SOCKERRNO(0);
+    err = 0;
+  }
+#endif
+#if defined(EBADIOCTL) && defined(__minix)
+  /* Minix 3.1.x doesn't support getsockopt on UDP sockets */
+  if(EBADIOCTL == err) {
+    SET_SOCKERRNO(0);
+    err = 0;
+  }
+#endif
+  if((0 == err) || (EISCONN == err))
+    /* we are connected, awesome! */
+    rc = TRUE;
+  else
+    /* This wasn't a successful connect */
+    rc = FALSE;
+  if(error)
+    *error = err;
+#else
+  (void)sockfd;
+  if(error)
+    *error = SOCKERRNO;
+#endif
+  return rc;
+}
+
+CURLcode Curl_socket_connect_result(struct Curl_easy *data,
+                                    const char *ipaddress, int error)
+{
+  char buffer[STRERROR_LEN];
+
+  switch(error) {
+  case EINPROGRESS:
+  case EWOULDBLOCK:
+#if defined(EAGAIN)
+#if (EAGAIN) != (EWOULDBLOCK)
+    /* On some platforms EAGAIN and EWOULDBLOCK are the
+     * same value, and on others they are different, hence
+     * the odd #if
+     */
+  case EAGAIN:
+#endif
+#endif
+    return CURLE_OK;
+
+  default:
+    /* unknown error, fallthrough and try another address! */
+    infof(data, "Immediate connect fail for %s: %s",
+          ipaddress, Curl_strerror(error, buffer, sizeof(buffer)));
+    data->state.os_errno = error;
+    /* connect failed */
+    return CURLE_COULDNT_CONNECT;
+  }
+}
+
+#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
+struct io_buffer {
+  char *bufr;
+  size_t allc;           /* size of the current allocation */
+  size_t head;           /* bufr index for next read */
+  size_t tail;           /* bufr index for next write */
+};
+
+static void io_buffer_reset(struct io_buffer *iob)
+{
+  if(iob->bufr)
+    free(iob->bufr);
+  memset(iob, 0, sizeof(*iob));
+}
+#endif /* USE_RECV_BEFORE_SEND_WORKAROUND */
+
+struct cf_socket_ctx {
+  int transport;
+  struct Curl_sockaddr_ex addr;      /* address to connect to */
+  curl_socket_t sock;                /* current attempt socket */
+#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
+  struct io_buffer recv_buffer;
+#endif
+  char r_ip[MAX_IPADR_LEN];          /* remote IP as string */
+  int r_port;                        /* remote port number */
+  char l_ip[MAX_IPADR_LEN];          /* local IP as string */
+  int l_port;                        /* local port number */
+  struct curltime started_at;        /* when socket was created */
+  struct curltime connected_at;      /* when socket connected/got first byte */
+  struct curltime first_byte_at;     /* when first byte was recvd */
+  int error;                         /* errno of last failure or 0 */
+  BIT(got_first_byte);               /* if first byte was received */
+  BIT(accepted);                     /* socket was accepted, not connected */
+  BIT(active);
+};
+
+static void cf_socket_ctx_init(struct cf_socket_ctx *ctx,
+                               const struct Curl_addrinfo *ai,
+                               int transport)
+{
+  memset(ctx, 0, sizeof(*ctx));
+  ctx->sock = CURL_SOCKET_BAD;
+  ctx->transport = transport;
+  Curl_sock_assign_addr(&ctx->addr, ai, transport);
+}
+
+static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+
+  if(ctx && CURL_SOCKET_BAD != ctx->sock) {
+    if(ctx->active) {
+      /* We share our socket at cf->conn->sock[cf->sockindex] when active.
+       * If it is no longer there, someone has stolen (and hopefully
+       * closed it) and we just forget about it.
+       */
+      if(ctx->sock == cf->conn->sock[cf->sockindex]) {
+        DEBUGF(LOG_CF(data, cf, "cf_socket_close(%d, active)",
+                     (int)ctx->sock));
+        socket_close(data, cf->conn, !ctx->accepted, ctx->sock);
+        cf->conn->sock[cf->sockindex] = CURL_SOCKET_BAD;
+      }
+      else {
+        DEBUGF(LOG_CF(data, cf, "cf_socket_close(%d) no longer at "
+                      "conn->sock[], discarding", (int)ctx->sock));
+        /* TODO: we do not want this to happen. Need to check which
+         * code is messing with conn->sock[cf->sockindex] */
+      }
+      ctx->sock = CURL_SOCKET_BAD;
+      if(cf->sockindex == FIRSTSOCKET)
+        cf->conn->remote_addr = NULL;
+    }
+    else {
+      /* this is our local socket, we did never publish it */
+      DEBUGF(LOG_CF(data, cf, "cf_socket_close(%d, not active)",
+                    (int)ctx->sock));
+      sclose(ctx->sock);
+      ctx->sock = CURL_SOCKET_BAD;
+    }
+#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
+    io_buffer_reset(&ctx->recv_buffer);
+#endif
+    ctx->active = FALSE;
+    memset(&ctx->started_at, 0, sizeof(ctx->started_at));
+    memset(&ctx->connected_at, 0, sizeof(ctx->connected_at));
+  }
+
+  cf->connected = FALSE;
+}
+
+static void cf_socket_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+
+  cf_socket_close(cf, data);
+  DEBUGF(LOG_CF(data, cf, "destroy"));
+  free(ctx);
+  cf->ctx = NULL;
+}
+
+static CURLcode set_local_ip(struct Curl_cfilter *cf,
+                             struct Curl_easy *data)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+
+#ifdef HAVE_GETSOCKNAME
+  char buffer[STRERROR_LEN];
+  struct Curl_sockaddr_storage ssloc;
+  curl_socklen_t slen = sizeof(struct Curl_sockaddr_storage);
+
+  memset(&ssloc, 0, sizeof(ssloc));
+  if(getsockname(ctx->sock, (struct sockaddr*) &ssloc, &slen)) {
+    int error = SOCKERRNO;
+    failf(data, "getsockname() failed with errno %d: %s",
+          error, Curl_strerror(error, buffer, sizeof(buffer)));
+    return CURLE_FAILED_INIT;
+  }
+  if(!Curl_addr2string((struct sockaddr*)&ssloc, slen,
+                       ctx->l_ip, &ctx->l_port)) {
+    failf(data, "ssloc inet_ntop() failed with errno %d: %s",
+          errno, Curl_strerror(errno, buffer, sizeof(buffer)));
+    return CURLE_FAILED_INIT;
+  }
+#else
+  (void)data;
+  ctx->l_ip[0] = 0;
+  ctx->l_port = -1;
+#endif
+  return CURLE_OK;
+}
+
+static CURLcode set_remote_ip(struct Curl_cfilter *cf,
+                              struct Curl_easy *data)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+
+  /* store remote address and port used in this connection attempt */
+  if(!Curl_addr2string(&ctx->addr.sa_addr, ctx->addr.addrlen,
+                       ctx->r_ip, &ctx->r_port)) {
+    char buffer[STRERROR_LEN];
+
+    ctx->error = errno;
+    /* malformed address or bug in inet_ntop, try next address */
+    failf(data, "sa_addr inet_ntop() failed with errno %d: %s",
+          errno, Curl_strerror(errno, buffer, sizeof(buffer)));
+    return CURLE_FAILED_INIT;
+  }
+  return CURLE_OK;
+}
+
+static CURLcode cf_socket_open(struct Curl_cfilter *cf,
+                              struct Curl_easy *data)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+  int error = 0;
+  bool isconnected = FALSE;
+  CURLcode result = CURLE_COULDNT_CONNECT;
+  bool is_tcp;
+  const char *ipmsg;
+
+  (void)data;
+  DEBUGASSERT(ctx->sock == CURL_SOCKET_BAD);
+  ctx->started_at = Curl_now();
+  result = socket_open(data, &ctx->addr, &ctx->sock);
+  if(result)
+    goto out;
+
+  result = set_remote_ip(cf, data);
+  if(result)
+    goto out;
+
+#ifdef ENABLE_IPV6
+  if(ctx->addr.family == AF_INET6)
+    ipmsg = "  Trying [%s]:%d...";
+  else
+#endif
+    ipmsg = "  Trying %s:%d...";
+  infof(data, ipmsg, ctx->r_ip, ctx->r_port);
+
+#ifdef ENABLE_IPV6
+  is_tcp = (ctx->addr.family == AF_INET
+            || ctx->addr.family == AF_INET6) &&
+           ctx->addr.socktype == SOCK_STREAM;
+#else
+  is_tcp = (ctx->addr.family == AF_INET) &&
+           ctx->addr.socktype == SOCK_STREAM;
+#endif
+  if(is_tcp && data->set.tcp_nodelay)
+    tcpnodelay(data, ctx->sock);
+
+  nosigpipe(data, ctx->sock);
+
+  Curl_sndbufset(ctx->sock);
+
+  if(is_tcp && data->set.tcp_keepalive)
+    tcpkeepalive(data, ctx->sock);
+
+  if(data->set.fsockopt) {
+    /* activate callback for setting socket options */
+    Curl_set_in_callback(data, true);
+    error = data->set.fsockopt(data->set.sockopt_client,
+                               ctx->sock,
+                               CURLSOCKTYPE_IPCXN);
+    Curl_set_in_callback(data, false);
+
+    if(error == CURL_SOCKOPT_ALREADY_CONNECTED)
+      isconnected = TRUE;
+    else if(error) {
+      result = CURLE_ABORTED_BY_CALLBACK;
+      goto out;
+    }
+  }
+
+  /* possibly bind the local end to an IP, interface or port */
+  if(ctx->addr.family == AF_INET
+#ifdef ENABLE_IPV6
+     || ctx->addr.family == AF_INET6
+#endif
+    ) {
+    result = bindlocal(data, cf->conn, ctx->sock, ctx->addr.family,
+                       Curl_ipv6_scope(&ctx->addr.sa_addr));
+    if(result) {
+      if(result == CURLE_UNSUPPORTED_PROTOCOL) {
+        /* The address family is not supported on this interface.
+           We can continue trying addresses */
+        result = CURLE_COULDNT_CONNECT;
+      }
+      goto out;
+    }
+  }
+
+  /* set socket non-blocking */
+  (void)curlx_nonblock(ctx->sock, TRUE);
+
+out:
+  if(result) {
+    if(ctx->sock != CURL_SOCKET_BAD) {
+      socket_close(data, cf->conn, TRUE, ctx->sock);
+      ctx->sock = CURL_SOCKET_BAD;
+    }
+  }
+  else if(isconnected) {
+    set_local_ip(cf, data);
+    ctx->connected_at = Curl_now();
+    cf->connected = TRUE;
+  }
+  DEBUGF(LOG_CF(data, cf, "cf_socket_open() -> %d, fd=%d", result, ctx->sock));
+  return result;
+}
+
+static int do_connect(struct Curl_cfilter *cf, struct Curl_easy *data,
+                      bool is_tcp_fastopen)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+#ifdef TCP_FASTOPEN_CONNECT
+  int optval = 1;
+#endif
+  int rc = -1;
+
+  (void)data;
+  if(is_tcp_fastopen) {
+#if defined(CONNECT_DATA_IDEMPOTENT) /* Darwin */
+#  if defined(HAVE_BUILTIN_AVAILABLE)
+    /* while connectx function is available since macOS 10.11 / iOS 9,
+       it did not have the interface declared correctly until
+       Xcode 9 / macOS SDK 10.13 */
+    if(__builtin_available(macOS 10.11, iOS 9.0, tvOS 9.0, watchOS 2.0, *)) {
+      sa_endpoints_t endpoints;
+      endpoints.sae_srcif = 0;
+      endpoints.sae_srcaddr = NULL;
+      endpoints.sae_srcaddrlen = 0;
+      endpoints.sae_dstaddr = &ctx->addr.sa_addr;
+      endpoints.sae_dstaddrlen = ctx->addr.addrlen;
+
+      rc = connectx(ctx->sock, &endpoints, SAE_ASSOCID_ANY,
+                    CONNECT_RESUME_ON_READ_WRITE | CONNECT_DATA_IDEMPOTENT,
+                    NULL, 0, NULL, NULL);
+    }
+    else {
+      rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen);
+    }
+#  else
+    rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen);
+#  endif /* HAVE_BUILTIN_AVAILABLE */
+#elif defined(TCP_FASTOPEN_CONNECT) /* Linux >= 4.11 */
+    if(setsockopt(ctx->sock, IPPROTO_TCP, TCP_FASTOPEN_CONNECT,
+                  (void *)&optval, sizeof(optval)) < 0)
+      infof(data, "Failed to enable TCP Fast Open on fd %d", ctx->sock);
+
+    rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen);
+#elif defined(MSG_FASTOPEN) /* old Linux */
+    if(cf->conn->given->flags & PROTOPT_SSL)
+      rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen);
+    else
+      rc = 0; /* Do nothing */
+#endif
+  }
+  else {
+    rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen);
+  }
+  return rc;
+}
+
+static CURLcode cf_tcp_connect(struct Curl_cfilter *cf,
+                               struct Curl_easy *data,
+                               bool blocking, bool *done)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_COULDNT_CONNECT;
+  int rc = 0;
+
+  (void)data;
+  if(cf->connected) {
+    *done = TRUE;
+    return CURLE_OK;
+  }
+
+  /* TODO: need to support blocking connect? */
+  if(blocking)
+    return CURLE_UNSUPPORTED_PROTOCOL;
+
+  *done = FALSE; /* a very negative world view is best */
+  if(ctx->sock == CURL_SOCKET_BAD) {
+
+    result = cf_socket_open(cf, data);
+    if(result)
+      goto out;
+
+    if(cf->connected) {
+      *done = TRUE;
+      return CURLE_OK;
+    }
+
+    /* Connect TCP socket */
+    rc = do_connect(cf, data, cf->conn->bits.tcp_fastopen);
+    if(-1 == rc) {
+      result = Curl_socket_connect_result(data, ctx->r_ip, SOCKERRNO);
+      goto out;
+    }
+  }
+
+#ifdef mpeix
+  /* Call this function once now, and ignore the results. We do this to
+     "clear" the error state on the socket so that we can later read it
+     reliably. This is reported necessary on the MPE/iX operating
+     system. */
+  (void)verifyconnect(ctx->sock, NULL);
+#endif
+  /* check socket for connect */
+  rc = SOCKET_WRITABLE(ctx->sock, 0);
+
+  if(rc == 0) { /* no connection yet */
+    DEBUGF(LOG_CF(data, cf, "not connected yet"));
+    return CURLE_OK;
+  }
+  else if(rc == CURL_CSELECT_OUT || cf->conn->bits.tcp_fastopen) {
+    if(verifyconnect(ctx->sock, &ctx->error)) {
+      /* we are connected with TCP, awesome! */
+      ctx->connected_at = Curl_now();
+      set_local_ip(cf, data);
+      *done = TRUE;
+      cf->connected = TRUE;
+      DEBUGF(LOG_CF(data, cf, "connected"));
+      return CURLE_OK;
+    }
+  }
+  else if(rc & CURL_CSELECT_ERR) {
+    (void)verifyconnect(ctx->sock, &ctx->error);
+    result = CURLE_COULDNT_CONNECT;
+  }
+
+out:
+  if(result) {
+    if(ctx->error) {
+      data->state.os_errno = ctx->error;
+      SET_SOCKERRNO(ctx->error);
+#ifndef CURL_DISABLE_VERBOSE_STRINGS
+      {
+        char buffer[STRERROR_LEN];
+        infof(data, "connect to %s port %u failed: %s",
+              ctx->r_ip, ctx->r_port,
+              Curl_strerror(ctx->error, buffer, sizeof(buffer)));
+      }
+#endif
+    }
+    if(ctx->sock != CURL_SOCKET_BAD) {
+      socket_close(data, cf->conn, TRUE, ctx->sock);
+      ctx->sock = CURL_SOCKET_BAD;
+    }
+    *done = FALSE;
+  }
+  return result;
+}
+
+static void cf_socket_get_host(struct Curl_cfilter *cf,
+                               struct Curl_easy *data,
+                               const char **phost,
+                               const char **pdisplay_host,
+                               int *pport)
+{
+  (void)data;
+  *phost = cf->conn->host.name;
+  *pdisplay_host = cf->conn->host.dispname;
+  *pport = cf->conn->port;
+}
+
+static int cf_socket_get_select_socks(struct Curl_cfilter *cf,
+                                      struct Curl_easy *data,
+                                      curl_socket_t *socks)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+  int rc = GETSOCK_BLANK;
+
+  (void)data;
+  if(!cf->connected && ctx->sock != CURL_SOCKET_BAD) {
+    socks[0] = ctx->sock;
+    rc |= GETSOCK_WRITESOCK(0);
+  }
+
+  return rc;
+}
+
+#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
+
+static CURLcode pre_receive_plain(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+  struct io_buffer * const iob = &ctx->recv_buffer;
+
+  /* WinSock will destroy unread received data if send() is
+     failed.
+     To avoid lossage of received data, recv() must be
+     performed before every send() if any incoming data is
+     available. However, skip this, if buffer is already full. */
+  if((cf->conn->handler->protocol&PROTO_FAMILY_HTTP) != 0 &&
+     cf->conn->recv[cf->sockindex] == Curl_conn_recv &&
+     (!iob->bufr || (iob->allc > iob->tail))) {
+    const int readymask = Curl_socket_check(ctx->sock, CURL_SOCKET_BAD,
+                                            CURL_SOCKET_BAD, 0);
+    if(readymask != -1 && (readymask & CURL_CSELECT_IN) != 0) {
+      size_t bytestorecv = iob->allc - iob->tail;
+      ssize_t nread;
+      /* Have some incoming data */
+      if(!iob->bufr) {
+        /* Use buffer double default size for intermediate buffer */
+        iob->allc = 2 * data->set.buffer_size;
+        iob->bufr = malloc(iob->allc);
+        if(!iob->bufr)
+          return CURLE_OUT_OF_MEMORY;
+        iob->tail = 0;
+        iob->head = 0;
+        bytestorecv = iob->allc;
+      }
+
+      nread = sread(ctx->sock, iob->bufr + iob->tail, bytestorecv);
+      if(nread > 0)
+        iob->tail += (size_t)nread;
+    }
+  }
+  return CURLE_OK;
+}
+
+static ssize_t get_pre_recved(struct Curl_cfilter *cf, char *buf, size_t len)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+  struct io_buffer * const iob = &ctx->recv_buffer;
+  size_t copysize;
+  if(!iob->bufr)
+    return 0;
+
+  DEBUGASSERT(iob->allc > 0);
+  DEBUGASSERT(iob->tail <= iob->allc);
+  DEBUGASSERT(iob->head <= iob->tail);
+  /* Check and process data that already received and storied in internal
+     intermediate buffer */
+  if(iob->tail > iob->head) {
+    copysize = CURLMIN(len, iob->tail - iob->head);
+    memcpy(buf, iob->bufr + iob->head, copysize);
+    iob->head += copysize;
+  }
+  else
+    copysize = 0; /* buffer was allocated, but nothing was received */
+
+  /* Free intermediate buffer if it has no unprocessed data */
+  if(iob->head == iob->tail)
+    io_buffer_reset(iob);
+
+  return (ssize_t)copysize;
+}
+#endif  /* USE_RECV_BEFORE_SEND_WORKAROUND */
+
+static bool cf_socket_data_pending(struct Curl_cfilter *cf,
+                                   const struct Curl_easy *data)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+  int readable;
+
+#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
+  if(ctx->recv_buffer.bufr && ctx->recv_buffer.allc &&
+     ctx->recv_buffer.tail > ctx->recv_buffer.head)
+     return TRUE;
+#endif
+
+  (void)data;
+  readable = SOCKET_READABLE(ctx->sock, 0);
+  return (readable > 0 && (readable & CURL_CSELECT_IN));
+}
+
+static ssize_t cf_socket_send(struct Curl_cfilter *cf, struct Curl_easy *data,
+                              const void *buf, size_t len, CURLcode *err)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+  curl_socket_t fdsave;
+  ssize_t nwritten;
+
+  *err = CURLE_OK;
+
+#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
+  /* WinSock will destroy unread received data if send() is
+     failed.
+     To avoid lossage of received data, recv() must be
+     performed before every send() if any incoming data is
+     available. */
+  if(pre_receive_plain(cf, data)) {
+    *err = CURLE_OUT_OF_MEMORY;
+    return -1;
+  }
+#endif
+
+  fdsave = cf->conn->sock[cf->sockindex];
+  cf->conn->sock[cf->sockindex] = ctx->sock;
+
+#if defined(MSG_FASTOPEN) && !defined(TCP_FASTOPEN_CONNECT) /* Linux */
+  if(cf->conn->bits.tcp_fastopen) {
+    nwritten = sendto(ctx->sock, buf, len, MSG_FASTOPEN,
+                      &cf->conn->remote_addr->sa_addr,
+                      cf->conn->remote_addr->addrlen);
+    cf->conn->bits.tcp_fastopen = FALSE;
+  }
+  else
+#endif
+    nwritten = swrite(ctx->sock, buf, len);
+
+  if(-1 == nwritten) {
+    int sockerr = SOCKERRNO;
+
+    if(
+#ifdef WSAEWOULDBLOCK
+      /* This is how Windows does it */
+      (WSAEWOULDBLOCK == sockerr)
+#else
+      /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned
+         due to its inability to send off data without blocking. We therefore
+         treat both error codes the same here */
+      (EWOULDBLOCK == sockerr) || (EAGAIN == sockerr) || (EINTR == sockerr) ||
+      (EINPROGRESS == sockerr)
+#endif
+      ) {
+      /* this is just a case of EWOULDBLOCK */
+      *err = CURLE_AGAIN;
+    }
+    else {
+      char buffer[STRERROR_LEN];
+      failf(data, "Send failure: %s",
+            Curl_strerror(sockerr, buffer, sizeof(buffer)));
+      data->state.os_errno = sockerr;
+      *err = CURLE_SEND_ERROR;
+    }
+  }
+
+  DEBUGF(LOG_CF(data, cf, "send(len=%zu) -> %d, err=%d",
+                len, (int)nwritten, *err));
+  cf->conn->sock[cf->sockindex] = fdsave;
+  return nwritten;
+}
+
+static ssize_t cf_socket_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
+                              char *buf, size_t len, CURLcode *err)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+  curl_socket_t fdsave;
+  ssize_t nread;
+
+  *err = CURLE_OK;
+
+#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
+  /* Check and return data that already received and storied in internal
+     intermediate buffer */
+  nread = get_pre_recved(cf, buf, len);
+  if(nread > 0) {
+    *err = CURLE_OK;
+    return nread;
+  }
+#endif
+
+  fdsave = cf->conn->sock[cf->sockindex];
+  cf->conn->sock[cf->sockindex] = ctx->sock;
+
+  nread = sread(ctx->sock, buf, len);
+
+  if(-1 == nread) {
+    int sockerr = SOCKERRNO;
+
+    if(
+#ifdef WSAEWOULDBLOCK
+      /* This is how Windows does it */
+      (WSAEWOULDBLOCK == sockerr)
+#else
+      /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned
+         due to its inability to send off data without blocking. We therefore
+         treat both error codes the same here */
+      (EWOULDBLOCK == sockerr) || (EAGAIN == sockerr) || (EINTR == sockerr)
+#endif
+      ) {
+      /* this is just a case of EWOULDBLOCK */
+      *err = CURLE_AGAIN;
+    }
+    else {
+      char buffer[STRERROR_LEN];
+      failf(data, "Recv failure: %s",
+            Curl_strerror(sockerr, buffer, sizeof(buffer)));
+      data->state.os_errno = sockerr;
+      *err = CURLE_RECV_ERROR;
+    }
+  }
+
+  DEBUGF(LOG_CF(data, cf, "recv(len=%zu) -> %d, err=%d", len, (int)nread,
+                *err));
+  if(nread > 0 && !ctx->got_first_byte) {
+    ctx->first_byte_at = Curl_now();
+    ctx->got_first_byte = TRUE;
+  }
+  cf->conn->sock[cf->sockindex] = fdsave;
+  return nread;
+}
+
+static void conn_set_primary_ip(struct Curl_cfilter *cf,
+                                struct Curl_easy *data)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+#ifdef HAVE_GETPEERNAME
+  char buffer[STRERROR_LEN];
+  struct Curl_sockaddr_storage ssrem;
+  curl_socklen_t plen;
+  int port;
+
+  plen = sizeof(ssrem);
+  memset(&ssrem, 0, plen);
+  if(getpeername(ctx->sock, (struct sockaddr*) &ssrem, &plen)) {
+    int error = SOCKERRNO;
+    failf(data, "getpeername() failed with errno %d: %s",
+          error, Curl_strerror(error, buffer, sizeof(buffer)));
+    return;
+  }
+  if(!Curl_addr2string((struct sockaddr*)&ssrem, plen,
+                       cf->conn->primary_ip, &port)) {
+    failf(data, "ssrem inet_ntop() failed with errno %d: %s",
+          errno, Curl_strerror(errno, buffer, sizeof(buffer)));
+    return;
+  }
+#else
+  cf->conn->primary_ip[0] = 0;
+  (void)data;
+#endif
+}
+
+static void cf_socket_active(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+
+  /* use this socket from now on */
+  cf->conn->sock[cf->sockindex] = ctx->sock;
+  /* the first socket info gets set at conn and data */
+  if(cf->sockindex == FIRSTSOCKET) {
+    cf->conn->remote_addr = &ctx->addr;
+  #ifdef ENABLE_IPV6
+    cf->conn->bits.ipv6 = (ctx->addr.family == AF_INET6)? TRUE : FALSE;
+  #endif
+    conn_set_primary_ip(cf, data);
+    set_local_ip(cf, data);
+    Curl_persistconninfo(data, cf->conn, ctx->l_ip, ctx->l_port);
+  }
+  ctx->active = TRUE;
+}
+
+static CURLcode cf_socket_cntrl(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                int event, int arg1, void *arg2)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+
+  (void)arg1;
+  (void)arg2;
+  switch(event) {
+  case CF_CTRL_CONN_INFO_UPDATE:
+    cf_socket_active(cf, data);
+    break;
+  case CF_CTRL_DATA_SETUP:
+    Curl_persistconninfo(data, cf->conn, ctx->l_ip, ctx->l_port);
+    break;
+  }
+  return CURLE_OK;
+}
+
+static bool cf_socket_conn_is_alive(struct Curl_cfilter *cf,
+                                    struct Curl_easy *data,
+                                    bool *input_pending)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+  struct pollfd pfd[1];
+  int r;
+
+  *input_pending = FALSE;
+  (void)data;
+  if(!ctx || ctx->sock == CURL_SOCKET_BAD)
+    return FALSE;
+
+  /* Check with 0 timeout if there are any events pending on the socket */
+  pfd[0].fd = ctx->sock;
+  pfd[0].events = POLLRDNORM|POLLIN|POLLRDBAND|POLLPRI;
+  pfd[0].revents = 0;
+
+  r = Curl_poll(pfd, 1, 0);
+  if(r < 0) {
+    DEBUGF(LOG_CF(data, cf, "is_alive: poll error, assume dead"));
+    return FALSE;
+  }
+  else if(r == 0) {
+    DEBUGF(LOG_CF(data, cf, "is_alive: poll timeout, assume alive"));
+    return TRUE;
+  }
+  else if(pfd[0].revents & (POLLERR|POLLHUP|POLLPRI|POLLNVAL)) {
+    DEBUGF(LOG_CF(data, cf, "is_alive: err/hup/etc events, assume dead"));
+    return FALSE;
+  }
+
+  DEBUGF(LOG_CF(data, cf, "is_alive: valid events, looks alive"));
+  *input_pending = TRUE;
+  return TRUE;
+}
+
+static CURLcode cf_socket_query(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                int query, int *pres1, void *pres2)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+
+  switch(query) {
+  case CF_QUERY_SOCKET:
+    DEBUGASSERT(pres2);
+    *((curl_socket_t *)pres2) = ctx->sock;
+    return CURLE_OK;
+  case CF_QUERY_CONNECT_REPLY_MS:
+    if(ctx->got_first_byte) {
+      timediff_t ms = Curl_timediff(ctx->first_byte_at, ctx->started_at);
+      *pres1 = (ms < INT_MAX)? (int)ms : INT_MAX;
+    }
+    else
+      *pres1 = -1;
+    return CURLE_OK;
+  case CF_QUERY_TIMER_CONNECT: {
+    struct curltime *when = pres2;
+    switch(ctx->transport) {
+    case TRNSPRT_UDP:
+    case TRNSPRT_QUIC:
+      /* Since UDP connected sockets work different from TCP, we use the
+       * time of the first byte from the peer as the "connect" time. */
+      if(ctx->got_first_byte) {
+        *when = ctx->first_byte_at;
+        break;
+      }
+      /* FALLTHROUGH */
+    default:
+      *when = ctx->connected_at;
+      break;
+    }
+    return CURLE_OK;
+  }
+  default:
+    break;
+  }
+  return cf->next?
+    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
+    CURLE_UNKNOWN_OPTION;
+}
+
+struct Curl_cftype Curl_cft_tcp = {
+  "TCP",
+  CF_TYPE_IP_CONNECT,
+  CURL_LOG_DEFAULT,
+  cf_socket_destroy,
+  cf_tcp_connect,
+  cf_socket_close,
+  cf_socket_get_host,
+  cf_socket_get_select_socks,
+  cf_socket_data_pending,
+  cf_socket_send,
+  cf_socket_recv,
+  cf_socket_cntrl,
+  cf_socket_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  cf_socket_query,
+};
+
+CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf,
+                            struct Curl_easy *data,
+                            struct connectdata *conn,
+                            const struct Curl_addrinfo *ai,
+                            int transport)
+{
+  struct cf_socket_ctx *ctx = NULL;
+  struct Curl_cfilter *cf = NULL;
+  CURLcode result;
+
+  (void)data;
+  (void)conn;
+  DEBUGASSERT(transport == TRNSPRT_TCP);
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+  cf_socket_ctx_init(ctx, ai, transport);
+
+  result = Curl_cf_create(&cf, &Curl_cft_tcp, ctx);
+
+out:
+  *pcf = (!result)? cf : NULL;
+  if(result) {
+    Curl_safefree(cf);
+    Curl_safefree(ctx);
+  }
+
+  return result;
+}
+
+static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf,
+                               struct Curl_easy *data)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+  int rc;
+
+  /* QUIC needs a connected socket, nonblocking */
+  DEBUGASSERT(ctx->sock != CURL_SOCKET_BAD);
+
+  rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen);
+  if(-1 == rc) {
+    return Curl_socket_connect_result(data, ctx->r_ip, SOCKERRNO);
+  }
+  set_local_ip(cf, data);
+  DEBUGF(LOG_CF(data, cf, "%s socket %d connected: [%s:%d] -> [%s:%d]",
+         (ctx->transport == TRNSPRT_QUIC)? "QUIC" : "UDP",
+         ctx->sock, ctx->l_ip, ctx->l_port, ctx->r_ip, ctx->r_port));
+
+  (void)curlx_nonblock(ctx->sock, TRUE);
+  switch(ctx->addr.family) {
+#if defined(__linux__) && defined(IP_MTU_DISCOVER)
+  case AF_INET: {
+    int val = IP_PMTUDISC_DO;
+    (void)setsockopt(ctx->sock, IPPROTO_IP, IP_MTU_DISCOVER, &val,
+                     sizeof(val));
+    break;
+  }
+#endif
+#if defined(__linux__) && defined(IPV6_MTU_DISCOVER)
+  case AF_INET6: {
+    int val = IPV6_PMTUDISC_DO;
+    (void)setsockopt(ctx->sock, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &val,
+                     sizeof(val));
+    break;
+  }
+#endif
+  }
+  return CURLE_OK;
+}
+
+static CURLcode cf_udp_connect(struct Curl_cfilter *cf,
+                               struct Curl_easy *data,
+                               bool blocking, bool *done)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_COULDNT_CONNECT;
+
+  (void)blocking;
+  if(cf->connected) {
+    *done = TRUE;
+    return CURLE_OK;
+  }
+  *done = FALSE;
+  if(ctx->sock == CURL_SOCKET_BAD) {
+    result = cf_socket_open(cf, data);
+    if(result) {
+      DEBUGF(LOG_CF(data, cf, "cf_udp_connect(), open failed -> %d", result));
+      if(ctx->sock != CURL_SOCKET_BAD) {
+        socket_close(data, cf->conn, TRUE, ctx->sock);
+        ctx->sock = CURL_SOCKET_BAD;
+      }
+      goto out;
+    }
+
+    if(ctx->transport == TRNSPRT_QUIC) {
+      result = cf_udp_setup_quic(cf, data);
+      if(result)
+        goto out;
+      DEBUGF(LOG_CF(data, cf, "cf_udp_connect(), opened socket=%d (%s:%d)",
+                    ctx->sock, ctx->l_ip, ctx->l_port));
+    }
+    else {
+      DEBUGF(LOG_CF(data, cf, "cf_udp_connect(), opened socket=%d "
+                    "(unconnected)", ctx->sock));
+    }
+    *done = TRUE;
+    cf->connected = TRUE;
+  }
+out:
+  return result;
+}
+
+struct Curl_cftype Curl_cft_udp = {
+  "UDP",
+  CF_TYPE_IP_CONNECT,
+  CURL_LOG_DEFAULT,
+  cf_socket_destroy,
+  cf_udp_connect,
+  cf_socket_close,
+  cf_socket_get_host,
+  cf_socket_get_select_socks,
+  cf_socket_data_pending,
+  cf_socket_send,
+  cf_socket_recv,
+  cf_socket_cntrl,
+  cf_socket_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  cf_socket_query,
+};
+
+CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf,
+                            struct Curl_easy *data,
+                            struct connectdata *conn,
+                            const struct Curl_addrinfo *ai,
+                            int transport)
+{
+  struct cf_socket_ctx *ctx = NULL;
+  struct Curl_cfilter *cf = NULL;
+  CURLcode result;
+
+  (void)data;
+  (void)conn;
+  DEBUGASSERT(transport == TRNSPRT_UDP || transport == TRNSPRT_QUIC);
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+  cf_socket_ctx_init(ctx, ai, transport);
+
+  result = Curl_cf_create(&cf, &Curl_cft_udp, ctx);
+
+out:
+  *pcf = (!result)? cf : NULL;
+  if(result) {
+    Curl_safefree(cf);
+    Curl_safefree(ctx);
+  }
+
+  return result;
+}
+
+/* this is the TCP filter which can also handle this case */
+struct Curl_cftype Curl_cft_unix = {
+  "UNIX",
+  CF_TYPE_IP_CONNECT,
+  CURL_LOG_DEFAULT,
+  cf_socket_destroy,
+  cf_tcp_connect,
+  cf_socket_close,
+  cf_socket_get_host,
+  cf_socket_get_select_socks,
+  cf_socket_data_pending,
+  cf_socket_send,
+  cf_socket_recv,
+  cf_socket_cntrl,
+  cf_socket_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  cf_socket_query,
+};
+
+CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf,
+                             struct Curl_easy *data,
+                             struct connectdata *conn,
+                             const struct Curl_addrinfo *ai,
+                             int transport)
+{
+  struct cf_socket_ctx *ctx = NULL;
+  struct Curl_cfilter *cf = NULL;
+  CURLcode result;
+
+  (void)data;
+  (void)conn;
+  DEBUGASSERT(transport == TRNSPRT_UNIX);
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+  cf_socket_ctx_init(ctx, ai, transport);
+
+  result = Curl_cf_create(&cf, &Curl_cft_unix, ctx);
+
+out:
+  *pcf = (!result)? cf : NULL;
+  if(result) {
+    Curl_safefree(cf);
+    Curl_safefree(ctx);
+  }
+
+  return result;
+}
+
+static CURLcode cf_tcp_accept_connect(struct Curl_cfilter *cf,
+                                      struct Curl_easy *data,
+                                      bool blocking, bool *done)
+{
+  /* we start accepted, if we ever close, we cannot go on */
+  (void)data;
+  (void)blocking;
+  if(cf->connected) {
+    *done = TRUE;
+    return CURLE_OK;
+  }
+  return CURLE_FAILED_INIT;
+}
+
+struct Curl_cftype Curl_cft_tcp_accept = {
+  "TCP-ACCEPT",
+  CF_TYPE_IP_CONNECT,
+  CURL_LOG_DEFAULT,
+  cf_socket_destroy,
+  cf_tcp_accept_connect,
+  cf_socket_close,
+  cf_socket_get_host,              /* TODO: not accurate */
+  cf_socket_get_select_socks,
+  cf_socket_data_pending,
+  cf_socket_send,
+  cf_socket_recv,
+  cf_socket_cntrl,
+  cf_socket_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  cf_socket_query,
+};
+
+CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data,
+                                  struct connectdata *conn,
+                                  int sockindex, curl_socket_t *s)
+{
+  CURLcode result;
+  struct Curl_cfilter *cf = NULL;
+  struct cf_socket_ctx *ctx = NULL;
+
+  /* replace any existing */
+  Curl_conn_cf_discard_all(data, conn, sockindex);
+  DEBUGASSERT(conn->sock[sockindex] == CURL_SOCKET_BAD);
+
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+  ctx->transport = conn->transport;
+  ctx->sock = *s;
+  ctx->accepted = FALSE;
+  result = Curl_cf_create(&cf, &Curl_cft_tcp_accept, ctx);
+  if(result)
+    goto out;
+  Curl_conn_cf_add(data, conn, sockindex, cf);
+
+  conn->sock[sockindex] = ctx->sock;
+  set_local_ip(cf, data);
+  ctx->active = TRUE;
+  ctx->connected_at = Curl_now();
+  cf->connected = TRUE;
+  DEBUGF(LOG_CF(data, cf, "Curl_conn_tcp_listen_set(%d)", (int)ctx->sock));
+
+out:
+  if(result) {
+    Curl_safefree(cf);
+    Curl_safefree(ctx);
+  }
+  return result;
+}
+
+static void set_accepted_remote_ip(struct Curl_cfilter *cf,
+                                   struct Curl_easy *data)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+#ifdef HAVE_GETPEERNAME
+  char buffer[STRERROR_LEN];
+  struct Curl_sockaddr_storage ssrem;
+  curl_socklen_t plen;
+
+  ctx->r_ip[0] = 0;
+  ctx->r_port = 0;
+  plen = sizeof(ssrem);
+  memset(&ssrem, 0, plen);
+  if(getpeername(ctx->sock, (struct sockaddr*) &ssrem, &plen)) {
+    int error = SOCKERRNO;
+    failf(data, "getpeername() failed with errno %d: %s",
+          error, Curl_strerror(error, buffer, sizeof(buffer)));
+    return;
+  }
+  if(!Curl_addr2string((struct sockaddr*)&ssrem, plen,
+                       ctx->r_ip, &ctx->r_port)) {
+    failf(data, "ssrem inet_ntop() failed with errno %d: %s",
+          errno, Curl_strerror(errno, buffer, sizeof(buffer)));
+    return;
+  }
+#else
+  ctx->r_ip[0] = 0;
+  ctx->r_port = 0;
+  (void)data;
+#endif
+}
+
+CURLcode Curl_conn_tcp_accepted_set(struct Curl_easy *data,
+                                    struct connectdata *conn,
+                                    int sockindex, curl_socket_t *s)
+{
+  struct Curl_cfilter *cf = NULL;
+  struct cf_socket_ctx *ctx = NULL;
+
+  cf = conn->cfilter[sockindex];
+  if(!cf || cf->cft != &Curl_cft_tcp_accept)
+    return CURLE_FAILED_INIT;
+
+  ctx = cf->ctx;
+  /* discard the listen socket */
+  socket_close(data, conn, TRUE, ctx->sock);
+  ctx->sock = *s;
+  conn->sock[sockindex] = ctx->sock;
+  set_accepted_remote_ip(cf, data);
+  set_local_ip(cf, data);
+  ctx->active = TRUE;
+  ctx->accepted = TRUE;
+  ctx->connected_at = Curl_now();
+  cf->connected = TRUE;
+  DEBUGF(LOG_CF(data, cf, "accepted_set(sock=%d, remote=%s port=%d)",
+         (int)ctx->sock, ctx->r_ip, ctx->r_port));
+
+  return CURLE_OK;
+}
+
+bool Curl_cf_is_socket(struct Curl_cfilter *cf)
+{
+  return cf && (cf->cft == &Curl_cft_tcp ||
+                cf->cft == &Curl_cft_udp ||
+                cf->cft == &Curl_cft_unix ||
+                cf->cft == &Curl_cft_tcp_accept);
+}
+
+CURLcode Curl_cf_socket_peek(struct Curl_cfilter *cf,
+                             struct Curl_easy *data,
+                             curl_socket_t *psock,
+                             const struct Curl_sockaddr_ex **paddr,
+                             const char **pr_ip_str, int *pr_port,
+                             const char **pl_ip_str, int *pl_port)
+{
+  if(Curl_cf_is_socket(cf) && cf->ctx) {
+    struct cf_socket_ctx *ctx = cf->ctx;
+
+    if(psock)
+      *psock = ctx->sock;
+    if(paddr)
+      *paddr = &ctx->addr;
+    if(pr_ip_str)
+      *pr_ip_str = ctx->r_ip;
+    if(pr_port)
+      *pr_port = ctx->r_port;
+    if(pl_port ||pl_ip_str) {
+      set_local_ip(cf, data);
+      if(pl_ip_str)
+        *pl_ip_str = ctx->l_ip;
+      if(pl_port)
+        *pl_port = ctx->l_port;
+    }
+    return CURLE_OK;
+  }
+  return CURLE_FAILED_INIT;
+}
+
diff --git a/Utilities/cmcurl/lib/cf-socket.h b/Utilities/cmcurl/lib/cf-socket.h
new file mode 100644
index 0000000..0eec61a
--- /dev/null
+++ b/Utilities/cmcurl/lib/cf-socket.h
@@ -0,0 +1,185 @@
+#ifndef HEADER_CURL_CF_SOCKET_H
+#define HEADER_CURL_CF_SOCKET_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#include "nonblock.h" /* for curlx_nonblock(), formerly Curl_nonblock() */
+#include "sockaddr.h"
+
+struct Curl_addrinfo;
+struct Curl_cfilter;
+struct Curl_easy;
+struct connectdata;
+struct Curl_sockaddr_ex;
+
+/*
+ * The Curl_sockaddr_ex structure is basically libcurl's external API
+ * curl_sockaddr structure with enough space available to directly hold any
+ * protocol-specific address structures. The variable declared here will be
+ * used to pass / receive data to/from the fopensocket callback if this has
+ * been set, before that, it is initialized from parameters.
+ */
+struct Curl_sockaddr_ex {
+  int family;
+  int socktype;
+  int protocol;
+  unsigned int addrlen;
+  union {
+    struct sockaddr addr;
+    struct Curl_sockaddr_storage buff;
+  } _sa_ex_u;
+};
+#define sa_addr _sa_ex_u.addr
+
+
+/*
+ * Create a socket based on info from 'conn' and 'ai'.
+ *
+ * Fill in 'addr' and 'sockfd' accordingly if OK is returned. If the open
+ * socket callback is set, used that!
+ *
+ */
+CURLcode Curl_socket_open(struct Curl_easy *data,
+                            const struct Curl_addrinfo *ai,
+                            struct Curl_sockaddr_ex *addr,
+                            int transport,
+                            curl_socket_t *sockfd);
+
+int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn,
+                      curl_socket_t sock);
+
+/**
+ * Determine the curl code for a socket connect() == -1 with errno.
+ */
+CURLcode Curl_socket_connect_result(struct Curl_easy *data,
+                                    const char *ipaddress, int error);
+
+#ifdef USE_WINSOCK
+/* When you run a program that uses the Windows Sockets API, you may
+   experience slow performance when you copy data to a TCP server.
+
+   https://support.microsoft.com/kb/823764
+
+   Work-around: Make the Socket Send Buffer Size Larger Than the Program Send
+   Buffer Size
+
+*/
+void Curl_sndbufset(curl_socket_t sockfd);
+#else
+#define Curl_sndbufset(y) Curl_nop_stmt
+#endif
+
+/**
+ * Assign the address `ai` to the Curl_sockaddr_ex `dest` and
+ * set the transport used.
+ */
+void Curl_sock_assign_addr(struct Curl_sockaddr_ex *dest,
+                           const struct Curl_addrinfo *ai,
+                           int transport);
+
+/**
+ * Creates a cfilter that opens a TCP socket to the given address
+ * when calling its `connect` implementation.
+ * The filter will not touch any connection/data flags and can be
+ * used in happy eyeballing. Once selected for use, its `_active()`
+ * method needs to be called.
+ */
+CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf,
+                            struct Curl_easy *data,
+                            struct connectdata *conn,
+                            const struct Curl_addrinfo *ai,
+                            int transport);
+
+/**
+ * Creates a cfilter that opens a UDP socket to the given address
+ * when calling its `connect` implementation.
+ * The filter will not touch any connection/data flags and can be
+ * used in happy eyeballing. Once selected for use, its `_active()`
+ * method needs to be called.
+ */
+CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf,
+                            struct Curl_easy *data,
+                            struct connectdata *conn,
+                            const struct Curl_addrinfo *ai,
+                            int transport);
+
+/**
+ * Creates a cfilter that opens a UNIX socket to the given address
+ * when calling its `connect` implementation.
+ * The filter will not touch any connection/data flags and can be
+ * used in happy eyeballing. Once selected for use, its `_active()`
+ * method needs to be called.
+ */
+CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf,
+                             struct Curl_easy *data,
+                             struct connectdata *conn,
+                             const struct Curl_addrinfo *ai,
+                             int transport);
+
+/**
+ * Creates a cfilter that keeps a listening socket.
+ */
+CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data,
+                                  struct connectdata *conn,
+                                  int sockindex,
+                                  curl_socket_t *s);
+
+/**
+ * Replace the listen socket with the accept()ed one.
+ */
+CURLcode Curl_conn_tcp_accepted_set(struct Curl_easy *data,
+                                    struct connectdata *conn,
+                                    int sockindex,
+                                    curl_socket_t *s);
+
+/**
+ * Return TRUE iff `cf` is a socket filter.
+ */
+bool Curl_cf_is_socket(struct Curl_cfilter *cf);
+
+/**
+ * Peek at the socket and remote ip/port the socket filter is using.
+ * The filter owns all returned values.
+ * @param psock             pointer to hold socket descriptor or NULL
+ * @param paddr             pointer to hold addr reference or NULL
+ * @param pr_ip_str         pointer to hold remote addr as string or NULL
+ * @param pr_port           pointer to hold remote port number or NULL
+ * @param pl_ip_str         pointer to hold local addr as string or NULL
+ * @param pl_port           pointer to hold local port number or NULL
+ * Returns error if the filter is of invalid type.
+ */
+CURLcode Curl_cf_socket_peek(struct Curl_cfilter *cf,
+                             struct Curl_easy *data,
+                             curl_socket_t *psock,
+                             const struct Curl_sockaddr_ex **paddr,
+                             const char **pr_ip_str, int *pr_port,
+                             const char **pl_ip_str, int *pl_port);
+
+extern struct Curl_cftype Curl_cft_tcp;
+extern struct Curl_cftype Curl_cft_udp;
+extern struct Curl_cftype Curl_cft_unix;
+extern struct Curl_cftype Curl_cft_tcp_accept;
+
+#endif /* HEADER_CURL_CF_SOCKET_H */
diff --git a/Utilities/cmcurl/lib/cfilters.c b/Utilities/cmcurl/lib/cfilters.c
index bcb33da..e60d138 100644
--- a/Utilities/cmcurl/lib/cfilters.c
+++ b/Utilities/cmcurl/lib/cfilters.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -34,9 +34,6 @@
 #include "multiif.h"
 #include "progress.h"
 #include "warnless.h"
-#include "http_proxy.h"
-#include "socks.h"
-#include "vtls/vtls.h"
 
 /* The last 3 #include files should be in this order */
 #include "curl_printf.h"
@@ -54,89 +51,117 @@
   (void)data;
 }
 
-CURLcode Curl_cf_def_setup(struct Curl_cfilter *cf,
-                           struct Curl_easy *data,
-                           const struct Curl_dns_entry *remotehost)
-{
-  DEBUGASSERT(cf->next);
-  return cf->next->cft->setup(cf->next, data, remotehost);
-}
-
-void     Curl_cf_def_attach_data(struct Curl_cfilter *cf,
-                                 struct Curl_easy *data)
-{
-  (void)cf;
-  (void)data;
-}
-
-void     Curl_cf_def_detach_data(struct Curl_cfilter *cf,
-                                 struct Curl_easy *data)
-{
-  (void)cf;
-  (void)data;
-}
-
 void Curl_cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
-  DEBUGASSERT(cf->next);
   cf->connected = FALSE;
-  cf->next->cft->close(cf->next, data);
+  if(cf->next)
+    cf->next->cft->close(cf->next, data);
 }
 
 CURLcode Curl_cf_def_connect(struct Curl_cfilter *cf,
                              struct Curl_easy *data,
                              bool blocking, bool *done)
 {
-  DEBUGASSERT(cf->next);
-  return cf->next->cft->connect(cf->next, data, blocking, done);
+  CURLcode result;
+
+  if(cf->connected) {
+    *done = TRUE;
+    return CURLE_OK;
+  }
+  if(cf->next) {
+    result = cf->next->cft->connect(cf->next, data, blocking, done);
+    if(!result && *done) {
+      cf->connected = TRUE;
+    }
+    return result;
+  }
+  *done = FALSE;
+  return CURLE_FAILED_INIT;
 }
 
 void Curl_cf_def_get_host(struct Curl_cfilter *cf, struct Curl_easy *data,
                           const char **phost, const char **pdisplay_host,
                           int *pport)
 {
-  DEBUGASSERT(cf->next);
-  cf->next->cft->get_host(cf->next, data, phost, pdisplay_host, pport);
+  if(cf->next)
+    cf->next->cft->get_host(cf->next, data, phost, pdisplay_host, pport);
+  else {
+    *phost = cf->conn->host.name;
+    *pdisplay_host = cf->conn->host.dispname;
+    *pport = cf->conn->port;
+  }
 }
 
 int Curl_cf_def_get_select_socks(struct Curl_cfilter *cf,
                                  struct Curl_easy *data,
                                  curl_socket_t *socks)
 {
-  DEBUGASSERT(cf->next);
-  return cf->next->cft->get_select_socks(cf->next, data, socks);
+  return cf->next?
+    cf->next->cft->get_select_socks(cf->next, data, socks) : 0;
 }
 
 bool Curl_cf_def_data_pending(struct Curl_cfilter *cf,
                               const struct Curl_easy *data)
 {
-  DEBUGASSERT(cf->next);
-  return cf->next->cft->has_data_pending(cf->next, data);
+  return cf->next?
+    cf->next->cft->has_data_pending(cf->next, data) : FALSE;
 }
 
 ssize_t  Curl_cf_def_send(struct Curl_cfilter *cf, struct Curl_easy *data,
                           const void *buf, size_t len, CURLcode *err)
 {
-  DEBUGASSERT(cf->next);
-  return cf->next->cft->do_send(cf->next, data, buf, len, err);
+  return cf->next?
+    cf->next->cft->do_send(cf->next, data, buf, len, err) :
+    CURLE_RECV_ERROR;
 }
 
 ssize_t  Curl_cf_def_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
                           char *buf, size_t len, CURLcode *err)
 {
-  DEBUGASSERT(cf->next);
-  return cf->next->cft->do_recv(cf->next, data, buf, len, err);
+  return cf->next?
+    cf->next->cft->do_recv(cf->next, data, buf, len, err) :
+    CURLE_SEND_ERROR;
 }
 
-void Curl_conn_cf_discard_all(struct Curl_easy *data,
-                              struct connectdata *conn, int index)
+bool Curl_cf_def_conn_is_alive(struct Curl_cfilter *cf,
+                               struct Curl_easy *data,
+                               bool *input_pending)
 {
-  struct Curl_cfilter *cfn, *cf = conn->cfilter[index];
+  return cf->next?
+    cf->next->cft->is_alive(cf->next, data, input_pending) :
+    FALSE; /* pessimistic in absence of data */
+}
+
+CURLcode Curl_cf_def_conn_keep_alive(struct Curl_cfilter *cf,
+                                     struct Curl_easy *data)
+{
+  return cf->next?
+    cf->next->cft->keep_alive(cf->next, data) :
+    CURLE_OK;
+}
+
+CURLcode Curl_cf_def_query(struct Curl_cfilter *cf,
+                           struct Curl_easy *data,
+                           int query, int *pres1, void *pres2)
+{
+  return cf->next?
+    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
+    CURLE_UNKNOWN_OPTION;
+}
+
+void Curl_conn_cf_discard_chain(struct Curl_cfilter **pcf,
+                                struct Curl_easy *data)
+{
+  struct Curl_cfilter *cfn, *cf = *pcf;
 
   if(cf) {
-    conn->cfilter[index] = NULL;
+    *pcf = NULL;
     while(cf) {
       cfn = cf->next;
+      /* prevent destroying filter to mess with its sub-chain, since
+       * we have the reference now and will call destroy on it.
+       */
+      cf->next = NULL;
       cf->cft->destroy(cf, data);
       free(cf);
       cf = cfn;
@@ -144,6 +169,12 @@
   }
 }
 
+void Curl_conn_cf_discard_all(struct Curl_easy *data,
+                              struct connectdata *conn, int index)
+{
+  Curl_conn_cf_discard_chain(&conn->cfilter[index], data);
+}
+
 void Curl_conn_close(struct Curl_easy *data, int index)
 {
   struct Curl_cfilter *cf;
@@ -160,7 +191,6 @@
                        size_t len, CURLcode *code)
 {
   struct Curl_cfilter *cf;
-  ssize_t nread;
 
   DEBUGASSERT(data);
   DEBUGASSERT(data->conn);
@@ -169,13 +199,9 @@
     cf = cf->next;
   }
   if(cf) {
-    nread = cf->cft->do_recv(cf, data, buf, len, code);
-    /* DEBUGF(infof(data, "Curl_conn_recv(handle=%p, index=%d)"
-           "-> %ld, err=%d", data, num, nread, *code));*/
-    return nread;
+    return cf->cft->do_recv(cf, data, buf, len, code);
   }
-  failf(data, "no filter connected, conn=%ld, sockindex=%d",
-        data->conn->connection_id, num);
+  failf(data, CMSGI(data->conn, num, "recv: no filter connected"));
   *code = CURLE_FAILED_INIT;
   return -1;
 }
@@ -184,7 +210,6 @@
                        const void *mem, size_t len, CURLcode *code)
 {
   struct Curl_cfilter *cf;
-  ssize_t nwritten;
 
   DEBUGASSERT(data);
   DEBUGASSERT(data->conn);
@@ -193,13 +218,10 @@
     cf = cf->next;
   }
   if(cf) {
-    nwritten = cf->cft->do_send(cf, data, mem, len, code);
-    /* DEBUGF(infof(data, "Curl_conn_send(handle=%p, index=%d, len=%ld)"
-           " -> %ld, err=%d", data, num, len, nwritten, *code));*/
-    return nwritten;
+    return cf->cft->do_send(cf, data, mem, len, code);
   }
-  failf(data, "no filter connected, conn=%ld, sockindex=%d",
-        data->conn->connection_id, num);
+  failf(data, CMSGI(data->conn, num, "send: no filter connected"));
+  DEBUGASSERT(0);
   *code = CURLE_FAILED_INIT;
   return -1;
 }
@@ -234,12 +256,31 @@
   DEBUGASSERT(!cf->conn);
   DEBUGASSERT(!cf->next);
 
-  DEBUGF(infof(data, CMSGI(conn, index, "cf_add(filter=%s)"),
-               cf->cft->name));
   cf->next = conn->cfilter[index];
   cf->conn = conn;
   cf->sockindex = index;
   conn->cfilter[index] = cf;
+  DEBUGF(LOG_CF(data, cf, "added"));
+}
+
+void Curl_conn_cf_insert_after(struct Curl_cfilter *cf_at,
+                               struct Curl_cfilter *cf_new)
+{
+  struct Curl_cfilter *tail, **pnext;
+
+  DEBUGASSERT(cf_at);
+  DEBUGASSERT(cf_new);
+  DEBUGASSERT(!cf_new->conn);
+
+  tail = cf_at->next;
+  cf_at->next = cf_new;
+  do {
+    cf_new->conn = cf_at->conn;
+    cf_new->sockindex = cf_at->sockindex;
+    pnext = &cf_new->next;
+    cf_new = cf_new->next;
+  } while(cf_new);
+  *pnext = tail;
 }
 
 void Curl_conn_cf_discard(struct Curl_cfilter *cf, struct Curl_easy *data)
@@ -259,97 +300,54 @@
   free(cf);
 }
 
+CURLcode Curl_conn_cf_connect(struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
+                              bool blocking, bool *done)
+{
+  if(cf)
+    return cf->cft->connect(cf, data, blocking, done);
+  return CURLE_FAILED_INIT;
+}
+
+void Curl_conn_cf_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  if(cf)
+    cf->cft->close(cf, data);
+}
+
+int Curl_conn_cf_get_select_socks(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  curl_socket_t *socks)
+{
+  if(cf)
+    return cf->cft->get_select_socks(cf, data, socks);
+  return 0;
+}
+
+bool Curl_conn_cf_data_pending(struct Curl_cfilter *cf,
+                               const struct Curl_easy *data)
+{
+  if(cf)
+    return cf->cft->has_data_pending(cf, data);
+  return FALSE;
+}
+
 ssize_t Curl_conn_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data,
                           const void *buf, size_t len, CURLcode *err)
 {
-  return cf->cft->do_send(cf, data, buf, len, err);
+  if(cf)
+    return cf->cft->do_send(cf, data, buf, len, err);
+  *err = CURLE_SEND_ERROR;
+  return -1;
 }
 
 ssize_t Curl_conn_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
                           char *buf, size_t len, CURLcode *err)
 {
-  return cf->cft->do_recv(cf, data, buf, len, err);
-}
-
-CURLcode Curl_conn_setup(struct Curl_easy *data,
-                         struct connectdata *conn,
-                         int sockindex,
-                         const struct Curl_dns_entry *remotehost,
-                         int ssl_mode)
-{
-  struct Curl_cfilter *cf;
-  CURLcode result;
-
-  DEBUGASSERT(data);
-  /* If no filter is set, we have the "default" setup of connection filters.
-   * The filter chain from botton to top will be:
-   * - SOCKET       socket filter for outgoing connection to remotehost
-   * if http_proxy tunneling is engaged:
-   *    - SSL                 if proxytype is CURLPROXY_HTTPS
-   *    - HTTP_PROXY_TUNNEL
-   * otherwise, if socks_proxy is engaged:
-   *    - SOCKS_PROXY_TUNNEL
-   * - SSL          if conn->handler has PROTOPT_SSL
-   */
-  if(!conn->cfilter[sockindex]) {
-    DEBUGF(infof(data, DMSGI(data, sockindex, "setup, init filter chain")));
-    result = Curl_conn_socket_set(data, conn, sockindex);
-    if(result)
-      goto out;
-
-#ifndef CURL_DISABLE_PROXY
-    if(conn->bits.socksproxy) {
-      result = Curl_conn_socks_proxy_add(data, conn, sockindex);
-      if(result)
-        goto out;
-    }
-
-    if(conn->bits.httpproxy) {
-#ifdef USE_SSL
-      if(conn->http_proxy.proxytype == CURLPROXY_HTTPS) {
-        result = Curl_ssl_cfilter_proxy_add(data, conn, sockindex);
-        if(result)
-          goto out;
-      }
-#endif /* USE_SSL */
-
-#if !defined(CURL_DISABLE_HTTP)
-      if(conn->bits.tunnel_proxy) {
-        result = Curl_conn_http_proxy_add(data, conn, sockindex);
-        if(result)
-          goto out;
-      }
-#endif /* !CURL_DISABLE_HTTP */
-    }
-#endif /* !CURL_DISABLE_PROXY */
-
-#ifdef USE_SSL
-    if(ssl_mode == CURL_CF_SSL_ENABLE
-      || (ssl_mode != CURL_CF_SSL_DISABLE
-           && conn->handler->flags & PROTOPT_SSL)) {
-      result = Curl_ssl_cfilter_add(data, conn, sockindex);
-      if(result)
-        goto out;
-    }
-#else
-    (void)ssl_mode;
-#endif /* USE_SSL */
-
-#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
-    if(data->set.haproxyprotocol) {
-      result = Curl_conn_haproxy_add(data, conn, sockindex);
-      if(result)
-        goto out;
-    }
-#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */
-
-  }
-  DEBUGASSERT(conn->cfilter[sockindex]);
-  cf = data->conn->cfilter[sockindex];
-  result = cf->cft->setup(cf, data, remotehost);
-
-out:
-  return result;
+  if(cf)
+    return cf->cft->do_recv(cf, data, buf, len, err);
+  *err = CURLE_RECV_ERROR;
+  return -1;
 }
 
 CURLcode Curl_conn_connect(struct Curl_easy *data,
@@ -358,16 +356,29 @@
                            bool *done)
 {
   struct Curl_cfilter *cf;
-  CURLcode result;
+  CURLcode result = CURLE_OK;
 
   DEBUGASSERT(data);
+  DEBUGASSERT(data->conn);
 
   cf = data->conn->cfilter[sockindex];
   DEBUGASSERT(cf);
-  result = cf->cft->connect(cf, data, blocking, done);
+  if(!cf)
+    return CURLE_FAILED_INIT;
 
-  DEBUGF(infof(data, DMSGI(data, sockindex, "connect(block=%d)-> %d, done=%d"),
-         blocking, result, *done));
+  *done = cf->connected;
+  if(!*done) {
+    result = cf->cft->connect(cf, data, blocking, done);
+    if(!result && *done) {
+      Curl_conn_ev_update_info(data, data->conn);
+      Curl_conn_report_connect_stats(data, data->conn);
+      data->conn->keepalive = Curl_now();
+    }
+    else if(result) {
+      Curl_conn_report_connect_stats(data, data->conn);
+    }
+  }
+
   return result;
 }
 
@@ -394,11 +405,10 @@
   return FALSE;
 }
 
-bool Curl_conn_is_ssl(struct Curl_easy *data, int sockindex)
+bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex)
 {
-  struct Curl_cfilter *cf = data->conn? data->conn->cfilter[sockindex] : NULL;
+  struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL;
 
-  (void)data;
   for(; cf; cf = cf->next) {
     if(cf->cft->flags & CF_TYPE_SSL)
       return TRUE;
@@ -408,6 +418,19 @@
   return FALSE;
 }
 
+bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex)
+{
+  struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL;
+
+  for(; cf; cf = cf->next) {
+    if(cf->cft->flags & CF_TYPE_MULTIPLEX)
+      return TRUE;
+    if(cf->cft->flags & CF_TYPE_IP_CONNECT
+       || cf->cft->flags & CF_TYPE_SSL)
+      return FALSE;
+  }
+  return FALSE;
+}
 
 bool Curl_conn_data_pending(struct Curl_easy *data, int sockindex)
 {
@@ -416,8 +439,6 @@
   (void)data;
   DEBUGASSERT(data);
   DEBUGASSERT(data->conn);
-  if(Curl_recv_has_postponed_data(data->conn, sockindex))
-    return TRUE;
 
   cf = data->conn->cfilter[sockindex];
   while(cf && !cf->connected) {
@@ -437,46 +458,16 @@
   DEBUGASSERT(data);
   DEBUGASSERT(data->conn);
   cf = data->conn->cfilter[sockindex];
+
+  /* if the next one is not yet connected, that's the one we want */
+  while(cf && cf->next && !cf->next->connected)
+    cf = cf->next;
   if(cf) {
     return cf->cft->get_select_socks(cf, data, socks);
   }
   return GETSOCK_BLANK;
 }
 
-void Curl_conn_attach_data(struct connectdata *conn,
-                           struct Curl_easy *data)
-{
-  size_t i;
-  struct Curl_cfilter *cf;
-
-  for(i = 0; i < ARRAYSIZE(conn->cfilter); ++i) {
-    cf = conn->cfilter[i];
-    if(cf) {
-      while(cf) {
-        cf->cft->attach_data(cf, data);
-        cf = cf->next;
-      }
-    }
-  }
-}
-
-void Curl_conn_detach_data(struct connectdata *conn,
-                           struct Curl_easy *data)
-{
-  size_t i;
-  struct Curl_cfilter *cf;
-
-  for(i = 0; i < ARRAYSIZE(conn->cfilter); ++i) {
-    cf = conn->cfilter[i];
-    if(cf) {
-      while(cf) {
-        cf->cft->detach_data(cf, data);
-        cf = cf->next;
-      }
-    }
-  }
-}
-
 void Curl_conn_get_host(struct Curl_easy *data, int sockindex,
                         const char **phost, const char **pdisplay_host,
                         int *pport)
@@ -491,7 +482,7 @@
   else {
     /* Some filter ask during shutdown for this, mainly for debugging
      * purposes. We hand out the defaults, however this is not always
-     * accurate, as the connction might be tunneled, etc. But all that
+     * accurate, as the connection might be tunneled, etc. But all that
      * state is already gone here. */
     *phost = data->conn->host.name;
     *pdisplay_host = data->conn->host.dispname;
@@ -499,4 +490,174 @@
   }
 }
 
+CURLcode Curl_cf_def_cntrl(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                int event, int arg1, void *arg2)
+{
+  (void)cf;
+  (void)data;
+  (void)event;
+  (void)arg1;
+  (void)arg2;
+  return CURLE_OK;
+}
+
+CURLcode Curl_conn_cf_cntrl(struct Curl_cfilter *cf,
+                            struct Curl_easy *data,
+                            bool ignore_result,
+                            int event, int arg1, void *arg2)
+{
+  CURLcode result = CURLE_OK;
+
+  for(; cf; cf = cf->next) {
+    if(Curl_cf_def_cntrl == cf->cft->cntrl)
+      continue;
+    result = cf->cft->cntrl(cf, data, event, arg1, arg2);
+    if(!ignore_result && result)
+      break;
+  }
+  return result;
+}
+
+curl_socket_t Curl_conn_cf_get_socket(struct Curl_cfilter *cf,
+                                      struct Curl_easy *data)
+{
+  curl_socket_t sock;
+  if(cf && !cf->cft->query(cf, data, CF_QUERY_SOCKET, NULL, &sock))
+    return sock;
+  return CURL_SOCKET_BAD;
+}
+
+curl_socket_t Curl_conn_get_socket(struct Curl_easy *data, int sockindex)
+{
+  struct Curl_cfilter *cf;
+
+  cf = data->conn? data->conn->cfilter[sockindex] : NULL;
+  /* if the top filter has not connected, ask it (and its sub-filters)
+   * for the socket. Otherwise conn->sock[sockindex] should have it.
+   */
+  if(cf && !cf->connected)
+    return Curl_conn_cf_get_socket(cf, data);
+  return data->conn? data->conn->sock[sockindex] : CURL_SOCKET_BAD;
+}
+
+static CURLcode cf_cntrl_all(struct connectdata *conn,
+                             struct Curl_easy *data,
+                             bool ignore_result,
+                             int event, int arg1, void *arg2)
+{
+  CURLcode result = CURLE_OK;
+  size_t i;
+
+  for(i = 0; i < ARRAYSIZE(conn->cfilter); ++i) {
+    result = Curl_conn_cf_cntrl(conn->cfilter[i], data, ignore_result,
+                                event, arg1, arg2);
+    if(!ignore_result && result)
+      break;
+  }
+  return result;
+}
+
+void Curl_conn_ev_data_attach(struct connectdata *conn,
+                              struct Curl_easy *data)
+{
+  cf_cntrl_all(conn, data, TRUE, CF_CTRL_DATA_ATTACH, 0, NULL);
+}
+
+void Curl_conn_ev_data_detach(struct connectdata *conn,
+                              struct Curl_easy *data)
+{
+  cf_cntrl_all(conn, data, TRUE, CF_CTRL_DATA_DETACH, 0, NULL);
+}
+
+CURLcode Curl_conn_ev_data_setup(struct Curl_easy *data)
+{
+  return cf_cntrl_all(data->conn, data, FALSE,
+                      CF_CTRL_DATA_SETUP, 0, NULL);
+}
+
+CURLcode Curl_conn_ev_data_idle(struct Curl_easy *data)
+{
+  return cf_cntrl_all(data->conn, data, FALSE,
+                      CF_CTRL_DATA_IDLE, 0, NULL);
+}
+
+/**
+ * Notify connection filters that the transfer represented by `data`
+ * is donw with sending data (e.g. has uploaded everything).
+ */
+void Curl_conn_ev_data_done_send(struct Curl_easy *data)
+{
+  cf_cntrl_all(data->conn, data, TRUE, CF_CTRL_DATA_DONE_SEND, 0, NULL);
+}
+
+/**
+ * Notify connection filters that the transfer represented by `data`
+ * is finished - eventually premature, e.g. before being complete.
+ */
+void Curl_conn_ev_data_done(struct Curl_easy *data, bool premature)
+{
+  cf_cntrl_all(data->conn, data, TRUE, CF_CTRL_DATA_DONE, premature, NULL);
+}
+
+CURLcode Curl_conn_ev_data_pause(struct Curl_easy *data, bool do_pause)
+{
+  return cf_cntrl_all(data->conn, data, FALSE,
+                      CF_CTRL_DATA_PAUSE, do_pause, NULL);
+}
+
+void Curl_conn_ev_update_info(struct Curl_easy *data,
+                              struct connectdata *conn)
+{
+  cf_cntrl_all(conn, data, TRUE, CF_CTRL_CONN_INFO_UPDATE, 0, NULL);
+}
+
+void Curl_conn_report_connect_stats(struct Curl_easy *data,
+                                    struct connectdata *conn)
+{
+  struct Curl_cfilter *cf = conn->cfilter[FIRSTSOCKET];
+  if(cf) {
+    struct curltime connected;
+    struct curltime appconnected;
+
+    memset(&connected, 0, sizeof(connected));
+    cf->cft->query(cf, data, CF_QUERY_TIMER_CONNECT, NULL, &connected);
+    if(connected.tv_sec || connected.tv_usec)
+      Curl_pgrsTimeWas(data, TIMER_CONNECT, connected);
+
+    memset(&appconnected, 0, sizeof(appconnected));
+    cf->cft->query(cf, data, CF_QUERY_TIMER_APPCONNECT, NULL, &appconnected);
+    if(appconnected.tv_sec || appconnected.tv_usec)
+      Curl_pgrsTimeWas(data, TIMER_APPCONNECT, appconnected);
+  }
+}
+
+bool Curl_conn_is_alive(struct Curl_easy *data, struct connectdata *conn,
+                        bool *input_pending)
+{
+  struct Curl_cfilter *cf = conn->cfilter[FIRSTSOCKET];
+  return cf && !cf->conn->bits.close &&
+         cf->cft->is_alive(cf, data, input_pending);
+}
+
+CURLcode Curl_conn_keep_alive(struct Curl_easy *data,
+                              struct connectdata *conn,
+                              int sockindex)
+{
+  struct Curl_cfilter *cf = conn->cfilter[sockindex];
+  return cf? cf->cft->keep_alive(cf, data) : CURLE_OK;
+}
+
+size_t Curl_conn_get_max_concurrent(struct Curl_easy *data,
+                                     struct connectdata *conn,
+                                     int sockindex)
+{
+  CURLcode result;
+  int n = 0;
+
+  struct Curl_cfilter *cf = conn->cfilter[sockindex];
+  result = cf? cf->cft->query(cf, data, CF_QUERY_MAX_CONCURRENT,
+                              &n, NULL) : CURLE_UNKNOWN_OPTION;
+  return (result || n <= 0)? 1 : (size_t)n;
+}
 
diff --git a/Utilities/cmcurl/lib/cfilters.h b/Utilities/cmcurl/lib/cfilters.h
index 4b81b42..317f2bb 100644
--- a/Utilities/cmcurl/lib/cfilters.h
+++ b/Utilities/cmcurl/lib/cfilters.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -36,11 +36,6 @@
 typedef void     Curl_cft_destroy_this(struct Curl_cfilter *cf,
                                        struct Curl_easy *data);
 
-/* Setup the connection for `data`, using destination `remotehost`.
- */
-typedef CURLcode Curl_cft_setup(struct Curl_cfilter *cf,
-                                struct Curl_easy *data,
-                                const struct Curl_dns_entry *remotehost);
 typedef void     Curl_cft_close(struct Curl_cfilter *cf,
                                 struct Curl_easy *data);
 
@@ -89,29 +84,90 @@
                                size_t len,             /* amount to read */
                                CURLcode *err);         /* error to return */
 
-typedef void     Curl_cft_attach_data(struct Curl_cfilter *cf,
-                                      struct Curl_easy *data);
-typedef void     Curl_cft_detach_data(struct Curl_cfilter *cf,
-                                      struct Curl_easy *data);
+typedef bool     Curl_cft_conn_is_alive(struct Curl_cfilter *cf,
+                                        struct Curl_easy *data,
+                                        bool *input_pending);
+
+typedef CURLcode Curl_cft_conn_keep_alive(struct Curl_cfilter *cf,
+                                          struct Curl_easy *data);
 
 /**
- * The easy handle `data` is being detached (no longer served)
- * by connection `conn`. All filters are informed to release any resources
- * related to `data`.
- * Note: there may be several `data` attached to a connection at the same
- * time.
+ * Events/controls for connection filters, their arguments and
+ * return code handling. Filter callbacks are invoked "top down".
+ * Return code handling:
+ * "first fail" meaning that the first filter returning != CURLE_OK, will
+ *              abort further event distribution and determine the result.
+ * "ignored" meaning return values are ignored and the event is distributed
+ *           to all filters in the chain. Overall result is always CURLE_OK.
  */
-void Curl_conn_detach(struct connectdata *conn, struct Curl_easy *data);
+/*      data event                          arg1       arg2     return */
+#define CF_CTRL_DATA_ATTACH           1  /* 0          NULL     ignored */
+#define CF_CTRL_DATA_DETACH           2  /* 0          NULL     ignored */
+#define CF_CTRL_DATA_SETUP            4  /* 0          NULL     first fail */
+#define CF_CTRL_DATA_IDLE             5  /* 0          NULL     first fail */
+#define CF_CTRL_DATA_PAUSE            6  /* on/off     NULL     first fail */
+#define CF_CTRL_DATA_DONE             7  /* premature  NULL     ignored */
+#define CF_CTRL_DATA_DONE_SEND        8  /* 0          NULL     ignored */
+/* update conn info at connection and data */
+#define CF_CTRL_CONN_INFO_UPDATE (256+0) /* 0          NULL     ignored */
 
+/**
+ * Handle event/control for the filter.
+ * Implementations MUST NOT chain calls to cf->next.
+ */
+typedef CURLcode Curl_cft_cntrl(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                int event, int arg1, void *arg2);
+
+
+/**
+ * Queries to ask via a `Curl_cft_query *query` method on a cfilter chain.
+ * - MAX_CONCURRENT: the maximum number of parallel transfers the filter
+ *                   chain expects to handle at the same time.
+ *                   default: 1 if no filter overrides.
+ * - CONNECT_REPLY_MS: milliseconds until the first indication of a server
+ *                   response was received on a connect. For TCP, this
+ *                   reflects the time until the socket connected. On UDP
+ *                   this gives the time the first bytes from the server
+ *                   were received.
+ *                   -1 if not determined yet.
+ * - CF_QUERY_SOCKET: the socket used by the filter chain
+ */
+/*      query                             res1       res2     */
+#define CF_QUERY_MAX_CONCURRENT     1  /* number     -        */
+#define CF_QUERY_CONNECT_REPLY_MS   2  /* number     -        */
+#define CF_QUERY_SOCKET             3  /* -          curl_socket_t */
+#define CF_QUERY_TIMER_CONNECT      4  /* -          struct curltime */
+#define CF_QUERY_TIMER_APPCONNECT   5  /* -          struct curltime */
+
+/**
+ * Query the cfilter for properties. Filters ignorant of a query will
+ * pass it "down" the filter chain.
+ */
+typedef CURLcode Curl_cft_query(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                int query, int *pres1, void *pres2);
+
+/**
+ * Type flags for connection filters. A filter can have none, one or
+ * many of those. Use to evaluate state/capabilities of a filter chain.
+ *
+ * CF_TYPE_IP_CONNECT: provides an IP connection or sth equivalent, like
+ *                     a CONNECT tunnel, a UNIX domain socket, a QUIC
+ *                     connection, etc.
+ * CF_TYPE_SSL:        provide SSL/TLS
+ * CF_TYPE_MULTIPLEX:  provides multiplexing of easy handles
+ */
 #define CF_TYPE_IP_CONNECT  (1 << 0)
 #define CF_TYPE_SSL         (1 << 1)
+#define CF_TYPE_MULTIPLEX   (1 << 2)
 
 /* A connection filter type, e.g. specific implementation. */
 struct Curl_cftype {
   const char *name;                       /* name of the filter type */
-  long flags;                             /* flags of filter type */
+  int flags;                              /* flags of filter type */
+  int log_level;                          /* log level for such filters */
   Curl_cft_destroy_this *destroy;         /* destroy resources of this cf */
-  Curl_cft_setup *setup;                  /* setup for a connection */
   Curl_cft_connect *connect;              /* establish connection */
   Curl_cft_close *close;                  /* close conn */
   Curl_cft_get_host *get_host;            /* host filter talks to */
@@ -119,8 +175,10 @@
   Curl_cft_data_pending *has_data_pending;/* conn has data pending */
   Curl_cft_send *do_send;                 /* send data */
   Curl_cft_recv *do_recv;                 /* receive data */
-  Curl_cft_attach_data *attach_data;      /* data is being handled here */
-  Curl_cft_detach_data *detach_data;      /* data is no longer handled here */
+  Curl_cft_cntrl *cntrl;                  /* events/control */
+  Curl_cft_conn_is_alive *is_alive;       /* FALSE if conn is dead, Jim! */
+  Curl_cft_conn_keep_alive *keep_alive;   /* try to keep it alive */
+  Curl_cft_query *query;                  /* query filter chain */
 };
 
 /* A connection filter instance, e.g. registered at a connection */
@@ -129,7 +187,7 @@
   struct Curl_cfilter *next;     /* next filter in chain */
   void *ctx;                     /* filter type specific settings */
   struct connectdata *conn;      /* the connection this filter belongs to */
-  int sockindex;                 /* TODO: like to get rid off this */
+  int sockindex;                 /* the index the filter is installed at */
   BIT(connected);                /* != 0 iff this filter is connected */
 };
 
@@ -139,9 +197,6 @@
 
 /* Default implementations for the type functions, implementing pass-through
  * the filter chain. */
-CURLcode Curl_cf_def_setup(struct Curl_cfilter *cf,
-                           struct Curl_easy *data,
-                           const struct Curl_dns_entry *remotehost);
 void     Curl_cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data);
 CURLcode Curl_cf_def_connect(struct Curl_cfilter *cf,
                              struct Curl_easy *data,
@@ -158,16 +213,23 @@
                           const void *buf, size_t len, CURLcode *err);
 ssize_t  Curl_cf_def_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
                           char *buf, size_t len, CURLcode *err);
-void     Curl_cf_def_attach_data(struct Curl_cfilter *cf,
-                                 struct Curl_easy *data);
-void     Curl_cf_def_detach_data(struct Curl_cfilter *cf,
-                                 struct Curl_easy *data);
+CURLcode Curl_cf_def_cntrl(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                int event, int arg1, void *arg2);
+bool     Curl_cf_def_conn_is_alive(struct Curl_cfilter *cf,
+                                   struct Curl_easy *data,
+                                   bool *input_pending);
+CURLcode Curl_cf_def_conn_keep_alive(struct Curl_cfilter *cf,
+                                     struct Curl_easy *data);
+CURLcode Curl_cf_def_query(struct Curl_cfilter *cf,
+                           struct Curl_easy *data,
+                           int query, int *pres1, void *pres2);
 
 /**
  * Create a new filter instance, unattached to the filter chain.
  * Use Curl_conn_cf_add() to add it to the chain.
  * @param pcf  on success holds the created instance
- * @parm cft   the filter type
+ * @param cft   the filter type
  * @param ctx  the type specific context to use
  */
 CURLcode Curl_cf_create(struct Curl_cfilter **pcf,
@@ -176,7 +238,7 @@
 
 /**
  * Add a filter instance to the `sockindex` filter chain at connection
- * `data->conn`. The filter must not already be attached. It is inserted at
+ * `conn`. The filter must not already be attached. It is inserted at
  * the start of the chain (top).
  */
 void Curl_conn_cf_add(struct Curl_easy *data,
@@ -185,11 +247,11 @@
                       struct Curl_cfilter *cf);
 
 /**
- * Remove and destroy all filters at chain `sockindex` on connection `conn`.
+ * Insert a filter (chain) after `cf_at`.
+ * `cf_new` must not already be attached.
  */
-void Curl_conn_cf_discard_all(struct Curl_easy *data,
-                              struct connectdata *conn,
-                              int sockindex);
+void Curl_conn_cf_insert_after(struct Curl_cfilter *cf_at,
+                               struct Curl_cfilter *cf_new);
 
 /**
  * Discard, e.g. remove and destroy a specific filter instance.
@@ -198,29 +260,51 @@
  */
 void Curl_conn_cf_discard(struct Curl_cfilter *cf, struct Curl_easy *data);
 
+/**
+ * Discard all cfilters starting with `*pcf` and clearing it afterwards.
+ */
+void Curl_conn_cf_discard_chain(struct Curl_cfilter **pcf,
+                                struct Curl_easy *data);
 
+/**
+ * Remove and destroy all filters at chain `sockindex` on connection `conn`.
+ */
+void Curl_conn_cf_discard_all(struct Curl_easy *data,
+                              struct connectdata *conn,
+                              int sockindex);
+
+
+CURLcode Curl_conn_cf_connect(struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
+                              bool blocking, bool *done);
+void Curl_conn_cf_close(struct Curl_cfilter *cf, struct Curl_easy *data);
+int Curl_conn_cf_get_select_socks(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  curl_socket_t *socks);
+bool Curl_conn_cf_data_pending(struct Curl_cfilter *cf,
+                               const struct Curl_easy *data);
 ssize_t Curl_conn_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data,
                           const void *buf, size_t len, CURLcode *err);
 ssize_t Curl_conn_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
                           char *buf, size_t len, CURLcode *err);
+CURLcode Curl_conn_cf_cntrl(struct Curl_cfilter *cf,
+                            struct Curl_easy *data,
+                            bool ignore_result,
+                            int event, int arg1, void *arg2);
+
+/**
+ * Get the socket used by the filter chain starting at `cf`.
+ * Returns CURL_SOCKET_BAD if not available.
+ */
+curl_socket_t Curl_conn_cf_get_socket(struct Curl_cfilter *cf,
+                                      struct Curl_easy *data);
+
 
 #define CURL_CF_SSL_DEFAULT  -1
 #define CURL_CF_SSL_DISABLE  0
 #define CURL_CF_SSL_ENABLE   1
 
 /**
- * Setup the filter chain at `sockindex` in connection `conn`, invoking
- * the instance `setup(remotehost)` methods. If no filter chain is
- * installed yet, inspects the configuration in `data` to install a
- * suitable filter chain.
- */
-CURLcode Curl_conn_setup(struct Curl_easy *data,
-                         struct connectdata *conn,
-                         int sockindex,
-                         const struct Curl_dns_entry *remotehost,
-                         int ssl_mode);
-
-/**
  * Bring the filter chain at `sockindex` for connection `data->conn` into
  * connected state. Which will set `*done` to TRUE.
  * This can be called on an already connected chain with no side effects.
@@ -248,7 +332,12 @@
  * (or will be once connected). This will return FALSE, if SSL
  * is only used in proxying and not for the tunnel itself.
  */
-bool Curl_conn_is_ssl(struct Curl_easy *data, int sockindex);
+bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex);
+
+/**
+ * Connection provides multiplexing of easy handles at `socketindex`.
+ */
+bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex);
 
 /**
  * Close the filter chain at `sockindex` for connection `data->conn`.
@@ -264,6 +353,12 @@
                             int sockindex);
 
 /**
+ * Return the socket used on data's connection for the index.
+ * Returns CURL_SOCKET_BAD if not available.
+ */
+curl_socket_t Curl_conn_get_socket(struct Curl_easy *data, int sockindex);
+
+/**
  * Get any select fd flags and the socket filters at chain `sockindex`
  * at connection `conn` might be waiting for.
  */
@@ -289,13 +384,12 @@
                        const void *buf, size_t len, CURLcode *code);
 
 /**
- * The easy handle `data` is being attached (served) by connection `conn`.
- * All filters are informed to adapt to handling `data`.
- * Note: there may be several `data` attached to a connection at the same
- * time.
+ * The easy handle `data` is being attached to `conn`. This does
+ * not mean that data will actually do a transfer. Attachment is
+ * also used for temporary actions on the connection.
  */
-void Curl_conn_attach_data(struct connectdata *conn,
-                           struct Curl_easy *data);
+void Curl_conn_ev_data_attach(struct connectdata *conn,
+                              struct Curl_easy *data);
 
 /**
  * The easy handle `data` is being detached (no longer served)
@@ -304,12 +398,142 @@
  * Note: there may be several `data` attached to a connection at the same
  * time.
  */
-void Curl_conn_detach_data(struct connectdata *conn,
-                           struct Curl_easy *data);
+void Curl_conn_ev_data_detach(struct connectdata *conn,
+                              struct Curl_easy *data);
+
+/**
+ * Notify connection filters that they need to setup data for
+ * a transfer.
+ */
+CURLcode Curl_conn_ev_data_setup(struct Curl_easy *data);
+
+/**
+ * Notify connection filters that now would be a good time to
+ * perform any idle, e.g. time related, actions.
+ */
+CURLcode Curl_conn_ev_data_idle(struct Curl_easy *data);
+
+/**
+ * Notify connection filters that the transfer represented by `data`
+ * is donw with sending data (e.g. has uploaded everything).
+ */
+void Curl_conn_ev_data_done_send(struct Curl_easy *data);
+
+/**
+ * Notify connection filters that the transfer represented by `data`
+ * is finished - eventually premature, e.g. before being complete.
+ */
+void Curl_conn_ev_data_done(struct Curl_easy *data, bool premature);
+
+/**
+ * Notify connection filters that the transfer of data is paused/unpaused.
+ */
+CURLcode Curl_conn_ev_data_pause(struct Curl_easy *data, bool do_pause);
+
+/**
+ * Inform connection filters to update their info in `conn`.
+ */
+void Curl_conn_ev_update_info(struct Curl_easy *data,
+                              struct connectdata *conn);
+
+/**
+ * Update connection statistics
+ */
+void Curl_conn_report_connect_stats(struct Curl_easy *data,
+                                    struct connectdata *conn);
+
+/**
+ * Check if FIRSTSOCKET's cfilter chain deems connection alive.
+ */
+bool Curl_conn_is_alive(struct Curl_easy *data, struct connectdata *conn,
+                        bool *input_pending);
+
+/**
+ * Try to upkeep the connection filters at sockindex.
+ */
+CURLcode Curl_conn_keep_alive(struct Curl_easy *data,
+                              struct connectdata *conn,
+                              int sockindex);
 
 void Curl_conn_get_host(struct Curl_easy *data, int sockindex,
                         const char **phost, const char **pdisplay_host,
                         int *pport);
 
+/**
+ * Get the maximum number of parallel transfers the connection
+ * expects to be able to handle at `sockindex`.
+ */
+size_t Curl_conn_get_max_concurrent(struct Curl_easy *data,
+                                    struct connectdata *conn,
+                                    int sockindex);
+
+
+/**
+ * Types and macros used to keep the current easy handle in filter calls,
+ * allowing for nested invocations. See #10336.
+ *
+ * `cf_call_data` is intended to be a member of the cfilter's `ctx` type.
+ * A filter defines the macro `CF_CTX_CALL_DATA` to give access to that.
+ *
+ * With all values 0, the default, this indicates that there is no cfilter
+ * call with `data` ongoing.
+ * Macro `CF_DATA_SAVE` preserves the current `cf_call_data` in a local
+ * variable and sets the `data` given, incrementing the `depth` counter.
+ *
+ * Macro `CF_DATA_RESTORE` restores the old values from the local variable,
+ * while checking that `depth` values are as expected (debug build), catching
+ * cases where a "lower" RESTORE was not called.
+ *
+ * Finally, macro `CF_DATA_CURRENT` gives the easy handle of the current
+ * invocation.
+ */
+struct cf_call_data {
+  struct Curl_easy *data;
+#ifdef DEBUGBUILD
+  int depth;
+#endif
+};
+
+/**
+ * define to access the `struct cf_call_data for a cfilter. Normally
+ * a member in the cfilter's `ctx`.
+ *
+ * #define CF_CTX_CALL_DATA(cf)   -> struct cf_call_data instance
+*/
+
+#ifdef DEBUGBUILD
+
+#define CF_DATA_SAVE(save, cf, data) \
+  do { \
+    (save) = CF_CTX_CALL_DATA(cf); \
+    DEBUGASSERT((save).data == NULL || (save).depth > 0); \
+    CF_CTX_CALL_DATA(cf).depth++;  \
+    CF_CTX_CALL_DATA(cf).data = (struct Curl_easy *)data; \
+  } while(0)
+
+#define CF_DATA_RESTORE(cf, save) \
+  do { \
+    DEBUGASSERT(CF_CTX_CALL_DATA(cf).depth == (save).depth + 1); \
+    DEBUGASSERT((save).data == NULL || (save).depth > 0); \
+    CF_CTX_CALL_DATA(cf) = (save); \
+  } while(0)
+
+#else /* DEBUGBUILD */
+
+#define CF_DATA_SAVE(save, cf, data) \
+  do { \
+    (save) = CF_CTX_CALL_DATA(cf); \
+    CF_CTX_CALL_DATA(cf).data = (struct Curl_easy *)data; \
+  } while(0)
+
+#define CF_DATA_RESTORE(cf, save) \
+  do { \
+    CF_CTX_CALL_DATA(cf) = (save); \
+  } while(0)
+
+#endif /* !DEBUGBUILD */
+
+#define CF_DATA_CURRENT(cf) \
+  ((cf)? (CF_CTX_CALL_DATA(cf).data) : NULL)
 
 #endif /* HEADER_CURL_CFILTERS_H */
diff --git a/Utilities/cmcurl/lib/conncache.c b/Utilities/cmcurl/lib/conncache.c
index a557ac6..1c736da 100644
--- a/Utilities/cmcurl/lib/conncache.c
+++ b/Utilities/cmcurl/lib/conncache.c
@@ -5,8 +5,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012 - 2016, Linus Nielsen Feltzing, <linus@haxx.se>
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Linus Nielsen Feltzing, <linus@haxx.se>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -45,13 +45,6 @@
 
 #define HASHKEY_SIZE 128
 
-static void conn_llist_dtor(void *user, void *element)
-{
-  struct connectdata *conn = element;
-  (void)user;
-  conn->bundle = NULL;
-}
-
 static CURLcode bundle_create(struct connectbundle **bundlep)
 {
   DEBUGASSERT(*bundlep == NULL);
@@ -62,17 +55,12 @@
   (*bundlep)->num_connections = 0;
   (*bundlep)->multiuse = BUNDLE_UNKNOWN;
 
-  Curl_llist_init(&(*bundlep)->conn_list, (Curl_llist_dtor) conn_llist_dtor);
+  Curl_llist_init(&(*bundlep)->conn_list, NULL);
   return CURLE_OK;
 }
 
 static void bundle_destroy(struct connectbundle *bundle)
 {
-  if(!bundle)
-    return;
-
-  Curl_llist_destroy(&bundle->conn_list, NULL);
-
   free(bundle);
 }
 
diff --git a/Utilities/cmcurl/lib/conncache.h b/Utilities/cmcurl/lib/conncache.h
index 94664bc..959767d 100644
--- a/Utilities/cmcurl/lib/conncache.h
+++ b/Utilities/cmcurl/lib/conncache.h
@@ -7,8 +7,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2015 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2012 - 2014, Linus Nielsen Feltzing, <linus@haxx.se>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Linus Nielsen Feltzing, <linus@haxx.se>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/connect.c b/Utilities/cmcurl/lib/connect.c
index af04138..10d0c11 100644
--- a/Utilities/cmcurl/lib/connect.c
+++ b/Utilities/cmcurl/lib/connect.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -59,106 +59,30 @@
 #include "strerror.h"
 #include "cfilters.h"
 #include "connect.h"
+#include "cf-https-connect.h"
+#include "cf-socket.h"
 #include "select.h"
 #include "url.h" /* for Curl_safefree() */
 #include "multiif.h"
 #include "sockaddr.h" /* required for Curl_sockaddr_storage */
 #include "inet_ntop.h"
 #include "inet_pton.h"
-#include "vtls/vtls.h" /* for Curl_ssl_check_cxn() */
+#include "vtls/vtls.h" /* for vtsl cfilters */
 #include "progress.h"
 #include "warnless.h"
 #include "conncache.h"
 #include "multihandle.h"
 #include "share.h"
 #include "version_win32.h"
-#include "quic.h"
+#include "vquic/vquic.h" /* for quic cfilters */
+#include "http_proxy.h"
+#include "socks.h"
 
 /* The last 3 #include files should be in this order */
 #include "curl_printf.h"
 #include "curl_memory.h"
 #include "memdebug.h"
 
-static bool verifyconnect(curl_socket_t sockfd, int *error);
-
-#if defined(__DragonFly__) || defined(HAVE_WINSOCK2_H)
-/* DragonFlyBSD and Windows use millisecond units */
-#define KEEPALIVE_FACTOR(x) (x *= 1000)
-#else
-#define KEEPALIVE_FACTOR(x)
-#endif
-
-#if defined(HAVE_WINSOCK2_H) && !defined(SIO_KEEPALIVE_VALS)
-#define SIO_KEEPALIVE_VALS    _WSAIOW(IOC_VENDOR,4)
-
-struct tcp_keepalive {
-  u_long onoff;
-  u_long keepalivetime;
-  u_long keepaliveinterval;
-};
-#endif
-
-static void
-tcpkeepalive(struct Curl_easy *data,
-             curl_socket_t sockfd)
-{
-  int optval = data->set.tcp_keepalive?1:0;
-
-  /* only set IDLE and INTVL if setting KEEPALIVE is successful */
-  if(setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE,
-        (void *)&optval, sizeof(optval)) < 0) {
-    infof(data, "Failed to set SO_KEEPALIVE on fd %d", sockfd);
-  }
-  else {
-#if defined(SIO_KEEPALIVE_VALS)
-    struct tcp_keepalive vals;
-    DWORD dummy;
-    vals.onoff = 1;
-    optval = curlx_sltosi(data->set.tcp_keepidle);
-    KEEPALIVE_FACTOR(optval);
-    vals.keepalivetime = optval;
-    optval = curlx_sltosi(data->set.tcp_keepintvl);
-    KEEPALIVE_FACTOR(optval);
-    vals.keepaliveinterval = optval;
-    if(WSAIoctl(sockfd, SIO_KEEPALIVE_VALS, (LPVOID) &vals, sizeof(vals),
-                NULL, 0, &dummy, NULL, NULL) != 0) {
-      infof(data, "Failed to set SIO_KEEPALIVE_VALS on fd %d: %d",
-            (int)sockfd, WSAGetLastError());
-    }
-#else
-#ifdef TCP_KEEPIDLE
-    optval = curlx_sltosi(data->set.tcp_keepidle);
-    KEEPALIVE_FACTOR(optval);
-    if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE,
-          (void *)&optval, sizeof(optval)) < 0) {
-      infof(data, "Failed to set TCP_KEEPIDLE on fd %d", sockfd);
-    }
-#elif defined(TCP_KEEPALIVE)
-    /* Mac OS X style */
-    optval = curlx_sltosi(data->set.tcp_keepidle);
-    KEEPALIVE_FACTOR(optval);
-    if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE,
-      (void *)&optval, sizeof(optval)) < 0) {
-      infof(data, "Failed to set TCP_KEEPALIVE on fd %d", sockfd);
-    }
-#endif
-#ifdef TCP_KEEPINTVL
-    optval = curlx_sltosi(data->set.tcp_keepintvl);
-    KEEPALIVE_FACTOR(optval);
-    if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL,
-          (void *)&optval, sizeof(optval)) < 0) {
-      infof(data, "Failed to set TCP_KEEPINTVL on fd %d", sockfd);
-    }
-#endif
-#endif
-  }
-}
-
-static CURLcode
-singleipconnect(struct Curl_easy *data,
-                struct connectdata *conn,
-                const struct Curl_addrinfo *ai, /* start connecting to this */
-                int tempindex);          /* 0 or 1 among the temp ones */
 
 /*
  * Curl_timeleft() returns the amount of milliseconds left allowed for the
@@ -230,387 +154,6 @@
   return timeout_ms;
 }
 
-static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
-                          curl_socket_t sockfd, int af, unsigned int scope)
-{
-  struct Curl_sockaddr_storage sa;
-  struct sockaddr *sock = (struct sockaddr *)&sa;  /* bind to this address */
-  curl_socklen_t sizeof_sa = 0; /* size of the data sock points to */
-  struct sockaddr_in *si4 = (struct sockaddr_in *)&sa;
-#ifdef ENABLE_IPV6
-  struct sockaddr_in6 *si6 = (struct sockaddr_in6 *)&sa;
-#endif
-
-  struct Curl_dns_entry *h = NULL;
-  unsigned short port = data->set.localport; /* use this port number, 0 for
-                                                "random" */
-  /* how many port numbers to try to bind to, increasing one at a time */
-  int portnum = data->set.localportrange;
-  const char *dev = data->set.str[STRING_DEVICE];
-  int error;
-#ifdef IP_BIND_ADDRESS_NO_PORT
-  int on = 1;
-#endif
-#ifndef ENABLE_IPV6
-  (void)scope;
-#endif
-
-  /*************************************************************
-   * Select device to bind socket to
-   *************************************************************/
-  if(!dev && !port)
-    /* no local kind of binding was requested */
-    return CURLE_OK;
-
-  memset(&sa, 0, sizeof(struct Curl_sockaddr_storage));
-
-  if(dev && (strlen(dev)<255) ) {
-    char myhost[256] = "";
-    int done = 0; /* -1 for error, 1 for address found */
-    bool is_interface = FALSE;
-    bool is_host = FALSE;
-    static const char *if_prefix = "if!";
-    static const char *host_prefix = "host!";
-
-    if(strncmp(if_prefix, dev, strlen(if_prefix)) == 0) {
-      dev += strlen(if_prefix);
-      is_interface = TRUE;
-    }
-    else if(strncmp(host_prefix, dev, strlen(host_prefix)) == 0) {
-      dev += strlen(host_prefix);
-      is_host = TRUE;
-    }
-
-    /* interface */
-    if(!is_host) {
-#ifdef SO_BINDTODEVICE
-      /* I am not sure any other OSs than Linux that provide this feature,
-       * and at the least I cannot test. --Ben
-       *
-       * This feature allows one to tightly bind the local socket to a
-       * particular interface.  This will force even requests to other
-       * local interfaces to go out the external interface.
-       *
-       *
-       * Only bind to the interface when specified as interface, not just
-       * as a hostname or ip address.
-       *
-       * interface might be a VRF, eg: vrf-blue, which means it cannot be
-       * converted to an IP address and would fail Curl_if2ip. Simply try
-       * to use it straight away.
-       */
-      if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE,
-                    dev, (curl_socklen_t)strlen(dev) + 1) == 0) {
-        /* This is typically "errno 1, error: Operation not permitted" if
-         * you're not running as root or another suitable privileged
-         * user.
-         * If it succeeds it means the parameter was a valid interface and
-         * not an IP address. Return immediately.
-         */
-        return CURLE_OK;
-      }
-#endif
-
-      switch(Curl_if2ip(af,
-#ifdef ENABLE_IPV6
-                        scope, conn->scope_id,
-#endif
-                        dev, myhost, sizeof(myhost))) {
-        case IF2IP_NOT_FOUND:
-          if(is_interface) {
-            /* Do not fall back to treating it as a host name */
-            failf(data, "Couldn't bind to interface '%s'", dev);
-            return CURLE_INTERFACE_FAILED;
-          }
-          break;
-        case IF2IP_AF_NOT_SUPPORTED:
-          /* Signal the caller to try another address family if available */
-          return CURLE_UNSUPPORTED_PROTOCOL;
-        case IF2IP_FOUND:
-          is_interface = TRUE;
-          /*
-           * We now have the numerical IP address in the 'myhost' buffer
-           */
-          infof(data, "Local Interface %s is ip %s using address family %i",
-                dev, myhost, af);
-          done = 1;
-          break;
-      }
-    }
-    if(!is_interface) {
-      /*
-       * This was not an interface, resolve the name as a host name
-       * or IP number
-       *
-       * Temporarily force name resolution to use only the address type
-       * of the connection. The resolve functions should really be changed
-       * to take a type parameter instead.
-       */
-      unsigned char ipver = conn->ip_version;
-      int rc;
-
-      if(af == AF_INET)
-        conn->ip_version = CURL_IPRESOLVE_V4;
-#ifdef ENABLE_IPV6
-      else if(af == AF_INET6)
-        conn->ip_version = CURL_IPRESOLVE_V6;
-#endif
-
-      rc = Curl_resolv(data, dev, 0, FALSE, &h);
-      if(rc == CURLRESOLV_PENDING)
-        (void)Curl_resolver_wait_resolv(data, &h);
-      conn->ip_version = ipver;
-
-      if(h) {
-        /* convert the resolved address, sizeof myhost >= INET_ADDRSTRLEN */
-        Curl_printable_address(h->addr, myhost, sizeof(myhost));
-        infof(data, "Name '%s' family %i resolved to '%s' family %i",
-              dev, af, myhost, h->addr->ai_family);
-        Curl_resolv_unlock(data, h);
-        if(af != h->addr->ai_family) {
-          /* bad IP version combo, signal the caller to try another address
-             family if available */
-          return CURLE_UNSUPPORTED_PROTOCOL;
-        }
-        done = 1;
-      }
-      else {
-        /*
-         * provided dev was no interface (or interfaces are not supported
-         * e.g. solaris) no ip address and no domain we fail here
-         */
-        done = -1;
-      }
-    }
-
-    if(done > 0) {
-#ifdef ENABLE_IPV6
-      /* IPv6 address */
-      if(af == AF_INET6) {
-#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
-        char *scope_ptr = strchr(myhost, '%');
-        if(scope_ptr)
-          *(scope_ptr++) = '\0';
-#endif
-        if(Curl_inet_pton(AF_INET6, myhost, &si6->sin6_addr) > 0) {
-          si6->sin6_family = AF_INET6;
-          si6->sin6_port = htons(port);
-#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
-          if(scope_ptr) {
-            /* The "myhost" string either comes from Curl_if2ip or from
-               Curl_printable_address. The latter returns only numeric scope
-               IDs and the former returns none at all.  So the scope ID, if
-               present, is known to be numeric */
-            unsigned long scope_id = strtoul(scope_ptr, NULL, 10);
-            if(scope_id > UINT_MAX)
-              return CURLE_UNSUPPORTED_PROTOCOL;
-
-            si6->sin6_scope_id = (unsigned int)scope_id;
-          }
-#endif
-        }
-        sizeof_sa = sizeof(struct sockaddr_in6);
-      }
-      else
-#endif
-      /* IPv4 address */
-      if((af == AF_INET) &&
-         (Curl_inet_pton(AF_INET, myhost, &si4->sin_addr) > 0)) {
-        si4->sin_family = AF_INET;
-        si4->sin_port = htons(port);
-        sizeof_sa = sizeof(struct sockaddr_in);
-      }
-    }
-
-    if(done < 1) {
-      /* errorbuf is set false so failf will overwrite any message already in
-         the error buffer, so the user receives this error message instead of a
-         generic resolve error. */
-      data->state.errorbuf = FALSE;
-      failf(data, "Couldn't bind to '%s'", dev);
-      return CURLE_INTERFACE_FAILED;
-    }
-  }
-  else {
-    /* no device was given, prepare sa to match af's needs */
-#ifdef ENABLE_IPV6
-    if(af == AF_INET6) {
-      si6->sin6_family = AF_INET6;
-      si6->sin6_port = htons(port);
-      sizeof_sa = sizeof(struct sockaddr_in6);
-    }
-    else
-#endif
-    if(af == AF_INET) {
-      si4->sin_family = AF_INET;
-      si4->sin_port = htons(port);
-      sizeof_sa = sizeof(struct sockaddr_in);
-    }
-  }
-#ifdef IP_BIND_ADDRESS_NO_PORT
-  (void)setsockopt(sockfd, SOL_IP, IP_BIND_ADDRESS_NO_PORT, &on, sizeof(on));
-#endif
-  for(;;) {
-    if(bind(sockfd, sock, sizeof_sa) >= 0) {
-      /* we succeeded to bind */
-      struct Curl_sockaddr_storage add;
-      curl_socklen_t size = sizeof(add);
-      memset(&add, 0, sizeof(struct Curl_sockaddr_storage));
-      if(getsockname(sockfd, (struct sockaddr *) &add, &size) < 0) {
-        char buffer[STRERROR_LEN];
-        data->state.os_errno = error = SOCKERRNO;
-        failf(data, "getsockname() failed with errno %d: %s",
-              error, Curl_strerror(error, buffer, sizeof(buffer)));
-        return CURLE_INTERFACE_FAILED;
-      }
-      infof(data, "Local port: %hu", port);
-      conn->bits.bound = TRUE;
-      return CURLE_OK;
-    }
-
-    if(--portnum > 0) {
-      port++; /* try next port */
-      if(port == 0)
-        break;
-      infof(data, "Bind to local port %hu failed, trying next", port - 1);
-      /* We re-use/clobber the port variable here below */
-      if(sock->sa_family == AF_INET)
-        si4->sin_port = ntohs(port);
-#ifdef ENABLE_IPV6
-      else
-        si6->sin6_port = ntohs(port);
-#endif
-    }
-    else
-      break;
-  }
-  {
-    char buffer[STRERROR_LEN];
-    data->state.os_errno = error = SOCKERRNO;
-    failf(data, "bind failed with errno %d: %s",
-          error, Curl_strerror(error, buffer, sizeof(buffer)));
-  }
-
-  return CURLE_INTERFACE_FAILED;
-}
-
-/*
- * verifyconnect() returns TRUE if the connect really has happened.
- */
-static bool verifyconnect(curl_socket_t sockfd, int *error)
-{
-  bool rc = TRUE;
-#ifdef SO_ERROR
-  int err = 0;
-  curl_socklen_t errSize = sizeof(err);
-
-#ifdef WIN32
-  /*
-   * In October 2003 we effectively nullified this function on Windows due to
-   * problems with it using all CPU in multi-threaded cases.
-   *
-   * In May 2004, we bring it back to offer more info back on connect failures.
-   * Gisle Vanem could reproduce the former problems with this function, but
-   * could avoid them by adding this SleepEx() call below:
-   *
-   *    "I don't have Rational Quantify, but the hint from his post was
-   *    ntdll::NtRemoveIoCompletion(). So I'd assume the SleepEx (or maybe
-   *    just Sleep(0) would be enough?) would release whatever
-   *    mutex/critical-section the ntdll call is waiting on.
-   *
-   *    Someone got to verify this on Win-NT 4.0, 2000."
-   */
-
-#ifdef _WIN32_WCE
-  Sleep(0);
-#else
-  SleepEx(0, FALSE);
-#endif
-
-#endif
-
-  if(0 != getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&err, &errSize))
-    err = SOCKERRNO;
-#ifdef _WIN32_WCE
-  /* Old WinCE versions don't support SO_ERROR */
-  if(WSAENOPROTOOPT == err) {
-    SET_SOCKERRNO(0);
-    err = 0;
-  }
-#endif
-#if defined(EBADIOCTL) && defined(__minix)
-  /* Minix 3.1.x doesn't support getsockopt on UDP sockets */
-  if(EBADIOCTL == err) {
-    SET_SOCKERRNO(0);
-    err = 0;
-  }
-#endif
-  if((0 == err) || (EISCONN == err))
-    /* we are connected, awesome! */
-    rc = TRUE;
-  else
-    /* This wasn't a successful connect */
-    rc = FALSE;
-  if(error)
-    *error = err;
-#else
-  (void)sockfd;
-  if(error)
-    *error = SOCKERRNO;
-#endif
-  return rc;
-}
-
-/* update tempaddr[tempindex] (to the next entry), makes sure to stick
-   to the correct family */
-static struct Curl_addrinfo *ainext(struct connectdata *conn,
-                                    int tempindex,
-                                    bool next) /* use next entry? */
-{
-  struct Curl_addrinfo *ai = conn->tempaddr[tempindex];
-  if(ai && next)
-    ai = ai->ai_next;
-  while(ai && (ai->ai_family != conn->tempfamily[tempindex]))
-    ai = ai->ai_next;
-  conn->tempaddr[tempindex] = ai;
-  return ai;
-}
-
-/* Used within the multi interface. Try next IP address, returns error if no
-   more address exists or error */
-static CURLcode trynextip(struct Curl_easy *data,
-                          struct connectdata *conn,
-                          int sockindex,
-                          int tempindex)
-{
-  CURLcode result = CURLE_COULDNT_CONNECT;
-
-  /* First clean up after the failed socket.
-     Don't close it yet to ensure that the next IP's socket gets a different
-     file descriptor, which can prevent bugs when the curl_multi_socket_action
-     interface is used with certain select() replacements such as kqueue. */
-  curl_socket_t fd_to_close = conn->tempsock[tempindex];
-  conn->tempsock[tempindex] = CURL_SOCKET_BAD;
-
-  if(sockindex == FIRSTSOCKET) {
-    struct Curl_addrinfo *ai = conn->tempaddr[tempindex];
-
-    while(ai) {
-      result = singleipconnect(data, conn, ai, tempindex);
-      if(result == CURLE_COULDNT_CONNECT) {
-        ai = ainext(conn, tempindex, TRUE);
-        continue;
-      }
-      break;
-    }
-  }
-
-  if(fd_to_close != CURL_SOCKET_BAD)
-    Curl_closesocket(data, conn, fd_to_close);
-
-  return result;
-}
-
 /* Copies connection info into the transfer handle to make it available when
    the transfer handle is no longer associated with the connection. */
 void Curl_persistconninfo(struct Curl_easy *data, struct connectdata *conn,
@@ -629,6 +172,28 @@
   data->info.conn_local_port = local_port;
 }
 
+static const struct Curl_addrinfo *
+addr_first_match(const struct Curl_addrinfo *addr, int family)
+{
+  while(addr) {
+    if(addr->ai_family == family)
+      return addr;
+    addr = addr->ai_next;
+  }
+  return NULL;
+}
+
+static const struct Curl_addrinfo *
+addr_next_match(const struct Curl_addrinfo *addr, int family)
+{
+  while(addr && addr->ai_next) {
+    addr = addr->ai_next;
+    if(addr->ai_family == family)
+      return addr;
+  }
+  return NULL;
+}
+
 /* retrieves ip address and port from a sockaddr structure.
    note it calls Curl_inet_ntop which sets errno on fail, not SOCKERRNO. */
 bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen,
@@ -686,689 +251,6 @@
   return FALSE;
 }
 
-/* retrieves the start/end point information of a socket of an established
-   connection */
-void Curl_conninfo_remote(struct Curl_easy *data,
-                          struct connectdata *conn, curl_socket_t sockfd)
-{
-#ifdef HAVE_GETPEERNAME
-  char buffer[STRERROR_LEN];
-  struct Curl_sockaddr_storage ssrem;
-  curl_socklen_t plen;
-  int port;
-  plen = sizeof(struct Curl_sockaddr_storage);
-  memset(&ssrem, 0, sizeof(ssrem));
-  if(getpeername(sockfd, (struct sockaddr*) &ssrem, &plen)) {
-    int error = SOCKERRNO;
-    failf(data, "getpeername() failed with errno %d: %s",
-          error, Curl_strerror(error, buffer, sizeof(buffer)));
-    return;
-  }
-  if(!Curl_addr2string((struct sockaddr*)&ssrem, plen,
-                       conn->primary_ip, &port)) {
-    failf(data, "ssrem inet_ntop() failed with errno %d: %s",
-          errno, Curl_strerror(errno, buffer, sizeof(buffer)));
-    return;
-  }
-#else
-  (void)data;
-  (void)conn;
-  (void)sockfd;
-#endif
-}
-
-/* retrieves the start/end point information of a socket of an established
-   connection */
-void Curl_conninfo_local(struct Curl_easy *data, curl_socket_t sockfd,
-                         char *local_ip, int *local_port)
-{
-#ifdef HAVE_GETSOCKNAME
-  char buffer[STRERROR_LEN];
-  struct Curl_sockaddr_storage ssloc;
-  curl_socklen_t slen;
-  slen = sizeof(struct Curl_sockaddr_storage);
-  memset(&ssloc, 0, sizeof(ssloc));
-  if(getsockname(sockfd, (struct sockaddr*) &ssloc, &slen)) {
-    int error = SOCKERRNO;
-    failf(data, "getsockname() failed with errno %d: %s",
-          error, Curl_strerror(error, buffer, sizeof(buffer)));
-    return;
-  }
-  if(!Curl_addr2string((struct sockaddr*)&ssloc, slen,
-                       local_ip, local_port)) {
-    failf(data, "ssloc inet_ntop() failed with errno %d: %s",
-          errno, Curl_strerror(errno, buffer, sizeof(buffer)));
-    return;
-  }
-#else
-  (void)data;
-  (void)sockfd;
-  (void)local_ip;
-  (void)local_port;
-#endif
-}
-
-/* retrieves the start/end point information of a socket of an established
-   connection */
-void Curl_updateconninfo(struct Curl_easy *data, struct connectdata *conn,
-                         curl_socket_t sockfd)
-{
-  /* 'local_ip' and 'local_port' get filled with local's numerical
-     ip address and port number whenever an outgoing connection is
-     **established** from the primary socket to a remote address. */
-  char local_ip[MAX_IPADR_LEN] = "";
-  int local_port = -1;
-
-  if(!conn->bits.reuse &&
-     (conn->transport != TRNSPRT_TCP || !conn->bits.tcp_fastopen))
-    Curl_conninfo_remote(data, conn, sockfd);
-  Curl_conninfo_local(data, sockfd, local_ip, &local_port);
-
-  /* persist connection info in session handle */
-  Curl_persistconninfo(data, conn, local_ip, local_port);
-}
-
-/*
- * post_connect() is called after a successful connect to the peer
- */
-static void post_connect(struct Curl_easy *data,
-                       struct connectdata *conn,
-                       int sockindex)
-{
-  Curl_updateconninfo(data, conn, conn->sock[sockindex]);
-  Curl_verboseconnect(data, conn);
-  data->info.numconnects++; /* to track the number of connections made */
-}
-
-/*
- * is_connected() checks if the socket has connected.
- */
-static CURLcode is_connected(struct Curl_easy *data,
-                             struct connectdata *conn,
-                             int sockindex,
-                             bool *connected)
-{
-  CURLcode result = CURLE_OK;
-  timediff_t allow;
-  int error = 0;
-  struct curltime now;
-  int rc = 0;
-  int i;
-
-  DEBUGASSERT(sockindex >= FIRSTSOCKET && sockindex <= SECONDARYSOCKET);
-
-  *connected = FALSE; /* a very negative world view is best */
-
-  now = Curl_now();
-
-  /* Check if any of the conn->tempsock we use for establishing connections
-   * succeeded and, if so, close any ongoing other ones.
-   * Transfer the successful conn->tempsock to conn->sock[sockindex]
-   * and set conn->tempsock to CURL_SOCKET_BAD.
-   * If transport is QUIC, we need to shutdown the ongoing 'other'
-   * connect attempts in a QUIC appropriate way. */
-  for(i = 0; i<2; i++) {
-    const int other = i ^ 1;
-    if(conn->tempsock[i] == CURL_SOCKET_BAD)
-      continue;
-    error = 0;
-#ifdef ENABLE_QUIC
-    if(conn->transport == TRNSPRT_QUIC) {
-      result = Curl_quic_is_connected(data, conn, i, connected);
-      if(!result && *connected) {
-        /* use this socket from now on */
-        conn->sock[sockindex] = conn->tempsock[i];
-        conn->ip_addr = conn->tempaddr[i];
-        conn->tempsock[i] = CURL_SOCKET_BAD;
-        post_connect(data, conn, sockindex);
-        connkeep(conn, "HTTP/3 default");
-        if(conn->tempsock[other] != CURL_SOCKET_BAD)
-          Curl_quic_disconnect(data, conn, other);
-        return CURLE_OK;
-      }
-      /* When a QUIC connect attempt fails, the better error explanation is in
-         'result' and not in errno */
-      if(result) {
-        conn->tempsock[i] = CURL_SOCKET_BAD;
-        error = SOCKERRNO;
-      }
-    }
-    else
-#endif
-    {
-#ifdef mpeix
-      /* Call this function once now, and ignore the results. We do this to
-         "clear" the error state on the socket so that we can later read it
-         reliably. This is reported necessary on the MPE/iX operating
-         system. */
-      (void)verifyconnect(conn->tempsock[i], NULL);
-#endif
-
-      /* check socket for connect */
-      rc = SOCKET_WRITABLE(conn->tempsock[i], 0);
-    }
-
-    if(rc == 0) { /* no connection yet */
-      if(Curl_timediff(now, conn->connecttime) >=
-         conn->timeoutms_per_addr[i]) {
-        infof(data, "After %" CURL_FORMAT_TIMEDIFF_T
-              "ms connect time, move on!", conn->timeoutms_per_addr[i]);
-        error = ETIMEDOUT;
-      }
-
-      /* should we try another protocol family? */
-      if(i == 0 && !conn->bits.parallel_connect &&
-         (Curl_timediff(now, conn->connecttime) >=
-          data->set.happy_eyeballs_timeout)) {
-        conn->bits.parallel_connect = TRUE; /* starting now */
-        trynextip(data, conn, sockindex, 1);
-      }
-    }
-    else if(rc == CURL_CSELECT_OUT || conn->bits.tcp_fastopen) {
-      if(verifyconnect(conn->tempsock[i], &error)) {
-        /* we are connected with TCP, awesome! */
-
-        /* use this socket from now on */
-        conn->sock[sockindex] = conn->tempsock[i];
-        conn->ip_addr = conn->tempaddr[i];
-        conn->tempsock[i] = CURL_SOCKET_BAD;
-#ifdef ENABLE_IPV6
-        conn->bits.ipv6 = (conn->ip_addr->ai_family == AF_INET6)?TRUE:FALSE;
-#endif
-
-        /* close the other socket, if open */
-        if(conn->tempsock[other] != CURL_SOCKET_BAD) {
-          Curl_closesocket(data, conn, conn->tempsock[other]);
-          conn->tempsock[other] = CURL_SOCKET_BAD;
-        }
-
-        *connected = TRUE;
-        return CURLE_OK;
-      }
-    }
-    else if(rc & CURL_CSELECT_ERR) {
-      (void)verifyconnect(conn->tempsock[i], &error);
-    }
-
-    /*
-     * The connection failed here, we should attempt to connect to the "next
-     * address" for the given host. But first remember the latest error.
-     */
-    if(error) {
-      data->state.os_errno = error;
-      SET_SOCKERRNO(error);
-      if(conn->tempaddr[i]) {
-        CURLcode status;
-#ifndef CURL_DISABLE_VERBOSE_STRINGS
-        char ipaddress[MAX_IPADR_LEN];
-        char buffer[STRERROR_LEN];
-        Curl_printable_address(conn->tempaddr[i], ipaddress,
-                               sizeof(ipaddress));
-#ifdef ENABLE_QUIC
-        if(conn->transport == TRNSPRT_QUIC) {
-          infof(data, "connect to %s port %u failed: %s",
-                ipaddress, conn->port, curl_easy_strerror(result));
-        }
-        else
-#endif
-        infof(data, "connect to %s port %u failed: %s",
-              ipaddress, conn->port,
-              Curl_strerror(error, buffer, sizeof(buffer)));
-#endif
-
-        allow = Curl_timeleft(data, &now, TRUE);
-        conn->timeoutms_per_addr[i] = conn->tempaddr[i]->ai_next == NULL ?
-          allow : allow / 2;
-        ainext(conn, i, TRUE);
-        status = trynextip(data, conn, sockindex, i);
-        if((status != CURLE_COULDNT_CONNECT) ||
-           conn->tempsock[other] == CURL_SOCKET_BAD) {
-          /* the last attempt failed and no other sockets remain open */
-          if(!result)
-            result = status;
-        }
-      }
-    }
-  }
-
-  /*
-   * Now that we've checked whether we are connected, check whether we've
-   * already timed out.
-   *
-   * First figure out how long time we have left to connect */
-
-  allow = Curl_timeleft(data, &now, TRUE);
-
-  if(allow < 0) {
-    /* time-out, bail out, go home */
-    failf(data, "Connection timeout after %ld ms",
-          Curl_timediff(now, data->progress.t_startsingle));
-    return CURLE_OPERATION_TIMEDOUT;
-  }
-
-  if(result &&
-     (conn->tempsock[0] == CURL_SOCKET_BAD) &&
-     (conn->tempsock[1] == CURL_SOCKET_BAD)) {
-    /* no more addresses to try */
-    const char *hostname;
-    CURLcode failreason = result;
-
-    /* if the first address family runs out of addresses to try before the
-       happy eyeball timeout, go ahead and try the next family now */
-    result = trynextip(data, conn, sockindex, 1);
-    if(!result)
-      return result;
-
-    result = failreason;
-
-#ifndef CURL_DISABLE_PROXY
-    if(conn->bits.socksproxy)
-      hostname = conn->socks_proxy.host.name;
-    else if(conn->bits.httpproxy)
-      hostname = conn->http_proxy.host.name;
-    else
-#endif
-      if(conn->bits.conn_to_host)
-        hostname = conn->conn_to_host.name;
-    else
-      hostname = conn->host.name;
-
-    failf(data, "Failed to connect to %s port %u after "
-          "%" CURL_FORMAT_TIMEDIFF_T " ms: %s",
-          hostname, conn->port,
-          Curl_timediff(now, data->progress.t_startsingle),
-          curl_easy_strerror(result));
-
-    Curl_quic_disconnect(data, conn, 0);
-    Curl_quic_disconnect(data, conn, 1);
-
-#ifdef WSAETIMEDOUT
-    if(WSAETIMEDOUT == data->state.os_errno)
-      result = CURLE_OPERATION_TIMEDOUT;
-#elif defined(ETIMEDOUT)
-    if(ETIMEDOUT == data->state.os_errno)
-      result = CURLE_OPERATION_TIMEDOUT;
-#endif
-  }
-  else
-    result = CURLE_OK; /* still trying */
-
-  return result;
-}
-
-static void tcpnodelay(struct Curl_easy *data, curl_socket_t sockfd)
-{
-#if defined(TCP_NODELAY)
-  curl_socklen_t onoff = (curl_socklen_t) 1;
-  int level = IPPROTO_TCP;
-#if !defined(CURL_DISABLE_VERBOSE_STRINGS)
-  char buffer[STRERROR_LEN];
-#else
-  (void) data;
-#endif
-
-  if(setsockopt(sockfd, level, TCP_NODELAY, (void *)&onoff,
-                sizeof(onoff)) < 0)
-    infof(data, "Could not set TCP_NODELAY: %s",
-          Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
-#else
-  (void)data;
-  (void)sockfd;
-#endif
-}
-
-#ifdef SO_NOSIGPIPE
-/* The preferred method on Mac OS X (10.2 and later) to prevent SIGPIPEs when
-   sending data to a dead peer (instead of relying on the 4th argument to send
-   being MSG_NOSIGNAL). Possibly also existing and in use on other BSD
-   systems? */
-static void nosigpipe(struct Curl_easy *data,
-                      curl_socket_t sockfd)
-{
-  int onoff = 1;
-  if(setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&onoff,
-                sizeof(onoff)) < 0) {
-#if !defined(CURL_DISABLE_VERBOSE_STRINGS)
-    char buffer[STRERROR_LEN];
-    infof(data, "Could not set SO_NOSIGPIPE: %s",
-          Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
-#endif
-  }
-}
-#else
-#define nosigpipe(x,y) Curl_nop_stmt
-#endif
-
-#ifdef USE_WINSOCK
-/* When you run a program that uses the Windows Sockets API, you may
-   experience slow performance when you copy data to a TCP server.
-
-   https://support.microsoft.com/kb/823764
-
-   Work-around: Make the Socket Send Buffer Size Larger Than the Program Send
-   Buffer Size
-
-   The problem described in this knowledge-base is applied only to pre-Vista
-   Windows.  Following function trying to detect OS version and skips
-   SO_SNDBUF adjustment for Windows Vista and above.
-*/
-#define DETECT_OS_NONE 0
-#define DETECT_OS_PREVISTA 1
-#define DETECT_OS_VISTA_OR_LATER 2
-
-void Curl_sndbufset(curl_socket_t sockfd)
-{
-  int val = CURL_MAX_WRITE_SIZE + 32;
-  int curval = 0;
-  int curlen = sizeof(curval);
-
-  static int detectOsState = DETECT_OS_NONE;
-
-  if(detectOsState == DETECT_OS_NONE) {
-    if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT,
-                                    VERSION_GREATER_THAN_EQUAL))
-      detectOsState = DETECT_OS_VISTA_OR_LATER;
-    else
-      detectOsState = DETECT_OS_PREVISTA;
-  }
-
-  if(detectOsState == DETECT_OS_VISTA_OR_LATER)
-    return;
-
-  if(getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char *)&curval, &curlen) == 0)
-    if(curval > val)
-      return;
-
-  setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&val, sizeof(val));
-}
-#endif
-
-/*
- * singleipconnect()
- *
- * Note that even on connect fail it returns CURLE_OK, but with 'sock' set to
- * CURL_SOCKET_BAD. Other errors will however return proper errors.
- *
- * singleipconnect() connects to the given IP only, and it may return without
- * having connected.
- */
-static CURLcode singleipconnect(struct Curl_easy *data,
-                                struct connectdata *conn,
-                                const struct Curl_addrinfo *ai,
-                                int tempindex)
-{
-  struct Curl_sockaddr_ex addr;
-  int rc = -1;
-  int error = 0;
-  bool isconnected = FALSE;
-  curl_socket_t sockfd;
-  CURLcode result;
-  char ipaddress[MAX_IPADR_LEN];
-  int port;
-  bool is_tcp;
-#ifdef TCP_FASTOPEN_CONNECT
-  int optval = 1;
-#endif
-  const char *ipmsg;
-  char buffer[STRERROR_LEN];
-  curl_socket_t *sockp = &conn->tempsock[tempindex];
-  *sockp = CURL_SOCKET_BAD;
-
-  result = Curl_socket(data, ai, &addr, &sockfd);
-  if(result)
-    return result;
-
-  /* store remote address and port used in this connection attempt */
-  if(!Curl_addr2string(&addr.sa_addr, addr.addrlen,
-                       ipaddress, &port)) {
-    /* malformed address or bug in inet_ntop, try next address */
-    failf(data, "sa_addr inet_ntop() failed with errno %d: %s",
-          errno, Curl_strerror(errno, buffer, sizeof(buffer)));
-    Curl_closesocket(data, conn, sockfd);
-    return CURLE_OK;
-  }
-#ifdef ENABLE_IPV6
-  if(addr.family == AF_INET6)
-    ipmsg = "  Trying [%s]:%d...";
-  else
-#endif
-    ipmsg = "  Trying %s:%d...";
-  infof(data, ipmsg, ipaddress, port);
-
-#ifdef ENABLE_IPV6
-  is_tcp = (addr.family == AF_INET || addr.family == AF_INET6) &&
-    addr.socktype == SOCK_STREAM;
-#else
-  is_tcp = (addr.family == AF_INET) && addr.socktype == SOCK_STREAM;
-#endif
-  if(is_tcp && data->set.tcp_nodelay)
-    tcpnodelay(data, sockfd);
-
-  nosigpipe(data, sockfd);
-
-  Curl_sndbufset(sockfd);
-
-  if(is_tcp && data->set.tcp_keepalive)
-    tcpkeepalive(data, sockfd);
-
-  if(data->set.fsockopt) {
-    /* activate callback for setting socket options */
-    Curl_set_in_callback(data, true);
-    error = data->set.fsockopt(data->set.sockopt_client,
-                               sockfd,
-                               CURLSOCKTYPE_IPCXN);
-    Curl_set_in_callback(data, false);
-
-    if(error == CURL_SOCKOPT_ALREADY_CONNECTED)
-      isconnected = TRUE;
-    else if(error) {
-      Curl_closesocket(data, conn, sockfd); /* close the socket and bail out */
-      return CURLE_ABORTED_BY_CALLBACK;
-    }
-  }
-
-  /* possibly bind the local end to an IP, interface or port */
-  if(addr.family == AF_INET
-#ifdef ENABLE_IPV6
-     || addr.family == AF_INET6
-#endif
-    ) {
-    result = bindlocal(data, conn, sockfd, addr.family,
-                       Curl_ipv6_scope(&addr.sa_addr));
-    if(result) {
-      Curl_closesocket(data, conn, sockfd); /* close socket and bail out */
-      if(result == CURLE_UNSUPPORTED_PROTOCOL) {
-        /* The address family is not supported on this interface.
-           We can continue trying addresses */
-        return CURLE_COULDNT_CONNECT;
-      }
-      return result;
-    }
-  }
-
-  /* set socket non-blocking */
-  (void)curlx_nonblock(sockfd, TRUE);
-
-  conn->connecttime = Curl_now();
-  if(conn->num_addr > 1) {
-    Curl_expire(data, conn->timeoutms_per_addr[0], EXPIRE_DNS_PER_NAME);
-    Curl_expire(data, conn->timeoutms_per_addr[1], EXPIRE_DNS_PER_NAME2);
-  }
-
-  /* Connect TCP and QUIC sockets */
-  if(!isconnected && (conn->transport != TRNSPRT_UDP)) {
-    if(conn->bits.tcp_fastopen) {
-#if defined(CONNECT_DATA_IDEMPOTENT) /* Darwin */
-#  if defined(HAVE_BUILTIN_AVAILABLE)
-      /* while connectx function is available since macOS 10.11 / iOS 9,
-         it did not have the interface declared correctly until
-         Xcode 9 / macOS SDK 10.13 */
-      if(__builtin_available(macOS 10.11, iOS 9.0, tvOS 9.0, watchOS 2.0, *)) {
-        sa_endpoints_t endpoints;
-        endpoints.sae_srcif = 0;
-        endpoints.sae_srcaddr = NULL;
-        endpoints.sae_srcaddrlen = 0;
-        endpoints.sae_dstaddr = &addr.sa_addr;
-        endpoints.sae_dstaddrlen = addr.addrlen;
-
-        rc = connectx(sockfd, &endpoints, SAE_ASSOCID_ANY,
-                      CONNECT_RESUME_ON_READ_WRITE | CONNECT_DATA_IDEMPOTENT,
-                      NULL, 0, NULL, NULL);
-      }
-      else {
-        rc = connect(sockfd, &addr.sa_addr, addr.addrlen);
-      }
-#  else
-      rc = connect(sockfd, &addr.sa_addr, addr.addrlen);
-#  endif /* HAVE_BUILTIN_AVAILABLE */
-#elif defined(TCP_FASTOPEN_CONNECT) /* Linux >= 4.11 */
-      if(setsockopt(sockfd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT,
-                    (void *)&optval, sizeof(optval)) < 0)
-        infof(data, "Failed to enable TCP Fast Open on fd %d", sockfd);
-
-      rc = connect(sockfd, &addr.sa_addr, addr.addrlen);
-#elif defined(MSG_FASTOPEN) /* old Linux */
-      if(conn->given->flags & PROTOPT_SSL)
-        rc = connect(sockfd, &addr.sa_addr, addr.addrlen);
-      else
-        rc = 0; /* Do nothing */
-#endif
-    }
-    else {
-      rc = connect(sockfd, &addr.sa_addr, addr.addrlen);
-    }
-
-    if(-1 == rc)
-      error = SOCKERRNO;
-#ifdef ENABLE_QUIC
-    else if(conn->transport == TRNSPRT_QUIC) {
-      /* pass in 'sockfd' separately since it hasn't been put into the
-         tempsock array at this point */
-      result = Curl_quic_connect(data, conn, sockfd, tempindex,
-                                 &addr.sa_addr, addr.addrlen);
-      if(result)
-        error = SOCKERRNO;
-    }
-#endif
-  }
-  else {
-    *sockp = sockfd;
-    return CURLE_OK;
-  }
-
-  if(-1 == rc) {
-    switch(error) {
-    case EINPROGRESS:
-    case EWOULDBLOCK:
-#if defined(EAGAIN)
-#if (EAGAIN) != (EWOULDBLOCK)
-      /* On some platforms EAGAIN and EWOULDBLOCK are the
-       * same value, and on others they are different, hence
-       * the odd #if
-       */
-    case EAGAIN:
-#endif
-#endif
-      result = CURLE_OK;
-      break;
-
-    default:
-      /* unknown error, fallthrough and try another address! */
-      infof(data, "Immediate connect fail for %s: %s",
-            ipaddress, Curl_strerror(error, buffer, sizeof(buffer)));
-      data->state.os_errno = error;
-
-      /* connect failed */
-      Curl_closesocket(data, conn, sockfd);
-      result = CURLE_COULDNT_CONNECT;
-    }
-  }
-
-  if(!result)
-    *sockp = sockfd;
-
-  return result;
-}
-
-/*
- * TCP connect to the given host with timeout, proxy or remote doesn't matter.
- * There might be more than one IP address to try out. Fill in the passed
- * pointer with the connected socket.
- */
-
-CURLcode Curl_connecthost(struct Curl_easy *data,
-                          struct connectdata *conn,  /* context */
-                          const struct Curl_dns_entry *remotehost)
-{
-  CURLcode result = CURLE_COULDNT_CONNECT;
-  int i;
-  timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE);
-
-  if(timeout_ms < 0) {
-    /* a precaution, no need to continue if time already is up */
-    failf(data, "Connection time-out");
-    return CURLE_OPERATION_TIMEDOUT;
-  }
-
-  conn->num_addr = Curl_num_addresses(remotehost->addr);
-  conn->tempaddr[0] = conn->tempaddr[1] = remotehost->addr;
-  conn->tempsock[0] = conn->tempsock[1] = CURL_SOCKET_BAD;
-
-  /* Max time for the next connection attempt */
-  conn->timeoutms_per_addr[0] =
-    conn->tempaddr[0]->ai_next == NULL ? timeout_ms : timeout_ms / 2;
-  conn->timeoutms_per_addr[1] =
-    conn->tempaddr[1]->ai_next == NULL ? timeout_ms : timeout_ms / 2;
-
-  if(conn->ip_version == CURL_IPRESOLVE_WHATEVER) {
-    /* any IP version is allowed */
-    conn->tempfamily[0] = conn->tempaddr[0]?
-      conn->tempaddr[0]->ai_family:0;
-#ifdef ENABLE_IPV6
-    conn->tempfamily[1] = conn->tempfamily[0] == AF_INET6 ?
-      AF_INET : AF_INET6;
-#else
-    conn->tempfamily[1] = AF_UNSPEC;
-#endif
-  }
-  else {
-    /* only one IP version is allowed */
-    conn->tempfamily[0] = (conn->ip_version == CURL_IPRESOLVE_V4) ?
-      AF_INET :
-#ifdef ENABLE_IPV6
-      AF_INET6;
-#else
-      AF_UNSPEC;
-#endif
-    conn->tempfamily[1] = AF_UNSPEC;
-
-    ainext(conn, 0, FALSE); /* find first address of the right type */
-  }
-
-  ainext(conn, 1, FALSE); /* assigns conn->tempaddr[1] accordingly */
-
-  DEBUGF(infof(data, "family0 == %s, family1 == %s",
-               conn->tempfamily[0] == AF_INET ? "v4" : "v6",
-               conn->tempfamily[1] == AF_INET ? "v4" : "v6"));
-
-  /* get through the list in family order in case of quick failures */
-  for(i = 0; (i < 2) && result; i++) {
-    while(conn->tempaddr[i]) {
-      result = singleipconnect(data, conn, conn->tempaddr[i], i);
-      if(!result)
-        break;
-      ainext(conn, i, TRUE);
-    }
-  }
-  if(result)
-    return result;
-
-  Curl_expire(data, data->set.happy_eyeballs_timeout,
-              EXPIRE_HAPPY_EYEBALLS);
-
-  return CURLE_OK;
-}
-
 struct connfind {
   long id_tofind;
   struct connectdata *found;
@@ -1431,174 +313,6 @@
 }
 
 /*
- * Check if a connection seems to be alive.
- */
-bool Curl_connalive(struct Curl_easy *data, struct connectdata *conn)
-{
-  (void)data;
-  /* First determine if ssl */
-  if(Curl_conn_is_ssl(data, FIRSTSOCKET)) {
-    /* use the SSL context */
-    if(!Curl_ssl_check_cxn(data, conn))
-      return false;   /* FIN received */
-  }
-/* Minix 3.1 doesn't support any flags on recv; just assume socket is OK */
-#ifdef MSG_PEEK
-  else if(conn->sock[FIRSTSOCKET] == CURL_SOCKET_BAD)
-    return false;
-  else {
-    /* use the socket */
-    char buf;
-    if(recv((RECV_TYPE_ARG1)conn->sock[FIRSTSOCKET], (RECV_TYPE_ARG2)&buf,
-            (RECV_TYPE_ARG3)1, (RECV_TYPE_ARG4)MSG_PEEK) == 0) {
-      return false;   /* FIN received */
-    }
-  }
-#endif
-  return true;
-}
-
-/*
- * Close a socket.
- *
- * 'conn' can be NULL, beware!
- */
-int Curl_closesocket(struct Curl_easy *data, struct connectdata *conn,
-                     curl_socket_t sock)
-{
-  if(conn && conn->fclosesocket) {
-    if((sock == conn->sock[SECONDARYSOCKET]) && conn->bits.sock_accepted)
-      /* if this socket matches the second socket, and that was created with
-         accept, then we MUST NOT call the callback but clear the accepted
-         status */
-      conn->bits.sock_accepted = FALSE;
-    else {
-      int rc;
-      Curl_multi_closed(data, sock);
-      Curl_set_in_callback(data, true);
-      rc = conn->fclosesocket(conn->closesocket_client, sock);
-      Curl_set_in_callback(data, false);
-      return rc;
-    }
-  }
-
-  if(conn)
-    /* tell the multi-socket code about this */
-    Curl_multi_closed(data, sock);
-
-  sclose(sock);
-
-  return 0;
-}
-
-/*
- * Create a socket based on info from 'conn' and 'ai'.
- *
- * 'addr' should be a pointer to the correct struct to get data back, or NULL.
- * 'sockfd' must be a pointer to a socket descriptor.
- *
- * If the open socket callback is set, used that!
- *
- */
-CURLcode Curl_socket(struct Curl_easy *data,
-                     const struct Curl_addrinfo *ai,
-                     struct Curl_sockaddr_ex *addr,
-                     curl_socket_t *sockfd)
-{
-  struct connectdata *conn = data->conn;
-  struct Curl_sockaddr_ex dummy;
-
-  if(!addr)
-    /* if the caller doesn't want info back, use a local temp copy */
-    addr = &dummy;
-
-  /*
-   * The Curl_sockaddr_ex structure is basically libcurl's external API
-   * curl_sockaddr structure with enough space available to directly hold
-   * any protocol-specific address structures. The variable declared here
-   * will be used to pass / receive data to/from the fopensocket callback
-   * if this has been set, before that, it is initialized from parameters.
-   */
-
-  addr->family = ai->ai_family;
-  switch(conn->transport) {
-  case TRNSPRT_TCP:
-    addr->socktype = SOCK_STREAM;
-    addr->protocol = IPPROTO_TCP;
-    break;
-  case TRNSPRT_UNIX:
-    addr->socktype = SOCK_STREAM;
-    addr->protocol = IPPROTO_IP;
-    break;
-  default: /* UDP and QUIC */
-    addr->socktype = SOCK_DGRAM;
-    addr->protocol = IPPROTO_UDP;
-    break;
-  }
-  addr->addrlen = ai->ai_addrlen;
-
-  if(addr->addrlen > sizeof(struct Curl_sockaddr_storage))
-     addr->addrlen = sizeof(struct Curl_sockaddr_storage);
-  memcpy(&addr->sa_addr, ai->ai_addr, addr->addrlen);
-
-  if(data->set.fopensocket) {
-   /*
-    * If the opensocket callback is set, all the destination address
-    * information is passed to the callback. Depending on this information the
-    * callback may opt to abort the connection, this is indicated returning
-    * CURL_SOCKET_BAD; otherwise it will return a not-connected socket. When
-    * the callback returns a valid socket the destination address information
-    * might have been changed and this 'new' address will actually be used
-    * here to connect.
-    */
-    Curl_set_in_callback(data, true);
-    *sockfd = data->set.fopensocket(data->set.opensocket_client,
-                                    CURLSOCKTYPE_IPCXN,
-                                    (struct curl_sockaddr *)addr);
-    Curl_set_in_callback(data, false);
-  }
-  else
-    /* opensocket callback not set, so simply create the socket now */
-    *sockfd = socket(addr->family, addr->socktype, addr->protocol);
-
-  if(*sockfd == CURL_SOCKET_BAD)
-    /* no socket, no connection */
-    return CURLE_COULDNT_CONNECT;
-
-  if(conn->transport == TRNSPRT_QUIC) {
-    /* QUIC sockets need to be nonblocking */
-    (void)curlx_nonblock(*sockfd, TRUE);
-    switch(addr->family) {
-#if defined(__linux__) && defined(IP_MTU_DISCOVER)
-    case AF_INET: {
-      int val = IP_PMTUDISC_DO;
-      (void)setsockopt(*sockfd, IPPROTO_IP, IP_MTU_DISCOVER, &val,
-                       sizeof(val));
-      break;
-    }
-#endif
-#if defined(__linux__) && defined(IPV6_MTU_DISCOVER)
-    case AF_INET6: {
-      int val = IPV6_PMTUDISC_DO;
-      (void)setsockopt(*sockfd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &val,
-                       sizeof(val));
-      break;
-    }
-#endif
-    }
-  }
-
-#if defined(ENABLE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID)
-  if(conn->scope_id && (addr->family == AF_INET6)) {
-    struct sockaddr_in6 * const sa6 = (void *)&addr->sa_addr;
-    sa6->sin6_scope_id = conn->scope_id;
-  }
-#endif
-
-  return CURLE_OK;
-}
-
-/*
  * Curl_conncontrol() marks streams or connection for closure.
  */
 void Curl_conncontrol(struct connectdata *conn,
@@ -1611,68 +325,554 @@
   /* close if a connection, or a stream that isn't multiplexed. */
   /* This function will be called both before and after this connection is
      associated with a transfer. */
-  bool closeit;
+  bool closeit, is_multiplex;
   DEBUGASSERT(conn);
 #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
   (void)reason; /* useful for debugging */
 #endif
+  is_multiplex = Curl_conn_is_multiplex(conn, FIRSTSOCKET);
   closeit = (ctrl == CONNCTRL_CONNECTION) ||
-    ((ctrl == CONNCTRL_STREAM) && !(conn->handler->flags & PROTOPT_STREAM));
-  if((ctrl == CONNCTRL_STREAM) &&
-     (conn->handler->flags & PROTOPT_STREAM))
-    ;
+    ((ctrl == CONNCTRL_STREAM) && !is_multiplex);
+  if((ctrl == CONNCTRL_STREAM) && is_multiplex)
+    ;  /* stream signal on multiplex conn never affects close state */
   else if((bit)closeit != conn->bits.close) {
     conn->bits.close = closeit; /* the only place in the source code that
                                    should assign this bit */
   }
 }
 
+/**
+ * job walking the matching addr infos, creating a sub-cfilter with the
+ * provided method `cf_create` and running setup/connect on it.
+ */
+struct eyeballer {
+  const char *name;
+  const struct Curl_addrinfo *addr;  /* List of addresses to try, not owned */
+  int ai_family;                     /* matching address family only */
+  cf_ip_connect_create *cf_create;   /* for creating cf */
+  struct Curl_cfilter *cf;           /* current sub-cfilter connecting */
+  struct eyeballer *primary;         /* eyeballer this one is backup for */
+  timediff_t delay_ms;               /* delay until start */
+  struct curltime started;           /* start of current attempt */
+  timediff_t timeoutms;              /* timeout for current attempt */
+  expire_id timeout_id;              /* ID for Curl_expire() */
+  CURLcode result;
+  int error;
+  BIT(has_started);                  /* attempts have started */
+  BIT(is_done);                      /* out of addresses/time */
+  BIT(connected);                    /* cf has connected */
+};
+
+
 typedef enum {
   SCFST_INIT,
   SCFST_WAITING,
   SCFST_DONE
 } cf_connect_state;
 
-struct socket_cf_ctx {
+struct cf_he_ctx {
+  int transport;
+  cf_ip_connect_create *cf_create;
   const struct Curl_dns_entry *remotehost;
   cf_connect_state state;
+  struct eyeballer *baller[2];
+  struct eyeballer *winner;
+  struct curltime started;
 };
 
-static int socket_cf_get_select_socks(struct Curl_cfilter *cf,
-                                      struct Curl_easy *data,
-                                      curl_socket_t *socks)
+static CURLcode eyeballer_new(struct eyeballer **pballer,
+                              cf_ip_connect_create *cf_create,
+                              const struct Curl_addrinfo *addr,
+                              int ai_family,
+                              struct eyeballer *primary,
+                              timediff_t delay_ms,
+                              timediff_t timeout_ms,
+                              expire_id timeout_id)
 {
-  struct connectdata *conn = cf->conn;
-  int i, s, rc = GETSOCK_BLANK;
+  struct eyeballer *baller;
 
-  (void)data;
-  if(cf->connected) {
-    return rc;
+  *pballer = NULL;
+  baller = calloc(1, sizeof(*baller) + 1000);
+  if(!baller)
+    return CURLE_OUT_OF_MEMORY;
+
+  baller->name = ((ai_family == AF_INET)? "ipv4" : (
+#ifdef ENABLE_IPV6
+                  (ai_family == AF_INET6)? "ipv6" :
+#endif
+                  "ip"));
+  baller->cf_create = cf_create;
+  baller->addr = addr;
+  baller->ai_family = ai_family;
+  baller->primary = primary;
+  baller->delay_ms = delay_ms;
+  baller->timeoutms = addr_next_match(baller->addr, baller->ai_family)?
+                        timeout_ms / 2 : timeout_ms;
+  baller->timeout_id = timeout_id;
+  baller->result = CURLE_COULDNT_CONNECT;
+
+  *pballer = baller;
+  return CURLE_OK;
+}
+
+static void baller_close(struct eyeballer *baller,
+                          struct Curl_easy *data)
+{
+  if(baller && baller->cf) {
+    Curl_conn_cf_discard_chain(&baller->cf, data);
+  }
+}
+
+static void baller_free(struct eyeballer *baller,
+                         struct Curl_easy *data)
+{
+  if(baller) {
+    baller_close(baller, data);
+    free(baller);
+  }
+}
+
+static void baller_next_addr(struct eyeballer *baller)
+{
+  baller->addr = addr_next_match(baller->addr, baller->ai_family);
+}
+
+/*
+ * Initiate a connect attempt walk.
+ *
+ * Note that even on connect fail it returns CURLE_OK, but with 'sock' set to
+ * CURL_SOCKET_BAD. Other errors will however return proper errors.
+ */
+static void baller_initiate(struct Curl_cfilter *cf,
+                            struct Curl_easy *data,
+                            struct eyeballer *baller)
+{
+  struct cf_he_ctx *ctx = cf->ctx;
+  struct Curl_cfilter *cf_prev = baller->cf;
+  struct Curl_cfilter *wcf;
+  CURLcode result;
+
+
+  /* Don't close a previous cfilter yet to ensure that the next IP's
+     socket gets a different file descriptor, which can prevent bugs when
+     the curl_multi_socket_action interface is used with certain select()
+     replacements such as kqueue. */
+  result = baller->cf_create(&baller->cf, data, cf->conn, baller->addr,
+                             ctx->transport);
+  if(result)
+    goto out;
+
+  /* the new filter might have sub-filters */
+  for(wcf = baller->cf; wcf; wcf = wcf->next) {
+    wcf->conn = cf->conn;
+    wcf->sockindex = cf->sockindex;
   }
 
-  for(i = s = 0; i<2; i++) {
-    if(conn->tempsock[i] != CURL_SOCKET_BAD) {
-      socks[s] = conn->tempsock[i];
-      rc |= GETSOCK_WRITESOCK(s);
-#ifdef ENABLE_QUIC
-      if(conn->transport == TRNSPRT_QUIC)
-        /* when connecting QUIC, we want to read the socket too */
-        rc |= GETSOCK_READSOCK(s);
+  if(addr_next_match(baller->addr, baller->ai_family)) {
+    Curl_expire(data, baller->timeoutms, baller->timeout_id);
+  }
+
+out:
+  if(result) {
+    DEBUGF(LOG_CF(data, cf, "%s failed", baller->name));
+    baller_close(baller, data);
+  }
+  if(cf_prev)
+    Curl_conn_cf_discard_chain(&cf_prev, data);
+  baller->result = result;
+}
+
+/**
+ * Start a connection attempt on the current baller address.
+ * Will return CURLE_OK on the first address where a socket
+ * could be created and the non-blocking connect started.
+ * Returns error when all remaining addresses have been tried.
+ */
+static CURLcode baller_start(struct Curl_cfilter *cf,
+                             struct Curl_easy *data,
+                             struct eyeballer *baller,
+                             timediff_t timeoutms)
+{
+  baller->error = 0;
+  baller->connected = FALSE;
+  baller->has_started = TRUE;
+
+  while(baller->addr) {
+    baller->started = Curl_now();
+    baller->timeoutms = addr_next_match(baller->addr, baller->ai_family) ?
+                         timeoutms / 2 : timeoutms;
+    baller_initiate(cf, data, baller);
+    if(!baller->result)
+      break;
+    baller_next_addr(baller);
+  }
+  if(!baller->addr) {
+    baller->is_done = TRUE;
+  }
+  return baller->result;
+}
+
+
+/* Used within the multi interface. Try next IP address, returns error if no
+   more address exists or error */
+static CURLcode baller_start_next(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  struct eyeballer *baller,
+                                  timediff_t timeoutms)
+{
+  if(cf->sockindex == FIRSTSOCKET) {
+    baller_next_addr(baller);
+    baller_start(cf, data, baller, timeoutms);
+  }
+  else {
+    baller->error = 0;
+    baller->connected = FALSE;
+    baller->has_started = TRUE;
+    baller->is_done = TRUE;
+    baller->result = CURLE_COULDNT_CONNECT;
+  }
+  return baller->result;
+}
+
+static CURLcode baller_connect(struct Curl_cfilter *cf,
+                               struct Curl_easy *data,
+                               struct eyeballer *baller,
+                               struct curltime *now,
+                               bool *connected)
+{
+  (void)cf;
+  *connected = baller->connected;
+  if(!baller->result &&  !*connected) {
+    /* evaluate again */
+    baller->result = Curl_conn_cf_connect(baller->cf, data, 0, connected);
+
+    if(!baller->result) {
+      if (*connected) {
+        baller->connected = TRUE;
+        baller->is_done = TRUE;
+      }
+      else if(Curl_timediff(*now, baller->started) >= baller->timeoutms) {
+        infof(data, "%s connect timeout after %" CURL_FORMAT_TIMEDIFF_T
+              "ms, move on!", baller->name, baller->timeoutms);
+#if defined(ETIMEDOUT)
+        baller->error = ETIMEDOUT;
 #endif
-      s++;
+        baller->result = CURLE_OPERATION_TIMEDOUT;
+      }
+    }
+  }
+  return baller->result;
+}
+
+/*
+ * is_connected() checks if the socket has connected.
+ */
+static CURLcode is_connected(struct Curl_cfilter *cf,
+                             struct Curl_easy *data,
+                             bool *connected)
+{
+  struct cf_he_ctx *ctx = cf->ctx;
+  struct connectdata *conn = cf->conn;
+  CURLcode result;
+  struct curltime now;
+  size_t i;
+  int ongoing, not_started;
+  const char *hostname;
+
+  /* Check if any of the conn->tempsock we use for establishing connections
+   * succeeded and, if so, close any ongoing other ones.
+   * Transfer the successful conn->tempsock to conn->sock[sockindex]
+   * and set conn->tempsock to CURL_SOCKET_BAD.
+   * If transport is QUIC, we need to shutdown the ongoing 'other'
+   * cot ballers in a QUIC appropriate way. */
+evaluate:
+  *connected = FALSE; /* a very negative world view is best */
+  now = Curl_now();
+  ongoing = not_started = 0;
+  for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) {
+    struct eyeballer *baller = ctx->baller[i];
+
+    if(!baller || baller->is_done)
+      continue;
+
+    if(!baller->has_started) {
+      ++not_started;
+      continue;
+    }
+    baller->result = baller_connect(cf, data, baller, &now, connected);
+    DEBUGF(LOG_CF(data, cf, "%s connect -> %d, connected=%d",
+                  baller->name, baller->result, *connected));
+
+    if(!baller->result) {
+      if(*connected) {
+        /* connected, declare the winner */
+        ctx->winner = baller;
+        ctx->baller[i] = NULL;
+        break;
+      }
+      else { /* still waiting */
+        ++ongoing;
+      }
+    }
+    else if(!baller->is_done) {
+      /* The baller failed to connect, start its next attempt */
+      if(baller->error) {
+        data->state.os_errno = baller->error;
+        SET_SOCKERRNO(baller->error);
+      }
+      baller_start_next(cf, data, baller, Curl_timeleft(data, &now, TRUE));
+      if(baller->is_done) {
+        DEBUGF(LOG_CF(data, cf, "%s done", baller->name));
+      }
+      else {
+        /* next attempt was started */
+        DEBUGF(LOG_CF(data, cf, "%s trying next", baller->name));
+        ++ongoing;
+      }
     }
   }
 
+  if(ctx->winner) {
+    *connected = TRUE;
+    return CURLE_OK;
+  }
+
+  /* Nothing connected, check the time before we might
+   * start new ballers or return ok. */
+  if((ongoing || not_started) && Curl_timeleft(data, &now, TRUE) < 0) {
+    failf(data, "Connection timeout after %ld ms",
+          Curl_timediff(now, data->progress.t_startsingle));
+    return CURLE_OPERATION_TIMEDOUT;
+  }
+
+  /* Check if we have any waiting ballers to start now. */
+  if(not_started > 0) {
+    int added = 0;
+
+    for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) {
+      struct eyeballer *baller = ctx->baller[i];
+
+      if(!baller || baller->has_started)
+        continue;
+      /* We start its primary baller has failed to connect or if
+       * its start delay_ms have expired */
+      if((baller->primary && baller->primary->is_done) ||
+          Curl_timediff(now, ctx->started) >= baller->delay_ms) {
+        baller_start(cf, data, baller, Curl_timeleft(data, &now, TRUE));
+        if(baller->is_done) {
+          DEBUGF(LOG_CF(data, cf, "%s done", baller->name));
+        }
+        else {
+          DEBUGF(LOG_CF(data, cf, "%s starting (timeout=%ldms)",
+                        baller->name, baller->timeoutms));
+          ++ongoing;
+          ++added;
+        }
+      }
+    }
+    if(added > 0)
+      goto evaluate;
+  }
+
+  if(ongoing > 0) {
+    /* We are still trying, return for more waiting */
+    *connected = FALSE;
+    return CURLE_OK;
+  }
+
+  /* all ballers have failed to connect. */
+  DEBUGF(LOG_CF(data, cf, "all eyeballers failed"));
+  result = CURLE_COULDNT_CONNECT;
+  for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) {
+    struct eyeballer *baller = ctx->baller[i];
+    DEBUGF(LOG_CF(data, cf, "%s assess started=%d, result=%d",
+                  baller?baller->name:NULL,
+                  baller?baller->has_started:0,
+                  baller?baller->result:0));
+    if(baller && baller->has_started && baller->result) {
+      result = baller->result;
+      break;
+    }
+  }
+
+#ifndef CURL_DISABLE_PROXY
+  if(conn->bits.socksproxy)
+    hostname = conn->socks_proxy.host.name;
+  else if(conn->bits.httpproxy)
+    hostname = conn->http_proxy.host.name;
+  else
+#endif
+    if(conn->bits.conn_to_host)
+      hostname = conn->conn_to_host.name;
+  else
+    hostname = conn->host.name;
+
+  failf(data, "Failed to connect to %s port %u after "
+        "%" CURL_FORMAT_TIMEDIFF_T " ms: %s",
+        hostname, conn->port,
+        Curl_timediff(now, data->progress.t_startsingle),
+        curl_easy_strerror(result));
+
+#ifdef WSAETIMEDOUT
+  if(WSAETIMEDOUT == data->state.os_errno)
+    result = CURLE_OPERATION_TIMEDOUT;
+#elif defined(ETIMEDOUT)
+  if(ETIMEDOUT == data->state.os_errno)
+    result = CURLE_OPERATION_TIMEDOUT;
+#endif
+
+  return result;
+}
+
+/*
+ * Connect to the given host with timeout, proxy or remote doesn't matter.
+ * There might be more than one IP address to try out.
+ */
+static CURLcode start_connect(struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
+                              const struct Curl_dns_entry *remotehost)
+{
+  struct cf_he_ctx *ctx = cf->ctx;
+  struct connectdata *conn = cf->conn;
+  CURLcode result = CURLE_COULDNT_CONNECT;
+  int ai_family0, ai_family1;
+  timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE);
+  const struct Curl_addrinfo *addr0, *addr1;
+
+  if(timeout_ms < 0) {
+    /* a precaution, no need to continue if time already is up */
+    failf(data, "Connection time-out");
+    return CURLE_OPERATION_TIMEDOUT;
+  }
+
+  ctx->started = Curl_now();
+
+  /* remotehost->addr is the list of addresses from the resolver, each
+   * with an address family. The list has at least one entry, possibly
+   * many more.
+   * We try at most 2 at a time, until we either get a connection or
+   * run out of addresses to try. Since likelihood of success is tied
+   * to the address family (e.g. IPV6 might not work at all ), we want
+   * the 2 connect attempt ballers to try different families, if possible.
+   *
+   */
+  if(conn->ip_version == CURL_IPRESOLVE_WHATEVER) {
+    /* any IP version is allowed */
+    ai_family0 = remotehost->addr?
+      remotehost->addr->ai_family : 0;
+#ifdef ENABLE_IPV6
+    ai_family1 = ai_family0 == AF_INET6 ?
+      AF_INET : AF_INET6;
+#else
+    ai_family1 = AF_UNSPEC;
+#endif
+  }
+  else {
+    /* only one IP version is allowed */
+    ai_family0 = (conn->ip_version == CURL_IPRESOLVE_V4) ?
+      AF_INET :
+#ifdef ENABLE_IPV6
+      AF_INET6;
+#else
+      AF_UNSPEC;
+#endif
+    ai_family1 = AF_UNSPEC;
+  }
+
+  /* Get the first address in the list that matches the family,
+   * this might give NULL, if we do not have any matches. */
+  addr0 = addr_first_match(remotehost->addr, ai_family0);
+  addr1 = addr_first_match(remotehost->addr, ai_family1);
+  if(!addr0 && addr1) {
+    /* switch around, so a single baller always uses addr0 */
+    addr0 = addr1;
+    ai_family0 = ai_family1;
+    addr1 = NULL;
+  }
+
+  /* We found no address that matches our criteria, we cannot connect */
+  if(!addr0) {
+    return CURLE_COULDNT_CONNECT;
+  }
+
+  memset(ctx->baller, 0, sizeof(ctx->baller));
+  result = eyeballer_new(&ctx->baller[0], ctx->cf_create, addr0, ai_family0,
+                          NULL, 0, /* no primary/delay, start now */
+                          timeout_ms,  EXPIRE_DNS_PER_NAME);
+  if(result)
+    return result;
+  DEBUGF(LOG_CF(data, cf, "created %s (timeout %ldms)",
+                ctx->baller[0]->name, ctx->baller[0]->timeoutms));
+  if(addr1) {
+    /* second one gets a delayed start */
+    result = eyeballer_new(&ctx->baller[1], ctx->cf_create, addr1, ai_family1,
+                            ctx->baller[0], /* wait on that to fail */
+                            /* or start this delayed */
+                            data->set.happy_eyeballs_timeout,
+                            timeout_ms,  EXPIRE_DNS_PER_NAME2);
+    if(result)
+      return result;
+    DEBUGF(LOG_CF(data, cf, "created %s (timeout %ldms)",
+                  ctx->baller[1]->name, ctx->baller[1]->timeoutms));
+  }
+
+  Curl_expire(data, data->set.happy_eyeballs_timeout,
+              EXPIRE_HAPPY_EYEBALLS);
+
+  return CURLE_OK;
+}
+
+static void cf_he_ctx_clear(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_he_ctx *ctx = cf->ctx;
+  size_t i;
+
+  DEBUGASSERT(ctx);
+  DEBUGASSERT(data);
+  for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) {
+    baller_free(ctx->baller[i], data);
+    ctx->baller[i] = NULL;
+  }
+  baller_free(ctx->winner, data);
+  ctx->winner = NULL;
+}
+
+static int cf_he_get_select_socks(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  curl_socket_t *socks)
+{
+  struct cf_he_ctx *ctx = cf->ctx;
+  size_t i, s;
+  int wrc, rc = GETSOCK_BLANK;
+  curl_socket_t wsocks[MAX_SOCKSPEREASYHANDLE];
+
+  if(cf->connected)
+    return cf->next->cft->get_select_socks(cf->next, data, socks);
+
+  for(i = s = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) {
+    struct eyeballer *baller = ctx->baller[i];
+    if(!baller || !baller->cf)
+      continue;
+
+    wrc = Curl_conn_cf_get_select_socks(baller->cf, data, wsocks);
+    if(wrc) {
+      /* TODO: we assume we get at most one socket back */
+      socks[s] = wsocks[0];
+      if(wrc & GETSOCK_WRITESOCK(0))
+        rc |= GETSOCK_WRITESOCK(s);
+      if(wrc & GETSOCK_READSOCK(0))
+        rc |= GETSOCK_READSOCK(s);
+      s++;
+    }
+  }
   return rc;
 }
 
-static CURLcode socket_cf_connect(struct Curl_cfilter *cf,
-                                  struct Curl_easy *data,
-                                  bool blocking, bool *done)
+static CURLcode cf_he_connect(struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
+                              bool blocking, bool *done)
 {
-  struct connectdata *conn = cf->conn;
-  int sockindex = cf->sockindex;
-  struct socket_cf_ctx *ctx = cf->ctx;
+  struct cf_he_ctx *ctx = cf->ctx;
   CURLcode result = CURLE_OK;
 
   if(cf->connected) {
@@ -1680,27 +880,39 @@
     return CURLE_OK;
   }
 
-  (void)blocking;
+  (void)blocking; /* TODO: do we want to support this? */
   DEBUGASSERT(ctx);
   *done = FALSE;
+
   switch(ctx->state) {
     case SCFST_INIT:
-      DEBUGASSERT(CURL_SOCKET_BAD == conn->sock[sockindex]);
+      DEBUGASSERT(CURL_SOCKET_BAD == Curl_conn_cf_get_socket(cf, data));
       DEBUGASSERT(!cf->connected);
-      result = Curl_connecthost(data, conn, ctx->remotehost);
-      if(!result)
-        ctx->state = SCFST_WAITING;
-      break;
+      result = start_connect(cf, data, ctx->remotehost);
+      if(result)
+        return result;
+      ctx->state = SCFST_WAITING;
+      /* FALLTHROUGH */
     case SCFST_WAITING:
-      result = is_connected(data, conn, sockindex, done);
+      result = is_connected(cf, data, done);
       if(!result && *done) {
-        Curl_pgrsTime(data, TIMER_CONNECT);    /* we're connected already */
-        if(Curl_conn_is_ssl(data, FIRSTSOCKET) ||
-           (conn->handler->protocol & PROTO_FAMILY_SSH))
-          Curl_pgrsTime(data, TIMER_APPCONNECT); /* we're connected already */
-        post_connect(data, conn, sockindex);
+        DEBUGASSERT(ctx->winner);
+        DEBUGASSERT(ctx->winner->cf);
+        DEBUGASSERT(ctx->winner->cf->connected);
+        /* we have a winner. Install and activate it.
+         * close/free all others. */
         ctx->state = SCFST_DONE;
         cf->connected = TRUE;
+        cf->next = ctx->winner->cf;
+        ctx->winner->cf = NULL;
+        cf_he_ctx_clear(cf, data);
+        Curl_conn_cf_cntrl(cf->next, data, TRUE,
+                           CF_CTRL_CONN_INFO_UPDATE, 0, NULL);
+
+        if(cf->conn->handler->protocol & PROTO_FAMILY_SSH)
+          Curl_pgrsTime(data, TIMER_APPCONNECT); /* we're connected already */
+        Curl_verboseconnect(data, cf->conn);
+        data->info.numconnects++; /* to track the # of connections made */
       }
       break;
     case SCFST_DONE:
@@ -1710,223 +922,508 @@
   return result;
 }
 
-static CURLcode socket_cf_setup(struct Curl_cfilter *cf,
-                                struct Curl_easy *data,
-                                const struct Curl_dns_entry *remotehost)
+static void cf_he_close(struct Curl_cfilter *cf,
+                        struct Curl_easy *data)
 {
-  struct socket_cf_ctx *ctx = cf->ctx;
+  struct cf_he_ctx *ctx = cf->ctx;
 
-  (void)data;
-  DEBUGASSERT(ctx);
-  if(ctx->remotehost != remotehost) {
-    if(ctx->remotehost) {
-      /* switching dns entry? TODO: reset? */
-    }
-    ctx->remotehost = remotehost;
-  }
-  DEBUGF(infof(data, CFMSG(cf, "setup(remotehost=%s)"),
-         cf->conn->hostname_resolve));
-  return CURLE_OK;
-}
-
-static void socket_cf_close(struct Curl_cfilter *cf,
-                            struct Curl_easy *data)
-{
-  int sockindex = cf->sockindex;
-  struct socket_cf_ctx *ctx = cf->ctx;
-
- DEBUGASSERT(ctx);
-   /* close possibly still open sockets */
-  if(CURL_SOCKET_BAD != cf->conn->sock[sockindex]) {
-    Curl_closesocket(data, cf->conn, cf->conn->sock[sockindex]);
-    cf->conn->sock[sockindex] = CURL_SOCKET_BAD;
-  }
-  if(CURL_SOCKET_BAD != cf->conn->tempsock[sockindex]) {
-    Curl_closesocket(data, cf->conn, cf->conn->tempsock[sockindex]);
-    cf->conn->tempsock[sockindex] = CURL_SOCKET_BAD;
-  }
+  DEBUGF(LOG_CF(data, cf, "close"));
+  cf_he_ctx_clear(cf, data);
   cf->connected = FALSE;
   ctx->state = SCFST_INIT;
+
+  if(cf->next) {
+    cf->next->cft->close(cf->next, data);
+    Curl_conn_cf_discard_chain(&cf->next, data);
+  }
 }
 
-static void socket_cf_get_host(struct Curl_cfilter *cf,
-                               struct Curl_easy *data,
-                               const char **phost,
-                               const char **pdisplay_host,
-                               int *pport)
+static bool cf_he_data_pending(struct Curl_cfilter *cf,
+                               const struct Curl_easy *data)
 {
-  (void)data;
-  *phost = cf->conn->host.name;
-  *pdisplay_host = cf->conn->host.dispname;
-  *pport = cf->conn->port;
+  struct cf_he_ctx *ctx = cf->ctx;
+  size_t i;
+
+  if(cf->connected)
+    return cf->next->cft->has_data_pending(cf->next, data);
+
+  for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) {
+    struct eyeballer *baller = ctx->baller[i];
+    if(!baller || !baller->cf)
+      continue;
+    if(baller->cf->cft->has_data_pending(baller->cf, data))
+      return TRUE;
+  }
+  return FALSE;
 }
 
-static bool socket_cf_data_pending(struct Curl_cfilter *cf,
-                                   const struct Curl_easy *data)
+static struct curltime get_max_baller_time(struct Curl_cfilter *cf,
+                                          struct Curl_easy *data,
+                                          int query)
 {
-  int readable;
-  (void)data;
-  DEBUGASSERT(cf);
+  struct cf_he_ctx *ctx = cf->ctx;
+  struct curltime t, tmax;
+  size_t i;
 
-  readable = SOCKET_READABLE(cf->conn->sock[cf->sockindex], 0);
-  return (readable > 0 && (readable & CURL_CSELECT_IN));
+  memset(&tmax, 0, sizeof(tmax));
+  for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) {
+    struct eyeballer *baller = ctx->baller[i];
+
+    memset(&t, 0, sizeof(t));
+    if(baller && baller->cf &&
+       !baller->cf->cft->query(baller->cf, data, query, NULL, &t)) {
+      if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0)
+        tmax = t;
+    }
+  }
+  return tmax;
 }
 
-static ssize_t socket_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data,
-                              const void *buf, size_t len, CURLcode *err)
+static CURLcode cf_he_query(struct Curl_cfilter *cf,
+                            struct Curl_easy *data,
+                            int query, int *pres1, void *pres2)
 {
-  ssize_t nwritten;
-  nwritten = Curl_send_plain(data, cf->sockindex, buf, len, err);
-  return nwritten;
+  struct cf_he_ctx *ctx = cf->ctx;
+
+  if(!cf->connected) {
+    switch(query) {
+    case CF_QUERY_CONNECT_REPLY_MS: {
+      int reply_ms = -1;
+      size_t i;
+
+      for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) {
+        struct eyeballer *baller = ctx->baller[i];
+        int breply_ms;
+
+        if(baller && baller->cf &&
+           !baller->cf->cft->query(baller->cf, data, query,
+                                   &breply_ms, NULL)) {
+          if(breply_ms >= 0 && (reply_ms < 0 || breply_ms < reply_ms))
+            reply_ms = breply_ms;
+        }
+      }
+      *pres1 = reply_ms;
+      DEBUGF(LOG_CF(data, cf, "query connect reply: %dms", *pres1));
+      return CURLE_OK;
+    }
+    case CF_QUERY_TIMER_CONNECT: {
+      struct curltime *when = pres2;
+      *when = get_max_baller_time(cf, data, CF_QUERY_TIMER_CONNECT);
+      return CURLE_OK;
+    }
+    case CF_QUERY_TIMER_APPCONNECT: {
+      struct curltime *when = pres2;
+      *when = get_max_baller_time(cf, data, CF_QUERY_TIMER_APPCONNECT);
+      return CURLE_OK;
+    }
+    default:
+      break;
+    }
+  }
+
+  return cf->next?
+    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
+    CURLE_UNKNOWN_OPTION;
 }
 
-static ssize_t socket_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
-                              char *buf, size_t len, CURLcode *err)
+static void cf_he_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
-  ssize_t nread;
-  nread = Curl_recv_plain(data, cf->sockindex, buf, len, err);
-  return nread;
-}
+  struct cf_he_ctx *ctx = cf->ctx;
 
-static void socket_cf_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
-{
-  struct socket_cf_ctx *state = cf->ctx;
-
-  (void)data;
-  if(cf->connected) {
-    socket_cf_close(cf, data);
+  DEBUGF(LOG_CF(data, cf, "destroy"));
+  if(ctx) {
+    cf_he_ctx_clear(cf, data);
   }
   /* release any resources held in state */
-  Curl_safefree(state);
+  Curl_safefree(ctx);
 }
 
-static const struct Curl_cftype cft_socket = {
-  "SOCKET",
-  CF_TYPE_IP_CONNECT,
-  socket_cf_destroy,
-  socket_cf_setup,
-  socket_cf_connect,
-  socket_cf_close,
-  socket_cf_get_host,
-  socket_cf_get_select_socks,
-  socket_cf_data_pending,
-  socket_cf_send,
-  socket_cf_recv,
-  Curl_cf_def_attach_data,
-  Curl_cf_def_detach_data,
+struct Curl_cftype Curl_cft_happy_eyeballs = {
+  "HAPPY-EYEBALLS",
+  0,
+  CURL_LOG_DEFAULT,
+  cf_he_destroy,
+  cf_he_connect,
+  cf_he_close,
+  Curl_cf_def_get_host,
+  cf_he_get_select_socks,
+  cf_he_data_pending,
+  Curl_cf_def_send,
+  Curl_cf_def_recv,
+  Curl_cf_def_cntrl,
+  Curl_cf_def_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  cf_he_query,
 };
 
-CURLcode Curl_conn_socket_set(struct Curl_easy *data,
-                              struct connectdata *conn,
-                              int sockindex)
+CURLcode Curl_cf_happy_eyeballs_create(struct Curl_cfilter **pcf,
+                                       struct Curl_easy *data,
+                                       struct connectdata *conn,
+                                       cf_ip_connect_create *cf_create,
+                                       const struct Curl_dns_entry *remotehost,
+                                       int transport)
 {
+  struct cf_he_ctx *ctx = NULL;
   CURLcode result;
-  struct Curl_cfilter *cf = NULL;
-  struct socket_cf_ctx *scf_ctx = NULL;
 
-  /* Need to be first */
-  DEBUGASSERT(conn);
-  DEBUGASSERT(!conn->cfilter[sockindex]);
-  scf_ctx = calloc(sizeof(*scf_ctx), 1);
-  if(!scf_ctx) {
+  (void)data;
+  (void)conn;
+  *pcf = NULL;
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx) {
     result = CURLE_OUT_OF_MEMORY;
     goto out;
   }
-  result = Curl_cf_create(&cf, &cft_socket, scf_ctx);
-  if(result)
-    goto out;
-  Curl_conn_cf_add(data, conn, sockindex, cf);
+  ctx->transport = transport;
+  ctx->cf_create = cf_create;
+  ctx->remotehost = remotehost;
+
+  result = Curl_cf_create(pcf, &Curl_cft_happy_eyeballs, ctx);
 
 out:
   if(result) {
-    Curl_safefree(cf);
-    Curl_safefree(scf_ctx);
+    Curl_safefree(*pcf);
+    Curl_safefree(ctx);
   }
   return result;
 }
 
-static CURLcode socket_accept_cf_connect(struct Curl_cfilter *cf,
-                                         struct Curl_easy *data,
-                                         bool blocking, bool *done)
+struct transport_provider {
+  int transport;
+  cf_ip_connect_create *cf_create;
+};
+
+static
+#ifndef DEBUGBUILD
+const
+#endif
+struct transport_provider transport_providers[] = {
+  { TRNSPRT_TCP, Curl_cf_tcp_create },
+#ifdef ENABLE_QUIC
+  { TRNSPRT_QUIC, Curl_cf_quic_create },
+#endif
+  { TRNSPRT_UDP, Curl_cf_udp_create },
+  { TRNSPRT_UNIX, Curl_cf_unix_create },
+};
+
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0]))
+#endif
+
+static cf_ip_connect_create *get_cf_create(int transport)
 {
-  /* we start accepted, if we ever close, we cannot go on */
-  (void)data;
-  (void)blocking;
+  size_t i;
+  for(i = 0; i < ARRAYSIZE(transport_providers); ++i) {
+    if(transport == transport_providers[i].transport)
+      return transport_providers[i].cf_create;
+  }
+  return NULL;
+}
+
+#ifdef DEBUGBUILD
+void Curl_debug_set_transport_provider(int transport,
+                                       cf_ip_connect_create *cf_create)
+{
+  size_t i;
+  for(i = 0; i < ARRAYSIZE(transport_providers); ++i) {
+    if(transport == transport_providers[i].transport) {
+      transport_providers[i].cf_create = cf_create;
+      return;
+    }
+  }
+}
+#endif /* DEBUGBUILD */
+
+static CURLcode cf_he_insert_after(struct Curl_cfilter *cf_at,
+                                   struct Curl_easy *data,
+                                   const struct Curl_dns_entry *remotehost,
+                                   int transport)
+{
+  cf_ip_connect_create *cf_create;
+  struct Curl_cfilter *cf;
+  CURLcode result;
+
+  /* Need to be first */
+  DEBUGASSERT(cf_at);
+  cf_create = get_cf_create(transport);
+  if(!cf_create) {
+    DEBUGF(LOG_CF(data, cf_at, "unsupported transport type %d", transport));
+    return CURLE_UNSUPPORTED_PROTOCOL;
+  }
+  result = Curl_cf_happy_eyeballs_create(&cf, data, cf_at->conn,
+                                         cf_create, remotehost,
+                                         transport);
+  if(result)
+    return result;
+
+  Curl_conn_cf_insert_after(cf_at, cf);
+  return CURLE_OK;
+}
+
+typedef enum {
+  CF_SETUP_INIT,
+  CF_SETUP_CNNCT_EYEBALLS,
+  CF_SETUP_CNNCT_SOCKS,
+  CF_SETUP_CNNCT_HTTP_PROXY,
+  CF_SETUP_CNNCT_HAPROXY,
+  CF_SETUP_CNNCT_SSL,
+  CF_SETUP_DONE
+} cf_setup_state;
+
+struct cf_setup_ctx {
+  cf_setup_state state;
+  const struct Curl_dns_entry *remotehost;
+  int ssl_mode;
+  int transport;
+};
+
+static CURLcode cf_setup_connect(struct Curl_cfilter *cf,
+                                 struct Curl_easy *data,
+                                 bool blocking, bool *done)
+{
+  struct cf_setup_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OK;
+
   if(cf->connected) {
     *done = TRUE;
     return CURLE_OK;
   }
-  return CURLE_FAILED_INIT;
-}
 
-static CURLcode socket_accept_cf_setup(struct Curl_cfilter *cf,
-                                       struct Curl_easy *data,
-                                       const struct Curl_dns_entry *remotehost)
-{
-  /* we start accepted, if we ever close, we cannot go on */
-  (void)data;
-  (void)remotehost;
-  if(cf->connected) {
-    return CURLE_OK;
+  /* connect current sub-chain */
+connect_sub_chain:
+  if(cf->next && !cf->next->connected) {
+    result = Curl_conn_cf_connect(cf->next, data, blocking, done);
+    if(result || !*done)
+      return result;
   }
-  return CURLE_FAILED_INIT;
+
+  if(ctx->state < CF_SETUP_CNNCT_EYEBALLS) {
+    result = cf_he_insert_after(cf, data, ctx->remotehost, ctx->transport);
+    if(result)
+      return result;
+    ctx->state = CF_SETUP_CNNCT_EYEBALLS;
+    if(!cf->next || !cf->next->connected)
+      goto connect_sub_chain;
+  }
+
+  /* sub-chain connected, do we need to add more? */
+#ifndef CURL_DISABLE_PROXY
+  if(ctx->state < CF_SETUP_CNNCT_SOCKS && cf->conn->bits.socksproxy) {
+    result = Curl_cf_socks_proxy_insert_after(cf, data);
+    if(result)
+      return result;
+    ctx->state = CF_SETUP_CNNCT_SOCKS;
+    if(!cf->next || !cf->next->connected)
+      goto connect_sub_chain;
+  }
+
+  if(ctx->state < CF_SETUP_CNNCT_HTTP_PROXY && cf->conn->bits.httpproxy) {
+#ifdef USE_SSL
+    if(cf->conn->http_proxy.proxytype == CURLPROXY_HTTPS
+       && !Curl_conn_is_ssl(cf->conn, cf->sockindex)) {
+      result = Curl_cf_ssl_proxy_insert_after(cf, data);
+      if(result)
+        return result;
+    }
+#endif /* USE_SSL */
+
+#if !defined(CURL_DISABLE_HTTP)
+    if(cf->conn->bits.tunnel_proxy) {
+      result = Curl_cf_http_proxy_insert_after(cf, data);
+      if(result)
+        return result;
+    }
+#endif /* !CURL_DISABLE_HTTP */
+    ctx->state = CF_SETUP_CNNCT_HTTP_PROXY;
+    if(!cf->next || !cf->next->connected)
+      goto connect_sub_chain;
+  }
+#endif /* !CURL_DISABLE_PROXY */
+
+  if(ctx->state < CF_SETUP_CNNCT_HAPROXY) {
+#if !defined(CURL_DISABLE_PROXY)
+    if(data->set.haproxyprotocol) {
+      if(Curl_conn_is_ssl(cf->conn, cf->sockindex)) {
+        failf(data, "haproxy protocol not support with SSL "
+              "encryption in place (QUIC?)");
+        return CURLE_UNSUPPORTED_PROTOCOL;
+      }
+      result = Curl_cf_haproxy_insert_after(cf, data);
+      if(result)
+        return result;
+    }
+#endif /* !CURL_DISABLE_PROXY */
+    ctx->state = CF_SETUP_CNNCT_HAPROXY;
+    if(!cf->next || !cf->next->connected)
+      goto connect_sub_chain;
+  }
+
+  if(ctx->state < CF_SETUP_CNNCT_SSL) {
+#ifdef USE_SSL
+    if((ctx->ssl_mode == CURL_CF_SSL_ENABLE
+        || (ctx->ssl_mode != CURL_CF_SSL_DISABLE
+           && cf->conn->handler->flags & PROTOPT_SSL)) /* we want SSL */
+       && !Curl_conn_is_ssl(cf->conn, cf->sockindex)) { /* it is missing */
+      result = Curl_cf_ssl_insert_after(cf, data);
+      if(result)
+        return result;
+    }
+#endif /* USE_SSL */
+    ctx->state = CF_SETUP_CNNCT_SSL;
+    if(!cf->next || !cf->next->connected)
+      goto connect_sub_chain;
+  }
+
+  ctx->state = CF_SETUP_DONE;
+  cf->connected = TRUE;
+  *done = TRUE;
+  return CURLE_OK;
 }
 
-static const struct Curl_cftype cft_socket_accept = {
-  "SOCKET-ACCEPT",
-  CF_TYPE_IP_CONNECT,
-  socket_cf_destroy,
-  socket_accept_cf_setup,
-  socket_accept_cf_connect,
-  socket_cf_close,
-  socket_cf_get_host,              /* TODO: not accurate */
+static void cf_setup_close(struct Curl_cfilter *cf,
+                           struct Curl_easy *data)
+{
+  struct cf_setup_ctx *ctx = cf->ctx;
+
+  DEBUGF(LOG_CF(data, cf, "close"));
+  cf->connected = FALSE;
+  ctx->state = CF_SETUP_INIT;
+
+  if(cf->next) {
+    cf->next->cft->close(cf->next, data);
+    Curl_conn_cf_discard_chain(&cf->next, data);
+  }
+}
+
+static void cf_setup_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_setup_ctx *ctx = cf->ctx;
+
+  (void)data;
+  DEBUGF(LOG_CF(data, cf, "destroy"));
+  Curl_safefree(ctx);
+}
+
+
+struct Curl_cftype Curl_cft_setup = {
+  "SETUP",
+  0,
+  CURL_LOG_DEFAULT,
+  cf_setup_destroy,
+  cf_setup_connect,
+  cf_setup_close,
+  Curl_cf_def_get_host,
   Curl_cf_def_get_select_socks,
-  socket_cf_data_pending,
-  socket_cf_send,
-  socket_cf_recv,
-  Curl_cf_def_attach_data,
-  Curl_cf_def_detach_data,
+  Curl_cf_def_data_pending,
+  Curl_cf_def_send,
+  Curl_cf_def_recv,
+  Curl_cf_def_cntrl,
+  Curl_cf_def_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  Curl_cf_def_query,
 };
 
-CURLcode Curl_conn_socket_accepted_set(struct Curl_easy *data,
-                                       struct connectdata *conn,
-                                       int sockindex, curl_socket_t *s)
+static CURLcode cf_setup_create(struct Curl_cfilter **pcf,
+                                struct Curl_easy *data,
+                                const struct Curl_dns_entry *remotehost,
+                                int transport,
+                                int ssl_mode)
 {
-  CURLcode result;
   struct Curl_cfilter *cf = NULL;
-  struct socket_cf_ctx *scf_ctx = NULL;
+  struct cf_setup_ctx *ctx;
+  CURLcode result = CURLE_OK;
 
-  cf = conn->cfilter[sockindex];
-  if(cf && cf->cft == &cft_socket_accept) {
-    /* already an accept filter installed, just replace the socket */
-    scf_ctx = cf->ctx;
-    result = CURLE_OK;
+  (void)data;
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
   }
-  else {
-    /* replace any existing */
-    Curl_conn_cf_discard_all(data, conn, sockindex);
-    scf_ctx = calloc(sizeof(*scf_ctx), 1);
-    if(!scf_ctx) {
-      result = CURLE_OUT_OF_MEMORY;
-      goto out;
-    }
-    result = Curl_cf_create(&cf, &cft_socket_accept, scf_ctx);
-    if(result)
-      goto out;
-    Curl_conn_cf_add(data, conn, sockindex, cf);
-  }
+  ctx->state = CF_SETUP_INIT;
+  ctx->remotehost = remotehost;
+  ctx->ssl_mode = ssl_mode;
+  ctx->transport = transport;
 
-   /* close any existing socket and replace */
-  Curl_closesocket(data, conn, conn->sock[sockindex]);
-  conn->sock[sockindex] = *s;
-  conn->bits.sock_accepted = TRUE;
-  cf->connected = TRUE;
-  scf_ctx->state = SCFST_DONE;
+  result = Curl_cf_create(&cf, &Curl_cft_setup, ctx);
+  if(result)
+    goto out;
+  ctx = NULL;
 
 out:
-  if(result) {
-    Curl_safefree(cf);
-    Curl_safefree(scf_ctx);
-  }
+  *pcf = result? NULL : cf;
+  free(ctx);
   return result;
 }
+
+CURLcode Curl_cf_setup_add(struct Curl_easy *data,
+                           struct connectdata *conn,
+                           int sockindex,
+                           const struct Curl_dns_entry *remotehost,
+                           int transport,
+                           int ssl_mode)
+{
+  struct Curl_cfilter *cf;
+  CURLcode result = CURLE_OK;
+
+  DEBUGASSERT(data);
+  result = cf_setup_create(&cf, data, remotehost, transport, ssl_mode);
+  if(result)
+    goto out;
+  Curl_conn_cf_add(data, conn, sockindex, cf);
+out:
+  return result;
+}
+
+CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at,
+                                    struct Curl_easy *data,
+                                    const struct Curl_dns_entry *remotehost,
+                                    int transport,
+                                    int ssl_mode)
+{
+  struct Curl_cfilter *cf;
+  CURLcode result;
+
+  DEBUGASSERT(data);
+  result = cf_setup_create(&cf, data, remotehost, transport, ssl_mode);
+  if(result)
+    goto out;
+  Curl_conn_cf_insert_after(cf_at, cf);
+out:
+  return result;
+}
+
+CURLcode Curl_conn_setup(struct Curl_easy *data,
+                         struct connectdata *conn,
+                         int sockindex,
+                         const struct Curl_dns_entry *remotehost,
+                         int ssl_mode)
+{
+  CURLcode result = CURLE_OK;
+
+  DEBUGASSERT(data);
+  DEBUGASSERT(conn->handler);
+
+#if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER)
+  if(!conn->cfilter[sockindex] &&
+     conn->handler->protocol == CURLPROTO_HTTPS &&
+     (ssl_mode == CURL_CF_SSL_ENABLE || ssl_mode != CURL_CF_SSL_DISABLE)) {
+
+    result = Curl_cf_https_setup(data, conn, sockindex, remotehost);
+    if(result)
+      goto out;
+  }
+#endif /* !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) */
+
+  /* Still no cfilter set, apply default. */
+  if(!conn->cfilter[sockindex]) {
+    result = Curl_cf_setup_add(data, conn, sockindex, remotehost,
+                               conn->transport, ssl_mode);
+    if(result)
+      goto out;
+  }
+
+  DEBUGASSERT(conn->cfilter[sockindex]);
+out:
+  return result;
+}
+
diff --git a/Utilities/cmcurl/lib/connect.h b/Utilities/cmcurl/lib/connect.h
index 1e90a85..e4fa10c 100644
--- a/Utilities/cmcurl/lib/connect.h
+++ b/Utilities/cmcurl/lib/connect.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -29,9 +29,7 @@
 #include "sockaddr.h"
 #include "timeval.h"
 
-CURLcode Curl_connecthost(struct Curl_easy *data,
-                          struct connectdata *conn,
-                          const struct Curl_dns_entry *host);
+struct Curl_dns_entry;
 
 /* generic function that returns how much time there's left to run, according
    to the timeouts set */
@@ -53,67 +51,8 @@
 bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen,
                       char *addr, int *port);
 
-/*
- * Check if a connection seems to be alive.
- */
-bool Curl_connalive(struct Curl_easy *data, struct connectdata *conn);
-
-#ifdef USE_WINSOCK
-/* When you run a program that uses the Windows Sockets API, you may
-   experience slow performance when you copy data to a TCP server.
-
-   https://support.microsoft.com/kb/823764
-
-   Work-around: Make the Socket Send Buffer Size Larger Than the Program Send
-   Buffer Size
-
-*/
-void Curl_sndbufset(curl_socket_t sockfd);
-#else
-#define Curl_sndbufset(y) Curl_nop_stmt
-#endif
-
-void Curl_updateconninfo(struct Curl_easy *data, struct connectdata *conn,
-                         curl_socket_t sockfd);
-void Curl_conninfo_remote(struct Curl_easy *data, struct connectdata *conn,
-                          curl_socket_t sockfd);
-void Curl_conninfo_local(struct Curl_easy *data, curl_socket_t sockfd,
-                         char *local_ip, int *local_port);
 void Curl_persistconninfo(struct Curl_easy *data, struct connectdata *conn,
                           char *local_ip, int local_port);
-int Curl_closesocket(struct Curl_easy *data, struct connectdata *conn,
-                     curl_socket_t sock);
-
-/*
- * The Curl_sockaddr_ex structure is basically libcurl's external API
- * curl_sockaddr structure with enough space available to directly hold any
- * protocol-specific address structures. The variable declared here will be
- * used to pass / receive data to/from the fopensocket callback if this has
- * been set, before that, it is initialized from parameters.
- */
-struct Curl_sockaddr_ex {
-  int family;
-  int socktype;
-  int protocol;
-  unsigned int addrlen;
-  union {
-    struct sockaddr addr;
-    struct Curl_sockaddr_storage buff;
-  } _sa_ex_u;
-};
-#define sa_addr _sa_ex_u.addr
-
-/*
- * Create a socket based on info from 'conn' and 'ai'.
- *
- * Fill in 'addr' and 'sockfd' accordingly if OK is returned. If the open
- * socket callback is set, used that!
- *
- */
-CURLcode Curl_socket(struct Curl_easy *data,
-                     const struct Curl_addrinfo *ai,
-                     struct Curl_sockaddr_ex *addr,
-                     curl_socket_t *sockfd);
 
 /*
  * Curl_conncontrol() marks the end of a connection/stream. The 'closeit'
@@ -148,13 +87,71 @@
 #define connkeep(x,y) Curl_conncontrol(x, CONNCTRL_KEEP)
 #endif
 
-CURLcode Curl_conn_socket_set(struct Curl_easy *data,
-                              struct connectdata *conn,
-                              int sockindex);
+/**
+ * Create a cfilter for making an "ip" connection to the
+ * given address, using parameters from `conn`. The "ip" connection
+ * can be a TCP socket, a UDP socket or even a QUIC connection.
+ *
+ * It MUST use only the supplied `ai` for its connection attempt.
+ *
+ * Such a filter may be used in "happy eyeball" scenarios, and its
+ * `connect` implementation needs to support non-blocking. Once connected,
+ * it MAY be installed in the connection filter chain to serve transfers.
+ */
+typedef CURLcode cf_ip_connect_create(struct Curl_cfilter **pcf,
+                                      struct Curl_easy *data,
+                                      struct connectdata *conn,
+                                      const struct Curl_addrinfo *ai,
+                                      int transport);
 
-CURLcode Curl_conn_socket_accepted_set(struct Curl_easy *data,
-                                       struct connectdata *conn,
-                                       int sockindex,
-                                       curl_socket_t *s);
+/**
+ * Create a happy eyeball connection filter that uses the, once resolved,
+ * address information to connect on ip families based on connection
+ * configuration.
+ * @param pcf        output, the created cfilter
+ * @param data       easy handle used in creation
+ * @param conn       connection the filter is created for
+ * @param cf_create  method to create the sub-filters performing the
+ *                   actual connects.
+ */
+CURLcode
+Curl_cf_happy_eyeballs_create(struct Curl_cfilter **pcf,
+                              struct Curl_easy *data,
+                              struct connectdata *conn,
+                              cf_ip_connect_create *cf_create,
+                              const struct Curl_dns_entry *remotehost,
+                              int transport);
+
+CURLcode Curl_cf_setup_add(struct Curl_easy *data,
+                           struct connectdata *conn,
+                           int sockindex,
+                           const struct Curl_dns_entry *remotehost,
+                           int transport,
+                           int ssl_mode);
+
+CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at,
+                                    struct Curl_easy *data,
+                                    const struct Curl_dns_entry *remotehost,
+                                    int transport,
+                                    int ssl_mode);
+
+/**
+ * Setup the cfilters at `sockindex` in connection `conn`.
+ * If no filter chain is installed yet, inspects the configuration
+ * in `data` and `conn? to install a suitable filter chain.
+ */
+CURLcode Curl_conn_setup(struct Curl_easy *data,
+                         struct connectdata *conn,
+                         int sockindex,
+                         const struct Curl_dns_entry *remotehost,
+                         int ssl_mode);
+
+extern struct Curl_cftype Curl_cft_happy_eyeballs;
+extern struct Curl_cftype Curl_cft_setup;
+
+#ifdef DEBUGBUILD
+void Curl_debug_set_transport_provider(int transport,
+                                       cf_ip_connect_create *cf_create);
+#endif
 
 #endif /* HEADER_CURL_CONNECT_H */
diff --git a/Utilities/cmcurl/lib/content_encoding.c b/Utilities/cmcurl/lib/content_encoding.c
index 9e345b1..aa1e0cb 100644
--- a/Utilities/cmcurl/lib/content_encoding.c
+++ b/Utilities/cmcurl/lib/content_encoding.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -33,7 +33,15 @@
 #endif
 
 #ifdef HAVE_BROTLI
+#if defined(__GNUC__)
+/* Ignore -Wvla warnings in brotli headers */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wvla"
+#endif
 #include <brotli/decode.h>
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
 #endif
 
 #ifdef HAVE_ZSTD
@@ -977,7 +985,8 @@
 static struct contenc_writer *
 new_unencoding_writer(struct Curl_easy *data,
                       const struct content_encoding *handler,
-                      struct contenc_writer *downstream)
+                      struct contenc_writer *downstream,
+                      int order)
 {
   struct contenc_writer *writer;
 
@@ -987,6 +996,7 @@
   if(writer) {
     writer->handler = handler;
     writer->downstream = downstream;
+    writer->order = order;
     if(handler->init_writer(data, writer)) {
       free(writer);
       writer = NULL;
@@ -1042,10 +1052,10 @@
 /* Set-up the unencoding stack from the Content-Encoding header value.
  * See RFC 7231 section 3.1.2.2. */
 CURLcode Curl_build_unencoding_stack(struct Curl_easy *data,
-                                     const char *enclist, int maybechunked)
+                                     const char *enclist, int is_transfer)
 {
   struct SingleRequest *k = &data->req;
-  int counter = 0;
+  unsigned int order = is_transfer? 2: 1;
 
   do {
     const char *name;
@@ -1062,7 +1072,7 @@
         namelen = enclist - name + 1;
 
     /* Special case: chunked encoding is handled at the reader level. */
-    if(maybechunked && namelen == 7 && strncasecompare(name, "chunked", 7)) {
+    if(is_transfer && namelen == 7 && strncasecompare(name, "chunked", 7)) {
       k->chunk = TRUE;             /* chunks coming our way. */
       Curl_httpchunk_init(data);   /* init our chunky engine. */
     }
@@ -1071,7 +1081,8 @@
       struct contenc_writer *writer;
 
       if(!k->writer_stack) {
-        k->writer_stack = new_unencoding_writer(data, &client_encoding, NULL);
+        k->writer_stack = new_unencoding_writer(data, &client_encoding,
+                                                NULL, 0);
 
         if(!k->writer_stack)
           return CURLE_OUT_OF_MEMORY;
@@ -1080,16 +1091,29 @@
       if(!encoding)
         encoding = &error_encoding;  /* Defer error at stack use. */
 
-      if(++counter >= MAX_ENCODE_STACK) {
-        failf(data, "Reject response due to %u content encodings",
-              counter);
+      if(k->writer_stack_depth++ >= MAX_ENCODE_STACK) {
+        failf(data, "Reject response due to more than %u content encodings",
+              MAX_ENCODE_STACK);
         return CURLE_BAD_CONTENT_ENCODING;
       }
       /* Stack the unencoding stage. */
-      writer = new_unencoding_writer(data, encoding, k->writer_stack);
-      if(!writer)
-        return CURLE_OUT_OF_MEMORY;
-      k->writer_stack = writer;
+      if(order >= k->writer_stack->order) {
+        writer = new_unencoding_writer(data, encoding,
+                                       k->writer_stack, order);
+        if(!writer)
+          return CURLE_OUT_OF_MEMORY;
+        k->writer_stack = writer;
+      }
+      else {
+        struct contenc_writer *w = k->writer_stack;
+        while(w->downstream && order < w->downstream->order)
+          w = w->downstream;
+        writer = new_unencoding_writer(data, encoding,
+                                       w->downstream, order);
+        if(!writer)
+          return CURLE_OUT_OF_MEMORY;
+        w->downstream = writer;
+      }
     }
   } while(*enclist);
 
@@ -1099,11 +1123,11 @@
 #else
 /* Stubs for builds without HTTP. */
 CURLcode Curl_build_unencoding_stack(struct Curl_easy *data,
-                                     const char *enclist, int maybechunked)
+                                     const char *enclist, int is_transfer)
 {
   (void) data;
   (void) enclist;
-  (void) maybechunked;
+  (void) is_transfer;
   return CURLE_NOT_BUILT_IN;
 }
 
diff --git a/Utilities/cmcurl/lib/content_encoding.h b/Utilities/cmcurl/lib/content_encoding.h
index 3c278cf..56e7f97 100644
--- a/Utilities/cmcurl/lib/content_encoding.h
+++ b/Utilities/cmcurl/lib/content_encoding.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -28,6 +28,7 @@
 struct contenc_writer {
   const struct content_encoding *handler;  /* Encoding handler. */
   struct contenc_writer *downstream;  /* Downstream writer. */
+  unsigned int order; /* Ordering within writer stack. */
 };
 
 /* Content encoding writer. */
@@ -46,7 +47,7 @@
 
 
 CURLcode Curl_build_unencoding_stack(struct Curl_easy *data,
-                                     const char *enclist, int maybechunked);
+                                     const char *enclist, int is_transfer);
 CURLcode Curl_unencode_write(struct Curl_easy *data,
                              struct contenc_writer *writer,
                              const char *buf, size_t nbytes);
diff --git a/Utilities/cmcurl/lib/cookie.c b/Utilities/cmcurl/lib/cookie.c
index bccf2e8..0c6e0f7 100644
--- a/Utilities/cmcurl/lib/cookie.c
+++ b/Utilities/cmcurl/lib/cookie.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -101,13 +101,14 @@
 #include "parsedate.h"
 #include "rename.h"
 #include "fopen.h"
+#include "strdup.h"
 
 /* The last 3 #include files should be in this order */
 #include "curl_printf.h"
 #include "curl_memory.h"
 #include "memdebug.h"
 
-static void strstore(char **str, const char *newstr);
+static void strstore(char **str, const char *newstr, size_t len);
 
 static void freecookie(struct Cookie *co)
 {
@@ -122,15 +123,17 @@
   free(co);
 }
 
-static bool tailmatch(const char *cooke_domain, const char *hostname)
+static bool tailmatch(const char *cookie_domain, size_t cookie_domain_len,
+                      const char *hostname)
 {
-  size_t cookie_domain_len = strlen(cooke_domain);
   size_t hostname_len = strlen(hostname);
 
   if(hostname_len < cookie_domain_len)
     return FALSE;
 
-  if(!strcasecompare(cooke_domain, hostname + hostname_len-cookie_domain_len))
+  if(!strncasecompare(cookie_domain,
+                      hostname + hostname_len-cookie_domain_len,
+                      cookie_domain_len))
     return FALSE;
 
   /*
@@ -176,7 +179,7 @@
 
   /* #-fragments are already cut off! */
   if(0 == strlen(uri_path) || uri_path[0] != '/') {
-    strstore(&uri_path, "/");
+    strstore(&uri_path, "/", 1);
     if(!uri_path)
       return FALSE;
   }
@@ -310,7 +313,7 @@
   /* RFC6265 5.2.4 The Path Attribute */
   if(new_path[0] != '/') {
     /* Let cookie-path be the default-path. */
-    strstore(&new_path, "/");
+    strstore(&new_path, "/", 1);
     return new_path;
   }
 
@@ -329,14 +332,13 @@
  */
 void Curl_cookie_loadfiles(struct Curl_easy *data)
 {
-  struct curl_slist *list = data->state.cookielist;
+  struct curl_slist *list = data->set.cookielist;
   if(list) {
     Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
     while(list) {
-      struct CookieInfo *newcookies = Curl_cookie_init(data,
-                                        list->data,
-                                        data->cookies,
-                                        data->set.cookiesession);
+      struct CookieInfo *newcookies =
+        Curl_cookie_init(data, list->data, data->cookies,
+                         data->set.cookiesession);
       if(!newcookies)
         /*
          * Failure may be due to OOM or a bad cookie; both are ignored
@@ -347,8 +349,6 @@
         data->cookies = newcookies;
       list = list->next;
     }
-    curl_slist_free_all(data->state.cookielist); /* clean up list */
-    data->state.cookielist = NULL; /* don't do this again! */
     Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
   }
 }
@@ -362,10 +362,14 @@
  * parsing in a last-wins scenario. The caller is responsible for checking
  * for OOM errors.
  */
-static void strstore(char **str, const char *newstr)
+static void strstore(char **str, const char *newstr, size_t len)
 {
+  DEBUGASSERT(newstr);
+  DEBUGASSERT(str);
   free(*str);
-  *str = strdup(newstr);
+  *str = Curl_memdup(newstr, len + 1);
+  if(*str)
+    (*str)[len] = 0;
 }
 
 /*
@@ -427,15 +431,19 @@
 }
 
 /* Make sure domain contains a dot or is localhost. */
-static bool bad_domain(const char *domain)
+static bool bad_domain(const char *domain, size_t len)
 {
-  if(strcasecompare(domain, "localhost"))
+  if((len == 9) && strncasecompare(domain, "localhost", 9))
     return FALSE;
   else {
     /* there must be a dot present, but that dot must not be a trailing dot */
-    char *dot = strchr(domain, '.');
-    if(dot)
-      return dot[1] ? FALSE : TRUE;
+    char *dot = memchr(domain, '.', len);
+    if(dot) {
+      size_t i = dot - domain;
+      if((len - i) > 1)
+        /* the dot is not the last byte */
+        return FALSE;
+    }
   }
   return TRUE;
 }
@@ -515,10 +523,9 @@
 
   if(httpheader) {
     /* This line was read off an HTTP-header */
-    char name[MAX_NAME];
-    char what[MAX_NAME];
+    const char *namep;
+    const char *valuep;
     const char *ptr;
-    const char *semiptr;
 
     size_t linelength = strlen(lineptr);
     if(linelength > MAX_COOKIE_LINE) {
@@ -527,73 +534,65 @@
       return NULL;
     }
 
-    semiptr = strchr(lineptr, ';'); /* first, find a semicolon */
-
-    while(*lineptr && ISBLANK(*lineptr))
-      lineptr++;
-
     ptr = lineptr;
     do {
-      /* we have a <what>=<this> pair or a stand-alone word here */
-      name[0] = what[0] = 0; /* init the buffers */
-      if(1 <= sscanf(ptr, "%" MAX_NAME_TXT "[^;\t\r\n=] =%"
-                     MAX_NAME_TXT "[^;\r\n]",
-                     name, what)) {
-        /*
-         * Use strstore() below to properly deal with received cookie
-         * headers that have the same string property set more than once,
-         * and then we use the last one.
-         */
-        const char *whatptr;
+      size_t vlen;
+      size_t nlen;
+
+      while(*ptr && ISBLANK(*ptr))
+        ptr++;
+
+      /* we have a <name>=<value> pair or a stand-alone word here */
+      nlen = strcspn(ptr, ";\t\r\n=");
+      if(nlen) {
         bool done = FALSE;
-        bool sep;
-        size_t len = strlen(what);
-        size_t nlen = strlen(name);
-        const char *endofn = &ptr[ nlen ];
+        bool sep = FALSE;
+
+        namep = ptr;
+        ptr += nlen;
+
+        /* trim trailing spaces and tabs after name */
+        while(nlen && ISBLANK(namep[nlen - 1]))
+          nlen--;
+
+        if(*ptr == '=') {
+          vlen = strcspn(++ptr, ";\r\n");
+          valuep = ptr;
+          sep = TRUE;
+          ptr = &valuep[vlen];
+
+          /* Strip off trailing whitespace from the value */
+          while(vlen && ISBLANK(valuep[vlen-1]))
+            vlen--;
+
+          /* Skip leading whitespace from the value */
+          while(vlen && ISBLANK(*valuep)) {
+            valuep++;
+            vlen--;
+          }
+
+          /* Reject cookies with a TAB inside the value */
+          if(memchr(valuep, '\t', vlen)) {
+            freecookie(co);
+            infof(data, "cookie contains TAB, dropping");
+            return NULL;
+          }
+        }
+        else {
+          valuep = NULL;
+          vlen = 0;
+        }
 
         /*
          * Check for too long individual name or contents, or too long
          * combination of name + contents. Chrome and Firefox support 4095 or
          * 4096 bytes combo
          */
-        if(nlen >= (MAX_NAME-1) || len >= (MAX_NAME-1) ||
-           ((nlen + len) > MAX_NAME)) {
+        if(nlen >= (MAX_NAME-1) || vlen >= (MAX_NAME-1) ||
+           ((nlen + vlen) > MAX_NAME)) {
           freecookie(co);
           infof(data, "oversized cookie dropped, name/val %zu + %zu bytes",
-                nlen, len);
-          return NULL;
-        }
-
-        /* name ends with a '=' ? */
-        sep = (*endofn == '=')?TRUE:FALSE;
-
-        if(nlen) {
-          endofn--; /* move to the last character */
-          if(ISBLANK(*endofn)) {
-            /* skip trailing spaces in name */
-            while(*endofn && ISBLANK(*endofn) && nlen) {
-              endofn--;
-              nlen--;
-            }
-            name[nlen] = 0; /* new end of name */
-          }
-        }
-
-        /* Strip off trailing whitespace from the 'what' */
-        while(len && ISBLANK(what[len-1])) {
-          what[len-1] = 0;
-          len--;
-        }
-
-        /* Skip leading whitespace from the 'what' */
-        whatptr = what;
-        while(*whatptr && ISBLANK(*whatptr))
-          whatptr++;
-
-        /* Reject cookies with a TAB inside the content */
-        if(strchr(whatptr, '\t')) {
-          freecookie(co);
-          infof(data, "cookie contains TAB, dropping");
+                nlen, vlen);
           return NULL;
         }
 
@@ -603,13 +602,19 @@
          * "the rest". Prefixes must start with '__' and end with a '-', so
          * only test for names where that can possibly be true.
          */
-        if(nlen > 3 && name[0] == '_' && name[1] == '_') {
-          if(strncasecompare("__Secure-", name, 9))
+        if(nlen >= 7 && namep[0] == '_' && namep[1] == '_') {
+          if(strncasecompare("__Secure-", namep, 9))
             co->prefix |= COOKIE_PREFIX__SECURE;
-          else if(strncasecompare("__Host-", name, 7))
+          else if(strncasecompare("__Host-", namep, 7))
             co->prefix |= COOKIE_PREFIX__HOST;
         }
 
+        /*
+         * Use strstore() below to properly deal with received cookie
+         * headers that have the same string property set more than once,
+         * and then we use the last one.
+         */
+
         if(!co->name) {
           /* The very first name/value pair is the actual cookie name */
           if(!sep) {
@@ -617,20 +622,20 @@
             badcookie = TRUE;
             break;
           }
-          co->name = strdup(name);
-          co->value = strdup(whatptr);
+          strstore(&co->name, namep, nlen);
+          strstore(&co->value, valuep, vlen);
           done = TRUE;
           if(!co->name || !co->value) {
             badcookie = TRUE;
             break;
           }
-          if(invalid_octets(whatptr) || invalid_octets(name)) {
+          if(invalid_octets(co->value) || invalid_octets(co->name)) {
             infof(data, "invalid octets in name/value, cookie dropped");
             badcookie = TRUE;
             break;
           }
         }
-        else if(!len) {
+        else if(!vlen) {
           /*
            * this was a "<name>=" with no content, and we must allow
            * 'secure' and 'httponly' specified this weirdly
@@ -641,7 +646,7 @@
            * using a secure protocol, or when the cookie is being set by
            * reading from file
            */
-          if(strcasecompare("secure", name)) {
+          if((nlen == 6) && strncasecompare("secure", namep, 6)) {
             if(secure || !c->running) {
               co->secure = TRUE;
             }
@@ -650,7 +655,7 @@
               break;
             }
           }
-          else if(strcasecompare("httponly", name))
+          else if((nlen == 8) && strncasecompare("httponly", namep, 8))
             co->httponly = TRUE;
           else if(sep)
             /* there was a '=' so we're not done parsing this field */
@@ -658,8 +663,8 @@
         }
         if(done)
           ;
-        else if(strcasecompare("path", name)) {
-          strstore(&co->path, whatptr);
+        else if((nlen == 4) && strncasecompare("path", namep, 4)) {
+          strstore(&co->path, valuep, vlen);
           if(!co->path) {
             badcookie = TRUE; /* out of memory bad */
             break;
@@ -671,7 +676,8 @@
             break;
           }
         }
-        else if(strcasecompare("domain", name) && whatptr[0]) {
+        else if((nlen == 6) &&
+                strncasecompare("domain", namep, 6) && vlen) {
           bool is_ip;
 
           /*
@@ -679,8 +685,10 @@
            * the given domain is not valid and thus cannot be set.
            */
 
-          if('.' == whatptr[0])
-            whatptr++; /* ignore preceding dot */
+          if('.' == valuep[0]) {
+            valuep++; /* ignore preceding dot */
+            vlen--;
+          }
 
 #ifndef USE_LIBPSL
           /*
@@ -688,16 +696,17 @@
            * TLD or otherwise "protected" suffix. To reduce risk, we require a
            * dot OR the exact host name being "localhost".
            */
-          if(bad_domain(whatptr))
+          if(bad_domain(valuep, vlen))
             domain = ":";
 #endif
 
-          is_ip = Curl_host_is_ipnum(domain ? domain : whatptr);
+          is_ip = Curl_host_is_ipnum(domain ? domain : valuep);
 
           if(!domain
-             || (is_ip && !strcmp(whatptr, domain))
-             || (!is_ip && tailmatch(whatptr, domain))) {
-            strstore(&co->domain, whatptr);
+             || (is_ip && !strncmp(valuep, domain, vlen) &&
+                 (vlen == strlen(domain)))
+             || (!is_ip && tailmatch(valuep, vlen, domain))) {
+            strstore(&co->domain, valuep, vlen);
             if(!co->domain) {
               badcookie = TRUE;
               break;
@@ -713,17 +722,17 @@
              */
             badcookie = TRUE;
             infof(data, "skipped cookie with bad tailmatch domain: %s",
-                  whatptr);
+                  valuep);
           }
         }
-        else if(strcasecompare("version", name)) {
-          strstore(&co->version, whatptr);
+        else if((nlen == 7) && strncasecompare("version", namep, 7)) {
+          strstore(&co->version, valuep, vlen);
           if(!co->version) {
             badcookie = TRUE;
             break;
           }
         }
-        else if(strcasecompare("max-age", name)) {
+        else if((nlen == 7) && strncasecompare("max-age", namep, 7)) {
           /*
            * Defined in RFC2109:
            *
@@ -733,14 +742,14 @@
            * client should discard the cookie.  A value of zero means the
            * cookie should be discarded immediately.
            */
-          strstore(&co->maxage, whatptr);
+          strstore(&co->maxage, valuep, vlen);
           if(!co->maxage) {
             badcookie = TRUE;
             break;
           }
         }
-        else if(strcasecompare("expires", name)) {
-          strstore(&co->expirestr, whatptr);
+        else if((nlen == 7) && strncasecompare("expires", namep, 7)) {
+          strstore(&co->expirestr, valuep, vlen);
           if(!co->expirestr) {
             badcookie = TRUE;
             break;
@@ -755,24 +764,13 @@
         /* this is an "illegal" <what>=<this> pair */
       }
 
-      if(!semiptr || !*semiptr) {
-        /* we already know there are no more cookies */
-        semiptr = NULL;
-        continue;
-      }
-
-      ptr = semiptr + 1;
       while(*ptr && ISBLANK(*ptr))
         ptr++;
-      semiptr = strchr(ptr, ';'); /* now, find the next semicolon */
-
-      if(!semiptr && *ptr)
-        /*
-         * There are no more semicolons, but there's a final name=value pair
-         * coming up
-         */
-        semiptr = strchr(ptr, '\0');
-    } while(semiptr);
+      if(*ptr == ';')
+        ptr++;
+      else
+        break;
+    } while(1);
 
     if(co->maxage) {
       CURLofft offt;
@@ -1059,7 +1057,7 @@
       Curl_psl_release(data);
     }
     else
-      acceptable = !bad_domain(domain);
+      acceptable = !bad_domain(domain, strlen(domain));
 
     if(!acceptable) {
       infof(data, "cookie '%s' dropped, domain '%s' must not "
@@ -1301,7 +1299,7 @@
      */
     remove_expired(c);
 
-    if(fromfile && fp)
+    if(fromfile)
       fclose(fp);
   }
 
@@ -1449,7 +1447,8 @@
 
       /* now check if the domain is correct */
       if(!co->domain ||
-         (co->tailmatch && !is_ip && tailmatch(co->domain, host)) ||
+         (co->tailmatch && !is_ip &&
+          tailmatch(co->domain, co->domain? strlen(co->domain):0, host)) ||
          ((!co->tailmatch || is_ip) && strcasecompare(host, co->domain)) ) {
         /*
          * the right part of the host matches the domain stuff in the
@@ -1800,13 +1799,6 @@
   CURLcode res;
 
   if(data->set.str[STRING_COOKIEJAR]) {
-    if(data->state.cookielist) {
-      /* If there is a list of cookie files to read, do it first so that
-         we have all the told files read before we write the new jar.
-         Curl_cookie_loadfiles() LOCKS and UNLOCKS the share itself! */
-      Curl_cookie_loadfiles(data);
-    }
-
     Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
 
     /* if we have a destination file for all the cookies to get dumped to */
@@ -1816,12 +1808,6 @@
             data->set.str[STRING_COOKIEJAR], curl_easy_strerror(res));
   }
   else {
-    if(cleanup && data->state.cookielist) {
-      /* since nothing is written, we can just free the list of cookie file
-         names */
-      curl_slist_free_all(data->state.cookielist); /* clean up list */
-      data->state.cookielist = NULL;
-    }
     Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
   }
 
diff --git a/Utilities/cmcurl/lib/cookie.h b/Utilities/cmcurl/lib/cookie.h
index abc0a2e..39bb08b 100644
--- a/Utilities/cmcurl/lib/cookie.h
+++ b/Utilities/cmcurl/lib/cookie.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_addrinfo.c b/Utilities/cmcurl/lib/curl_addrinfo.c
index bcea883..35a0635 100644
--- a/Utilities/cmcurl/lib/curl_addrinfo.c
+++ b/Utilities/cmcurl/lib/curl_addrinfo.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_addrinfo.h b/Utilities/cmcurl/lib/curl_addrinfo.h
index b778121..c757c49 100644
--- a/Utilities/cmcurl/lib/curl_addrinfo.h
+++ b/Utilities/cmcurl/lib/curl_addrinfo.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_base64.h b/Utilities/cmcurl/lib/curl_base64.h
index 85368a1..806d443 100644
--- a/Utilities/cmcurl/lib/curl_base64.h
+++ b/Utilities/cmcurl/lib/curl_base64.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_config.h.cmake b/Utilities/cmcurl/lib/curl_config.h.cmake
index 9cde948..6e2458d 100644
--- a/Utilities/cmcurl/lib/curl_config.h.cmake
+++ b/Utilities/cmcurl/lib/curl_config.h.cmake
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_ctype.h b/Utilities/cmcurl/lib/curl_ctype.h
index dc6b8ca..1d1d60c 100644
--- a/Utilities/cmcurl/lib/curl_ctype.h
+++ b/Utilities/cmcurl/lib/curl_ctype.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -27,7 +27,7 @@
 #define ISLOWHEXALHA(x) (((x) >= 'a') && ((x) <= 'f'))
 #define ISUPHEXALHA(x) (((x) >= 'A') && ((x) <= 'F'))
 
-#define ISLOWCNTRL(x) ((x) >= 0 && ((x) <= 0x1f))
+#define ISLOWCNTRL(x) ((unsigned char)(x) <= 0x1f)
 #define IS7F(x) ((x) == 0x7f)
 
 #define ISLOWPRINT(x) (((x) >= 9) && ((x) <= 0x0d))
diff --git a/Utilities/cmcurl/lib/curl_des.c b/Utilities/cmcurl/lib/curl_des.c
index a2bf648..5c623b3 100644
--- a/Utilities/cmcurl/lib/curl_des.c
+++ b/Utilities/cmcurl/lib/curl_des.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2015 - 2022, Steve Holme, <steve_holme@hotmail.com>.
+ * Copyright (C) Steve Holme, <steve_holme@hotmail.com>.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_des.h b/Utilities/cmcurl/lib/curl_des.h
index c1c1674..6ec450a 100644
--- a/Utilities/cmcurl/lib/curl_des.h
+++ b/Utilities/cmcurl/lib/curl_des.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2015 - 2022, Steve Holme, <steve_holme@hotmail.com>.
+ * Copyright (C) Steve Holme, <steve_holme@hotmail.com>.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_endian.c b/Utilities/cmcurl/lib/curl_endian.c
index 3cc7734..11c662a 100644
--- a/Utilities/cmcurl/lib/curl_endian.c
+++ b/Utilities/cmcurl/lib/curl_endian.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_endian.h b/Utilities/cmcurl/lib/curl_endian.h
index 08d52e7..fa28321 100644
--- a/Utilities/cmcurl/lib/curl_endian.h
+++ b/Utilities/cmcurl/lib/curl_endian.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_fnmatch.c b/Utilities/cmcurl/lib/curl_fnmatch.c
index b8a85a9..5f9ca4f 100644
--- a/Utilities/cmcurl/lib/curl_fnmatch.c
+++ b/Utilities/cmcurl/lib/curl_fnmatch.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_fnmatch.h b/Utilities/cmcurl/lib/curl_fnmatch.h
index 8324be5..595646f 100644
--- a/Utilities/cmcurl/lib/curl_fnmatch.h
+++ b/Utilities/cmcurl/lib/curl_fnmatch.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_get_line.c b/Utilities/cmcurl/lib/curl_get_line.c
index 0d8c285..686abe7 100644
--- a/Utilities/cmcurl/lib/curl_get_line.c
+++ b/Utilities/cmcurl/lib/curl_get_line.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_get_line.h b/Utilities/cmcurl/lib/curl_get_line.h
index b2a534d..0ff32c5 100644
--- a/Utilities/cmcurl/lib/curl_get_line.h
+++ b/Utilities/cmcurl/lib/curl_get_line.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_gethostname.c b/Utilities/cmcurl/lib/curl_gethostname.c
index 4747e93..706b2e6 100644
--- a/Utilities/cmcurl/lib/curl_gethostname.c
+++ b/Utilities/cmcurl/lib/curl_gethostname.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_gethostname.h b/Utilities/cmcurl/lib/curl_gethostname.h
index b736096..9281d9c 100644
--- a/Utilities/cmcurl/lib/curl_gethostname.h
+++ b/Utilities/cmcurl/lib/curl_gethostname.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_gssapi.c b/Utilities/cmcurl/lib/curl_gssapi.c
index 01ab48e..c6fe125 100644
--- a/Utilities/cmcurl/lib/curl_gssapi.c
+++ b/Utilities/cmcurl/lib/curl_gssapi.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2011 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -34,10 +34,16 @@
 #include "curl_memory.h"
 #include "memdebug.h"
 
-gss_OID_desc Curl_spnego_mech_oid = {
+#if defined(__GNUC__)
+#define CURL_ALIGN8   __attribute__ ((aligned(8)))
+#else
+#define CURL_ALIGN8
+#endif
+
+gss_OID_desc Curl_spnego_mech_oid CURL_ALIGN8 = {
   6, (char *)"\x2b\x06\x01\x05\x05\x02"
 };
-gss_OID_desc Curl_krb5_mech_oid = {
+gss_OID_desc Curl_krb5_mech_oid CURL_ALIGN8 = {
   9, (char *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"
 };
 
diff --git a/Utilities/cmcurl/lib/curl_gssapi.h b/Utilities/cmcurl/lib/curl_gssapi.h
index b4ed482..7b9a534 100644
--- a/Utilities/cmcurl/lib/curl_gssapi.h
+++ b/Utilities/cmcurl/lib/curl_gssapi.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2011 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_hmac.h b/Utilities/cmcurl/lib/curl_hmac.h
index 36c0bd6..11625c0 100644
--- a/Utilities/cmcurl/lib/curl_hmac.h
+++ b/Utilities/cmcurl/lib/curl_hmac.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_krb5.h b/Utilities/cmcurl/lib/curl_krb5.h
index ccd6f10..ccf6b96 100644
--- a/Utilities/cmcurl/lib/curl_krb5.h
+++ b/Utilities/cmcurl/lib/curl_krb5.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_ldap.h b/Utilities/cmcurl/lib/curl_ldap.h
index ba3ede4..8a1d807 100644
--- a/Utilities/cmcurl/lib/curl_ldap.h
+++ b/Utilities/cmcurl/lib/curl_ldap.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_log.c b/Utilities/cmcurl/lib/curl_log.c
new file mode 100644
index 0000000..2301cff
--- /dev/null
+++ b/Utilities/cmcurl/lib/curl_log.c
@@ -0,0 +1,223 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#include <curl/curl.h>
+
+#include "curl_log.h"
+#include "urldata.h"
+#include "easyif.h"
+#include "cfilters.h"
+#include "timeval.h"
+#include "multiif.h"
+#include "strcase.h"
+
+#include "cf-socket.h"
+#include "connect.h"
+#include "http2.h"
+#include "http_proxy.h"
+#include "cf-https-connect.h"
+#include "socks.h"
+#include "strtok.h"
+#include "vtls/vtls.h"
+#include "vquic/vquic.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+
+void Curl_debug(struct Curl_easy *data, curl_infotype type,
+                char *ptr, size_t size)
+{
+  if(data->set.verbose) {
+    static const char s_infotype[CURLINFO_END][3] = {
+      "* ", "< ", "> ", "{ ", "} ", "{ ", "} " };
+    if(data->set.fdebug) {
+      bool inCallback = Curl_is_in_callback(data);
+      Curl_set_in_callback(data, true);
+      (void)(*data->set.fdebug)(data, type, ptr, size, data->set.debugdata);
+      Curl_set_in_callback(data, inCallback);
+    }
+    else {
+      switch(type) {
+      case CURLINFO_TEXT:
+      case CURLINFO_HEADER_OUT:
+      case CURLINFO_HEADER_IN:
+        fwrite(s_infotype[type], 2, 1, data->set.err);
+        fwrite(ptr, size, 1, data->set.err);
+        break;
+      default: /* nada */
+        break;
+      }
+    }
+  }
+}
+
+
+/* Curl_failf() is for messages stating why we failed.
+ * The message SHALL NOT include any LF or CR.
+ */
+void Curl_failf(struct Curl_easy *data, const char *fmt, ...)
+{
+  DEBUGASSERT(!strchr(fmt, '\n'));
+  if(data->set.verbose || data->set.errorbuffer) {
+    va_list ap;
+    int len;
+    char error[CURL_ERROR_SIZE + 2];
+    va_start(ap, fmt);
+    len = mvsnprintf(error, CURL_ERROR_SIZE, fmt, ap);
+
+    if(data->set.errorbuffer && !data->state.errorbuf) {
+      strcpy(data->set.errorbuffer, error);
+      data->state.errorbuf = TRUE; /* wrote error string */
+    }
+    error[len++] = '\n';
+    error[len] = '\0';
+    Curl_debug(data, CURLINFO_TEXT, error, len);
+    va_end(ap);
+  }
+}
+
+/* Curl_infof() is for info message along the way */
+#define MAXINFO 2048
+
+void Curl_infof(struct Curl_easy *data, const char *fmt, ...)
+{
+  DEBUGASSERT(!strchr(fmt, '\n'));
+  if(data && data->set.verbose) {
+    va_list ap;
+    int len;
+    char buffer[MAXINFO + 2];
+    va_start(ap, fmt);
+    len = mvsnprintf(buffer, MAXINFO, fmt, ap);
+    va_end(ap);
+    buffer[len++] = '\n';
+    buffer[len] = '\0';
+    Curl_debug(data, CURLINFO_TEXT, buffer, len);
+  }
+}
+
+#ifdef DEBUGBUILD
+
+void Curl_log_cf_debug(struct Curl_easy *data, struct Curl_cfilter *cf,
+                       const char *fmt, ...)
+{
+  DEBUGASSERT(cf);
+  if(data && Curl_log_cf_is_debug(cf)) {
+    va_list ap;
+    int len;
+    char buffer[MAXINFO + 2];
+    len = msnprintf(buffer, MAXINFO, "[CONN-%ld%s-%s] ",
+                    cf->conn->connection_id, cf->sockindex? "/2" : "",
+                    cf->cft->name);
+    va_start(ap, fmt);
+    len += mvsnprintf(buffer + len, MAXINFO - len, fmt, ap);
+    va_end(ap);
+    buffer[len++] = '\n';
+    buffer[len] = '\0';
+    Curl_debug(data, CURLINFO_TEXT, buffer, len);
+  }
+}
+
+
+static struct Curl_cftype *cf_types[] = {
+  &Curl_cft_tcp,
+  &Curl_cft_udp,
+  &Curl_cft_unix,
+  &Curl_cft_tcp_accept,
+  &Curl_cft_happy_eyeballs,
+  &Curl_cft_setup,
+#ifdef USE_NGHTTP2
+  &Curl_cft_nghttp2,
+#endif
+#ifdef USE_SSL
+  &Curl_cft_ssl,
+  &Curl_cft_ssl_proxy,
+#endif
+#if !defined(CURL_DISABLE_PROXY)
+#if !defined(CURL_DISABLE_HTTP)
+  &Curl_cft_http_proxy,
+#endif /* !CURL_DISABLE_HTTP */
+  &Curl_cft_haproxy,
+  &Curl_cft_socks_proxy,
+#endif /* !CURL_DISABLE_PROXY */
+#ifdef ENABLE_QUIC
+  &Curl_cft_http3,
+#endif
+#if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER)
+  &Curl_cft_http_connect,
+#endif
+  NULL,
+};
+
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0]))
+#endif
+
+CURLcode Curl_log_init(void)
+{
+  const char *setting = getenv("CURL_DEBUG");
+  if(setting) {
+    char *token, *tok_buf, *tmp;
+    size_t i;
+
+    tmp = strdup(setting);
+    if(!tmp)
+      return CURLE_OUT_OF_MEMORY;
+
+    token = strtok_r(tmp, ", ", &tok_buf);
+    while(token) {
+      for(i = 0; cf_types[i]; ++i) {
+        if(strcasecompare(token, cf_types[i]->name)) {
+          cf_types[i]->log_level = CURL_LOG_DEBUG;
+          break;
+        }
+      }
+      token = strtok_r(NULL, ", ", &tok_buf);
+    }
+    free(tmp);
+  }
+  return CURLE_OK;
+}
+#else /* DEBUGBUILD */
+
+CURLcode Curl_log_init(void)
+{
+  return CURLE_OK;
+}
+
+#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L)
+void Curl_log_cf_debug(struct Curl_easy *data, struct Curl_cfilter *cf,
+                       const char *fmt, ...)
+{
+  (void)data;
+  (void)cf;
+  (void)fmt;
+}
+#endif
+
+#endif /* !DEBUGBUILD */
diff --git a/Utilities/cmcurl/lib/curl_log.h b/Utilities/cmcurl/lib/curl_log.h
new file mode 100644
index 0000000..ad6143f
--- /dev/null
+++ b/Utilities/cmcurl/lib/curl_log.h
@@ -0,0 +1,138 @@
+#ifndef HEADER_CURL_LOG_H
+#define HEADER_CURL_LOG_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+struct Curl_easy;
+struct Curl_cfilter;
+
+/**
+ * Init logging, return != 0 on failure.
+ */
+CURLcode Curl_log_init(void);
+
+
+void Curl_infof(struct Curl_easy *, const char *fmt, ...);
+void Curl_failf(struct Curl_easy *, const char *fmt, ...);
+
+#if defined(CURL_DISABLE_VERBOSE_STRINGS)
+
+#if defined(HAVE_VARIADIC_MACROS_C99)
+#define infof(...)  Curl_nop_stmt
+#elif defined(HAVE_VARIADIC_MACROS_GCC)
+#define infof(x...)  Curl_nop_stmt
+#else
+#error "missing VARIADIC macro define, fix and rebuild!"
+#endif
+
+#else /* CURL_DISABLE_VERBOSE_STRINGS */
+
+#define infof Curl_infof
+
+#endif /* CURL_DISABLE_VERBOSE_STRINGS */
+
+#define failf Curl_failf
+
+
+#define CURL_LOG_DEFAULT  0
+#define CURL_LOG_DEBUG    1
+#define CURL_LOG_TRACE    2
+
+
+/* the function used to output verbose information */
+void Curl_debug(struct Curl_easy *data, curl_infotype type,
+                char *ptr, size_t size);
+
+#ifdef DEBUGBUILD
+
+/* explainer: we have some mix configuration and werror settings
+ * that define HAVE_VARIADIC_MACROS_C99 even though C89 is enforced
+ * on gnuc and some other compiler. Need to treat carefully.
+ */
+#if defined(HAVE_VARIADIC_MACROS_C99) && \
+    defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
+
+#define LOG_CF(data, cf, ...) \
+  do { if(Curl_log_cf_is_debug(cf)) \
+         Curl_log_cf_debug(data, cf, __VA_ARGS__); } while(0)
+#else
+#define LOG_CF Curl_log_cf_debug
+#endif
+
+void Curl_log_cf_debug(struct Curl_easy *data, struct Curl_cfilter *cf,
+#if defined(__GNUC__) && !defined(printf) &&                    \
+  defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \
+  !defined(__MINGW32__)
+                       const char *fmt, ...)
+                       __attribute__((format(printf, 3, 4)));
+#else
+                       const char *fmt, ...);
+#endif
+
+#define Curl_log_cf_is_debug(cf) \
+    ((cf) && (cf)->cft->log_level >= CURL_LOG_DEBUG)
+
+#else /* !DEBUGBUILD */
+
+#if defined(HAVE_VARIADIC_MACROS_C99) && \
+    defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
+#define LOG_CF(...)               Curl_nop_stmt
+#define Curl_log_cf_debug(...)    Curl_nop_stmt
+#elif defined(HAVE_VARIADIC_MACROS_GCC) && \
+    defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
+#define LOG_CF(x...)              Curl_nop_stmt
+#define Curl_log_cf_debug(x...)   Curl_nop_stmt
+#else
+#define LOG_CF                    Curl_log_cf_debug
+/* without c99, we seem unable to completely define away this function. */
+void Curl_log_cf_debug(struct Curl_easy *data, struct Curl_cfilter *cf,
+                       const char *fmt, ...);
+#endif
+
+#define Curl_log_cf_is_debug(x)   ((void)(x), FALSE)
+
+#endif  /* !DEBUGBUILD */
+
+#define LOG_CF_IS_DEBUG(x)        Curl_log_cf_is_debug(x)
+
+/* Macros intended for DEBUGF logging, use like:
+ * DEBUGF(infof(data, CFMSG(cf, "this filter %s rocks"), "very much"));
+ * and it will output:
+ * [CONN-1-0][CF-SSL] this filter very much rocks
+ * on connection #1 with sockindex 0 for filter of type "SSL". */
+#define DMSG(d,msg)  \
+  "[CONN-%ld] "msg, (d)->conn->connection_id
+#define DMSGI(d,i,msg)  \
+  "[CONN-%ld-%d] "msg, (d)->conn->connection_id, (i)
+#define CMSG(c,msg)  \
+  "[CONN-%ld] "msg, (c)->connection_id
+#define CMSGI(c,i,msg)  \
+  "[CONN-%ld-%d] "msg, (c)->connection_id, (i)
+#define CFMSG(cf,msg)  \
+  "[CONN-%ld-%d][CF-%s] "msg, (cf)->conn->connection_id, \
+  (cf)->sockindex, (cf)->cft->name
+
+
+
+#endif /* HEADER_CURL_LOG_H */
diff --git a/Utilities/cmcurl/lib/curl_md4.h b/Utilities/cmcurl/lib/curl_md4.h
index 8049355..03567b9 100644
--- a/Utilities/cmcurl/lib/curl_md4.h
+++ b/Utilities/cmcurl/lib/curl_md4.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_md5.h b/Utilities/cmcurl/lib/curl_md5.h
index 7893296..ec2512f 100644
--- a/Utilities/cmcurl/lib/curl_md5.h
+++ b/Utilities/cmcurl/lib/curl_md5.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_memory.h b/Utilities/cmcurl/lib/curl_memory.h
index 092fc9f..7af1391 100644
--- a/Utilities/cmcurl/lib/curl_memory.h
+++ b/Utilities/cmcurl/lib/curl_memory.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_memrchr.c b/Utilities/cmcurl/lib/curl_memrchr.c
index c329a61..3f3dc6d 100644
--- a/Utilities/cmcurl/lib/curl_memrchr.c
+++ b/Utilities/cmcurl/lib/curl_memrchr.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_memrchr.h b/Utilities/cmcurl/lib/curl_memrchr.h
index e7654e1..a1a4ba0 100644
--- a/Utilities/cmcurl/lib/curl_memrchr.h
+++ b/Utilities/cmcurl/lib/curl_memrchr.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_multibyte.c b/Utilities/cmcurl/lib/curl_multibyte.c
index 309dccb..522ea34 100644
--- a/Utilities/cmcurl/lib/curl_multibyte.c
+++ b/Utilities/cmcurl/lib/curl_multibyte.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_multibyte.h b/Utilities/cmcurl/lib/curl_multibyte.h
index 9297148..ddac1f6 100644
--- a/Utilities/cmcurl/lib/curl_multibyte.h
+++ b/Utilities/cmcurl/lib/curl_multibyte.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_ntlm_core.c b/Utilities/cmcurl/lib/curl_ntlm_core.c
index 690f8f7..25d2526 100644
--- a/Utilities/cmcurl/lib/curl_ntlm_core.c
+++ b/Utilities/cmcurl/lib/curl_ntlm_core.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -36,12 +36,13 @@
 /* Please keep the SSL backend-specific #if branches in this order:
 
    1. USE_OPENSSL
-   2. USE_GNUTLS
-   3. USE_NSS
-   4. USE_MBEDTLS
-   5. USE_SECTRANSP
-   6. USE_OS400CRYPTO
-   7. USE_WIN32_CRYPTO
+   2. USE_WOLFSSL
+   3. USE_GNUTLS
+   4. USE_NSS
+   5. USE_MBEDTLS
+   6. USE_SECTRANSP
+   7. USE_OS400CRYPTO
+   8. USE_WIN32_CRYPTO
 
    This ensures that:
    - the same SSL branch gets activated throughout this source
diff --git a/Utilities/cmcurl/lib/curl_ntlm_core.h b/Utilities/cmcurl/lib/curl_ntlm_core.h
index 60444c9..33b651f 100644
--- a/Utilities/cmcurl/lib/curl_ntlm_core.h
+++ b/Utilities/cmcurl/lib/curl_ntlm_core.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -37,11 +37,11 @@
 #define NTLM_NEEDS_NSS_INIT
 #endif
 
-#ifdef USE_WOLFSSL
+#if defined(USE_OPENSSL)
+#  include <openssl/ssl.h>
+#elif defined(USE_WOLFSSL)
 #  include <wolfssl/options.h>
 #  include <wolfssl/openssl/ssl.h>
-#elif defined(USE_OPENSSL)
-#  include <openssl/ssl.h>
 #endif
 
 /* Helpers to generate function byte arguments in little endian order */
diff --git a/Utilities/cmcurl/lib/curl_ntlm_wb.c b/Utilities/cmcurl/lib/curl_ntlm_wb.c
index fcf5075..a10e2a1 100644
--- a/Utilities/cmcurl/lib/curl_ntlm_wb.c
+++ b/Utilities/cmcurl/lib/curl_ntlm_wb.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_ntlm_wb.h b/Utilities/cmcurl/lib/curl_ntlm_wb.h
index 1f04db8..37704c0 100644
--- a/Utilities/cmcurl/lib/curl_ntlm_wb.h
+++ b/Utilities/cmcurl/lib/curl_ntlm_wb.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_path.c b/Utilities/cmcurl/lib/curl_path.c
index f00e3ee..977e533 100644
--- a/Utilities/cmcurl/lib/curl_path.c
+++ b/Utilities/cmcurl/lib/curl_path.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -32,70 +32,65 @@
 #include "escape.h"
 #include "memdebug.h"
 
+#define MAX_SSHPATH_LEN 100000 /* arbitrary */
+
 /* figure out the path to work with in this particular request */
 CURLcode Curl_getworkingpath(struct Curl_easy *data,
                              char *homedir,  /* when SFTP is used */
                              char **path) /* returns the  allocated
                                              real path to work with */
 {
-  char *real_path = NULL;
   char *working_path;
   size_t working_path_len;
+  struct dynbuf npath;
   CURLcode result =
     Curl_urldecode(data->state.up.path, 0, &working_path,
                    &working_path_len, REJECT_ZERO);
   if(result)
     return result;
 
+  /* new path to switch to in case we need to */
+  Curl_dyn_init(&npath, MAX_SSHPATH_LEN);
+
   /* Check for /~/, indicating relative to the user's home directory */
-  if(data->conn->handler->protocol & CURLPROTO_SCP) {
-    real_path = malloc(working_path_len + 1);
-    if(!real_path) {
+  if((data->conn->handler->protocol & CURLPROTO_SCP) &&
+     (working_path_len > 3) && (!memcmp(working_path, "/~/", 3))) {
+    /* It is referenced to the home directory, so strip the leading '/~/' */
+    if(Curl_dyn_addn(&npath, &working_path[3], working_path_len - 3)) {
       free(working_path);
       return CURLE_OUT_OF_MEMORY;
     }
-    if((working_path_len > 3) && (!memcmp(working_path, "/~/", 3)))
-      /* It is referenced to the home directory, so strip the leading '/~/' */
-      memcpy(real_path, working_path + 3, working_path_len - 2);
-    else
-      memcpy(real_path, working_path, 1 + working_path_len);
   }
-  else if(data->conn->handler->protocol & CURLPROTO_SFTP) {
-    if((working_path_len > 1) && (working_path[1] == '~')) {
-      size_t homelen = strlen(homedir);
-      real_path = malloc(homelen + working_path_len + 1);
-      if(!real_path) {
-        free(working_path);
-        return CURLE_OUT_OF_MEMORY;
-      }
-      /* It is referenced to the home directory, so strip the
-         leading '/' */
-      memcpy(real_path, homedir, homelen);
-      /* Only add a trailing '/' if homedir does not end with one */
-      if(homelen == 0 || real_path[homelen - 1] != '/') {
-        real_path[homelen] = '/';
-        homelen++;
-        real_path[homelen] = '\0';
-      }
-      if(working_path_len > 3) {
-        memcpy(real_path + homelen, working_path + 3,
-               1 + working_path_len -3);
-      }
+  else if((data->conn->handler->protocol & CURLPROTO_SFTP) &&
+          (working_path_len > 2) && !memcmp(working_path, "/~/", 3)) {
+    size_t len;
+    const char *p;
+    int copyfrom = 3;
+    if(Curl_dyn_add(&npath, homedir)) {
+      free(working_path);
+      return CURLE_OUT_OF_MEMORY;
     }
-    else {
-      real_path = malloc(working_path_len + 1);
-      if(!real_path) {
-        free(working_path);
-        return CURLE_OUT_OF_MEMORY;
-      }
-      memcpy(real_path, working_path, 1 + working_path_len);
+    /* Copy a separating '/' if homedir does not end with one */
+    len = Curl_dyn_len(&npath);
+    p = Curl_dyn_ptr(&npath);
+    if(len && (p[len-1] != '/'))
+      copyfrom = 2;
+
+    if(Curl_dyn_addn(&npath,
+                     &working_path[copyfrom], working_path_len - copyfrom)) {
+      free(working_path);
+      return CURLE_OUT_OF_MEMORY;
     }
   }
 
-  free(working_path);
+  if(Curl_dyn_len(&npath)) {
+    free(working_path);
 
-  /* store the pointer for the caller to receive */
-  *path = real_path;
+    /* store the pointer for the caller to receive */
+    *path = Curl_dyn_ptr(&npath);
+  }
+  else
+    *path = working_path;
 
   return CURLE_OK;
 }
diff --git a/Utilities/cmcurl/lib/curl_path.h b/Utilities/cmcurl/lib/curl_path.h
index 98e56ea..9ed09de 100644
--- a/Utilities/cmcurl/lib/curl_path.h
+++ b/Utilities/cmcurl/lib/curl_path.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_printf.h b/Utilities/cmcurl/lib/curl_printf.h
index 3823828..6d3d492 100644
--- a/Utilities/cmcurl/lib/curl_printf.h
+++ b/Utilities/cmcurl/lib/curl_printf.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_range.c b/Utilities/cmcurl/lib/curl_range.c
index 4999936..d499953 100644
--- a/Utilities/cmcurl/lib/curl_range.c
+++ b/Utilities/cmcurl/lib/curl_range.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_range.h b/Utilities/cmcurl/lib/curl_range.h
index 33570ab..77679e2 100644
--- a/Utilities/cmcurl/lib/curl_range.h
+++ b/Utilities/cmcurl/lib/curl_range.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_rtmp.c b/Utilities/cmcurl/lib/curl_rtmp.c
index 1932cb4..2679a2c 100644
--- a/Utilities/cmcurl/lib/curl_rtmp.c
+++ b/Utilities/cmcurl/lib/curl_rtmp.c
@@ -5,8 +5,8 @@
  *                | (__| |_| |  _ <| |___
  *                 \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2012, Howard Chu, <hyc@highlandsun.com>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Howard Chu, <hyc@highlandsun.com>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_rtmp.h b/Utilities/cmcurl/lib/curl_rtmp.h
index f856085..9b93ee0 100644
--- a/Utilities/cmcurl/lib/curl_rtmp.h
+++ b/Utilities/cmcurl/lib/curl_rtmp.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2010 - 2022, Howard Chu, <hyc@highlandsun.com>
+ * Copyright (C) Howard Chu, <hyc@highlandsun.com>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_sasl.c b/Utilities/cmcurl/lib/curl_sasl.c
index 46ee800..119fb9b 100644
--- a/Utilities/cmcurl/lib/curl_sasl.c
+++ b/Utilities/cmcurl/lib/curl_sasl.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -36,7 +36,8 @@
 #include "curl_setup.h"
 
 #if !defined(CURL_DISABLE_IMAP) || !defined(CURL_DISABLE_SMTP) || \
-  !defined(CURL_DISABLE_POP3)
+  !defined(CURL_DISABLE_POP3) || \
+  (!defined(CURL_DISABLE_LDAP) && defined(USE_OPENLDAP))
 
 #include <curl/curl.h>
 #include "urldata.h"
diff --git a/Utilities/cmcurl/lib/curl_sasl.h b/Utilities/cmcurl/lib/curl_sasl.h
index c709d56..e94e643 100644
--- a/Utilities/cmcurl/lib/curl_sasl.h
+++ b/Utilities/cmcurl/lib/curl_sasl.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -125,9 +125,9 @@
   unsigned short authmechs;  /* Accepted authentication mechanisms */
   unsigned short prefmech;   /* Preferred authentication mechanism */
   unsigned short authused;   /* Auth mechanism used for the connection */
-  bool resetprefs;           /* For URL auth option parsing. */
-  bool mutual_auth;          /* Mutual authentication enabled (GSSAPI only) */
-  bool force_ir;             /* Protocol always supports initial response */
+  BIT(resetprefs);           /* For URL auth option parsing. */
+  BIT(mutual_auth);          /* Mutual authentication enabled (GSSAPI only) */
+  BIT(force_ir);             /* Protocol always supports initial response */
 };
 
 /* This is used to test whether the line starts with the given mechanism */
diff --git a/Utilities/cmcurl/lib/curl_setup.h b/Utilities/cmcurl/lib/curl_setup.h
index 084d19a..06fb613 100644
--- a/Utilities/cmcurl/lib/curl_setup.h
+++ b/Utilities/cmcurl/lib/curl_setup.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -456,8 +456,8 @@
 #  endif
 #endif
 
-#if (SIZEOF_CURL_OFF_T == 4)
-#  define CURL_OFF_T_MAX CURL_OFF_T_C(0x7FFFFFFF)
+#if (SIZEOF_CURL_OFF_T < 8)
+#error "too small curl_off_t"
 #else
    /* assume SIZEOF_CURL_OFF_T == 8 */
 #  define CURL_OFF_T_MAX CURL_OFF_T_C(0x7FFFFFFFFFFFFFFF)
@@ -792,14 +792,16 @@
 #define FOPEN_APPENDTEXT "a"
 #endif
 
-/* WinSock destroys recv() buffer when send() failed.
- * Enabled automatically for Windows and for Cygwin as Cygwin sockets are
- * wrappers for WinSock sockets. https://github.com/curl/curl/issues/657
- * Define DONT_USE_RECV_BEFORE_SEND_WORKAROUND to force disable workaround.
+/* Windows workaround to recv before every send, because apparently Winsock
+ * destroys destroys recv() buffer when send() failed.
+ * This workaround is now disabled by default since it caused hard to fix bugs.
+ * Define USE_RECV_BEFORE_SEND_WORKAROUND to enable it.
+ * https://github.com/curl/curl/issues/657
+ * https://github.com/curl/curl/pull/10409
  */
 #if !defined(DONT_USE_RECV_BEFORE_SEND_WORKAROUND)
 #  if defined(WIN32) || defined(__CYGWIN__)
-#    define USE_RECV_BEFORE_SEND_WORKAROUND
+/* #    define USE_RECV_BEFORE_SEND_WORKAROUND */
 #  endif
 #else  /* DONT_USE_RECV_BEFORE_SEND_WORKAROUND */
 #  ifdef USE_RECV_BEFORE_SEND_WORKAROUND
@@ -851,6 +853,13 @@
 #define USE_HTTP3
 #endif
 
+/* Certain Windows implementations are not aligned with what curl expects,
+   so always use the local one on this platform. E.g. the mingw-w64
+   implementation can return wrong results for non-ASCII inputs. */
+#if defined(HAVE_BASENAME) && defined(WIN32)
+#undef HAVE_BASENAME
+#endif
+
 #if defined(USE_UNIX_SOCKETS) && defined(WIN32)
 #  if defined(__MINGW32__) && !defined(LUP_SECURE)
      typedef u_short ADDRESS_FAMILY; /* Classic mingw, 11y+ old mingw-w64 */
@@ -868,4 +877,10 @@
 #  endif
 #endif
 
+/* OpenSSLv3 marks DES, MD5 and ENGINE functions deprecated but we have no
+   replacements (yet) so tell the compiler to not warn for them. */
+#ifdef USE_OPENSSL
+#define OPENSSL_SUPPRESS_DEPRECATED
+#endif
+
 #endif /* HEADER_CURL_SETUP_H */
diff --git a/Utilities/cmcurl/lib/curl_setup_once.h b/Utilities/cmcurl/lib/curl_setup_once.h
index ac4a7f1..dde7229 100644
--- a/Utilities/cmcurl/lib/curl_setup_once.h
+++ b/Utilities/cmcurl/lib/curl_setup_once.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -69,6 +69,14 @@
 #include <unistd.h>
 #endif
 
+#ifdef USE_WOLFSSL
+#  if defined(HAVE_STDINT_H)
+#    include <stdint.h>
+#  elif defined(HAVE_INTTYPES_H)
+#    include <inttypes.h>
+#  endif
+#endif
+
 #ifdef __hpux
 #  if !defined(_XOPEN_SOURCE_EXTENDED) || defined(_KERNEL)
 #    ifdef _APP32_64BIT_OFF_T
diff --git a/Utilities/cmcurl/lib/curl_sha256.h b/Utilities/cmcurl/lib/curl_sha256.h
index 39523af..c5e157b 100644
--- a/Utilities/cmcurl/lib/curl_sha256.h
+++ b/Utilities/cmcurl/lib/curl_sha256.h
@@ -7,8 +7,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2017, Florin Petriuc, <petriuc.florin@gmail.com>
- * Copyright (C) 2018 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Florin Petriuc, <petriuc.florin@gmail.com>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_sspi.c b/Utilities/cmcurl/lib/curl_sspi.c
index 33108c4..eb21e7e 100644
--- a/Utilities/cmcurl/lib/curl_sspi.c
+++ b/Utilities/cmcurl/lib/curl_sspi.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_sspi.h b/Utilities/cmcurl/lib/curl_sspi.h
index ad11130..9816d59 100644
--- a/Utilities/cmcurl/lib/curl_sspi.h
+++ b/Utilities/cmcurl/lib/curl_sspi.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_threads.c b/Utilities/cmcurl/lib/curl_threads.c
index dff6167..e13e294 100644
--- a/Utilities/cmcurl/lib/curl_threads.c
+++ b/Utilities/cmcurl/lib/curl_threads.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curl_threads.h b/Utilities/cmcurl/lib/curl_threads.h
index 63392f6..facbc73 100644
--- a/Utilities/cmcurl/lib/curl_threads.h
+++ b/Utilities/cmcurl/lib/curl_threads.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/curlx.h b/Utilities/cmcurl/lib/curlx.h
index 1796afa..7a753d6 100644
--- a/Utilities/cmcurl/lib/curlx.h
+++ b/Utilities/cmcurl/lib/curlx.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/dict.c b/Utilities/cmcurl/lib/dict.c
index 993373e..0ce62a0 100644
--- a/Utilities/cmcurl/lib/dict.c
+++ b/Utilities/cmcurl/lib/dict.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -98,37 +98,27 @@
   PROTOPT_NONE | PROTOPT_NOURLQUERY     /* flags */
 };
 
-static char *unescape_word(const char *inputbuff)
+#define DYN_DICT_WORD 10000
+static char *unescape_word(const char *input)
 {
-  char *newp = NULL;
-  char *dictp;
-  size_t len;
+  struct dynbuf out;
+  const char *ptr;
+  CURLcode result = CURLE_OK;
+  Curl_dyn_init(&out, DYN_DICT_WORD);
 
-  CURLcode result = Curl_urldecode(inputbuff, 0, &newp, &len,
-                                   REJECT_NADA);
-  if(!newp || result)
-    return NULL;
-
-  dictp = malloc(len*2 + 1); /* add one for terminating zero */
-  if(dictp) {
-    char *ptr;
-    char ch;
-    int olen = 0;
-    /* According to RFC2229 section 2.2, these letters need to be escaped with
-       \[letter] */
-    for(ptr = newp;
-        (ch = *ptr) != 0;
-        ptr++) {
-      if((ch <= 32) || (ch == 127) ||
-          (ch == '\'') || (ch == '\"') || (ch == '\\')) {
-        dictp[olen++] = '\\';
-      }
-      dictp[olen++] = ch;
-    }
-    dictp[olen] = 0;
+  /* According to RFC2229 section 2.2, these letters need to be escaped with
+     \[letter] */
+  for(ptr = input; *ptr; ptr++) {
+    char ch = *ptr;
+    if((ch <= 32) || (ch == 127) ||
+       (ch == '\'') || (ch == '\"') || (ch == '\\'))
+      result = Curl_dyn_addn(&out, "\\", 1);
+    if(!result)
+      result = Curl_dyn_addn(&out, ptr, 1);
+    if(result)
+      return NULL;
   }
-  free(newp);
-  return dictp;
+  return Curl_dyn_ptr(&out);
 }
 
 /* sendf() sends formatted data to the server */
@@ -178,20 +168,25 @@
 static CURLcode dict_do(struct Curl_easy *data, bool *done)
 {
   char *word;
-  char *eword;
+  char *eword = NULL;
   char *ppath;
   char *database = NULL;
   char *strategy = NULL;
   char *nthdef = NULL; /* This is not part of the protocol, but required
                           by RFC 2229 */
-  CURLcode result = CURLE_OK;
+  CURLcode result;
   struct connectdata *conn = data->conn;
   curl_socket_t sockfd = conn->sock[FIRSTSOCKET];
 
-  char *path = data->state.up.path;
+  char *path;
 
   *done = TRUE; /* unconditionally */
 
+  /* url-decode path before further evaluation */
+  result = Curl_urldecode(data->state.up.path, 0, &path, NULL, REJECT_CTRL);
+  if(result)
+    return result;
+
   if(strncasecompare(path, DICT_MATCH, sizeof(DICT_MATCH)-1) ||
      strncasecompare(path, DICT_MATCH2, sizeof(DICT_MATCH2)-1) ||
      strncasecompare(path, DICT_MATCH3, sizeof(DICT_MATCH3)-1)) {
@@ -225,8 +220,10 @@
     }
 
     eword = unescape_word(word);
-    if(!eword)
-      return CURLE_OUT_OF_MEMORY;
+    if(!eword) {
+      result = CURLE_OUT_OF_MEMORY;
+      goto error;
+    }
 
     result = sendf(sockfd, data,
                    "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n"
@@ -239,11 +236,9 @@
                    strategy,
                    eword);
 
-    free(eword);
-
     if(result) {
       failf(data, "Failed sending DICT request");
-      return result;
+      goto error;
     }
     Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); /* no upload */
   }
@@ -273,8 +268,10 @@
     }
 
     eword = unescape_word(word);
-    if(!eword)
-      return CURLE_OUT_OF_MEMORY;
+    if(!eword) {
+      result = CURLE_OUT_OF_MEMORY;
+      goto error;
+    }
 
     result = sendf(sockfd, data,
                    "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n"
@@ -285,11 +282,9 @@
                    database,
                    eword);
 
-    free(eword);
-
     if(result) {
       failf(data, "Failed sending DICT request");
-      return result;
+      goto error;
     }
     Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1);
   }
@@ -310,13 +305,16 @@
                      "QUIT\r\n", ppath);
       if(result) {
         failf(data, "Failed sending DICT request");
-        return result;
+        goto error;
       }
 
       Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1);
     }
   }
 
-  return CURLE_OK;
+  error:
+  free(eword);
+  free(path);
+  return result;
 }
 #endif /* CURL_DISABLE_DICT */
diff --git a/Utilities/cmcurl/lib/dict.h b/Utilities/cmcurl/lib/dict.h
index b283a0d..ba9a927 100644
--- a/Utilities/cmcurl/lib/dict.h
+++ b/Utilities/cmcurl/lib/dict.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/doh.c b/Utilities/cmcurl/lib/doh.c
index 3b1d5d6..922d757 100644
--- a/Utilities/cmcurl/lib/doh.c
+++ b/Utilities/cmcurl/lib/doh.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2018 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -396,6 +396,7 @@
     goto error;
   dohp->pending++;
 
+#ifdef ENABLE_IPV6
   if((conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) {
     /* create IPv6 DoH request */
     result = dohprobe(data, &dohp->probe[DOH_PROBE_SLOT_IPADDR_V6],
@@ -405,6 +406,7 @@
       goto error;
     dohp->pending++;
   }
+#endif
   return NULL;
 
   error:
@@ -950,7 +952,7 @@
         Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
 
       /* we got a response, store it in the cache */
-      dns = Curl_cache_addr(data, ai, dohp->host, dohp->port);
+      dns = Curl_cache_addr(data, ai, dohp->host, 0, dohp->port);
 
       if(data->share)
         Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
diff --git a/Utilities/cmcurl/lib/doh.h b/Utilities/cmcurl/lib/doh.h
index 678e807..7d7b694 100644
--- a/Utilities/cmcurl/lib/doh.h
+++ b/Utilities/cmcurl/lib/doh.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2018 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/dynbuf.c b/Utilities/cmcurl/lib/dynbuf.c
index 0b1cf9a..bd3b935 100644
--- a/Utilities/cmcurl/lib/dynbuf.c
+++ b/Utilities/cmcurl/lib/dynbuf.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2020 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -99,8 +99,7 @@
        include that as well when it uses this code */
     void *p = realloc(s->bufr, a);
     if(!p) {
-      Curl_safefree(s->bufr);
-      s->leng = s->allc = 0;
+      Curl_dyn_free(s);
       return CURLE_OUT_OF_MEMORY;
     }
     s->bufr = p;
diff --git a/Utilities/cmcurl/lib/dynbuf.h b/Utilities/cmcurl/lib/dynbuf.h
index 04a728c..57ad62b 100644
--- a/Utilities/cmcurl/lib/dynbuf.h
+++ b/Utilities/cmcurl/lib/dynbuf.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2020 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/easy.c b/Utilities/cmcurl/lib/easy.c
index d7f93be..27124a7 100644
--- a/Utilities/cmcurl/lib/easy.c
+++ b/Utilities/cmcurl/lib/easy.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -65,6 +65,7 @@
 #include "easyif.h"
 #include "multiif.h"
 #include "select.h"
+#include "cfilters.h"
 #include "sendf.h" /* for failf function prototype */
 #include "connect.h" /* for Curl_getconnectinfo */
 #include "slist.h"
@@ -113,7 +114,7 @@
 #if defined(_WIN32_WCE)
 #define system_strdup _strdup
 #elif !defined(HAVE_STRDUP)
-#define system_strdup curlx_strdup
+#define system_strdup Curl_strdup
 #else
 #define system_strdup strdup
 #endif
@@ -164,6 +165,11 @@
 #endif
   }
 
+  if(Curl_log_init()) {
+    DEBUGF(fprintf(stderr, "Error: Curl_log_init failed\n"));
+    goto fail;
+  }
+
   if(!Curl_ssl_init()) {
     DEBUGF(fprintf(stderr, "Error: Curl_ssl_init failed\n"));
     goto fail;
@@ -913,11 +919,9 @@
       goto fail;
   }
 
-  /* duplicate all values in 'change' */
-  if(data->state.cookielist) {
-    outcurl->state.cookielist =
-      Curl_slist_duplicate(data->state.cookielist);
-    if(!outcurl->state.cookielist)
+  if(data->set.cookielist) {
+    outcurl->set.cookielist = Curl_slist_duplicate(data->set.cookielist);
+    if(!outcurl->set.cookielist)
       goto fail;
   }
 #endif
@@ -1003,8 +1007,8 @@
 
   if(outcurl) {
 #ifndef CURL_DISABLE_COOKIES
-    curl_slist_free_all(outcurl->state.cookielist);
-    outcurl->state.cookielist = NULL;
+    curl_slist_free_all(outcurl->set.cookielist);
+    outcurl->set.cookielist = NULL;
 #endif
     Curl_safefree(outcurl->state.buffer);
     Curl_dyn_free(&outcurl->state.headerb);
@@ -1101,7 +1105,7 @@
   k->keepon = newstate;
 
   if(!(newstate & KEEP_RECV_PAUSE)) {
-    Curl_http2_stream_pause(data, FALSE);
+    Curl_conn_ev_data_pause(data, FALSE);
 
     if(data->state.tempcount) {
       /* there are buffers for sending that can be delivered as the receive
@@ -1224,7 +1228,6 @@
     return result;
 
   *n = (size_t)n1;
-
   return CURLE_OK;
 }
 
@@ -1294,29 +1297,34 @@
                        struct connectdata *conn,
                        void *param)
 {
-  /* Param is unused. */
-  (void)param;
+  struct curltime *now = param;
 
+  if(Curl_timediff(*now, conn->keepalive) <= data->set.upkeep_interval_ms)
+    return 0;
+
+  /* briefly attach for action */
+  Curl_attach_connection(data, conn);
   if(conn->handler->connection_check) {
-    /* briefly attach the connection to this transfer for the purpose of
-       checking it */
-    Curl_attach_connection(data, conn);
-
     /* Do a protocol-specific keepalive check on the connection. */
     conn->handler->connection_check(data, conn, CONNCHECK_KEEPALIVE);
-    /* detach the connection again */
-    Curl_detach_connection(data);
   }
+  else {
+    /* Do the generic action on the FIRSTSOCKE filter chain */
+    Curl_conn_keep_alive(data, conn, FIRSTSOCKET);
+  }
+  Curl_detach_connection(data);
 
+  conn->keepalive = *now;
   return 0; /* continue iteration */
 }
 
 static CURLcode upkeep(struct conncache *conn_cache, void *data)
 {
+  struct curltime now = Curl_now();
   /* Loop over every connection and make connection alive. */
   Curl_conncache_foreach(data,
                          conn_cache,
-                         data,
+                         &now,
                          conn_upkeep);
   return CURLE_OK;
 }
diff --git a/Utilities/cmcurl/lib/easy_lock.h b/Utilities/cmcurl/lib/easy_lock.h
index d96e56b..5fa9477 100644
--- a/Utilities/cmcurl/lib/easy_lock.h
+++ b/Utilities/cmcurl/lib/easy_lock.h
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/easygetopt.c b/Utilities/cmcurl/lib/easygetopt.c
index a639bb3..2b8a521 100644
--- a/Utilities/cmcurl/lib/easygetopt.c
+++ b/Utilities/cmcurl/lib/easygetopt.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             ___|___/|_| ______|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/easyif.h b/Utilities/cmcurl/lib/easyif.h
index 205382c..570ebef 100644
--- a/Utilities/cmcurl/lib/easyif.h
+++ b/Utilities/cmcurl/lib/easyif.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/easyoptions.c b/Utilities/cmcurl/lib/easyoptions.c
index 03b814e..a9c1efd 100644
--- a/Utilities/cmcurl/lib/easyoptions.c
+++ b/Utilities/cmcurl/lib/easyoptions.c
@@ -1,11 +1,11 @@
 /***************************************************************************
  *                                  _   _ ____  _
- *  Project                     ___| | | |  _ | |
+ *  Project                     ___| | | |  _ \| |
  *                             / __| | | | |_) | |
  *                            | (__| |_| |  _ <| |___
- *                             ___|___/|_| ______|
+ *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/easyoptions.h b/Utilities/cmcurl/lib/easyoptions.h
index 33f816d..24b4cd9 100644
--- a/Utilities/cmcurl/lib/easyoptions.h
+++ b/Utilities/cmcurl/lib/easyoptions.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/escape.c b/Utilities/cmcurl/lib/escape.c
index ed59838..56aa2b3 100644
--- a/Utilities/cmcurl/lib/escape.c
+++ b/Utilities/cmcurl/lib/escape.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -97,7 +97,7 @@
     return strdup("");
 
   while(length--) {
-    unsigned char in = *string; /* we need to treat the characters unsigned */
+    unsigned char in = *string++; /* treat the characters unsigned */
 
     if(Curl_isunreserved(in)) {
       /* append this */
@@ -106,15 +106,28 @@
     }
     else {
       /* encode it */
-      if(Curl_dyn_addf(&d, "%%%02X", in))
+      const char hex[] = "0123456789ABCDEF";
+      char out[3]={'%'};
+      out[1] = hex[in>>4];
+      out[2] = hex[in & 0xf];
+      if(Curl_dyn_addn(&d, out, 3))
         return NULL;
     }
-    string++;
   }
 
   return Curl_dyn_ptr(&d);
 }
 
+static const unsigned char hextable[] = {
+  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0,       /* 0x30 - 0x3f */
+  0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4f */
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,       /* 0x50 - 0x5f */
+  0, 10, 11, 12, 13, 14, 15                             /* 0x60 - 0x66 */
+};
+
+/* the input is a single hex digit */
+#define onehex2dec(x) hextable[x - '0']
+
 /*
  * Curl_urldecode() URL decodes the given string.
  *
@@ -137,54 +150,47 @@
 {
   size_t alloc;
   char *ns;
-  size_t strindex = 0;
-  unsigned long hex;
 
   DEBUGASSERT(string);
   DEBUGASSERT(ctrl >= REJECT_NADA); /* crash on TRUE/FALSE */
 
-  alloc = (length?length:strlen(string)) + 1;
-  ns = malloc(alloc);
+  alloc = (length?length:strlen(string));
+  ns = malloc(alloc + 1);
 
   if(!ns)
     return CURLE_OUT_OF_MEMORY;
 
-  while(--alloc > 0) {
+  /* store output string */
+  *ostring = ns;
+
+  while(alloc) {
     unsigned char in = *string;
     if(('%' == in) && (alloc > 2) &&
        ISXDIGIT(string[1]) && ISXDIGIT(string[2])) {
       /* this is two hexadecimal digits following a '%' */
-      char hexstr[3];
-      char *ptr;
-      hexstr[0] = string[1];
-      hexstr[1] = string[2];
-      hexstr[2] = 0;
+      in = (unsigned char)(onehex2dec(string[1]) << 4) | onehex2dec(string[2]);
 
-      hex = strtoul(hexstr, &ptr, 16);
-
-      in = curlx_ultouc(hex); /* this long is never bigger than 255 anyway */
-
-      string += 2;
-      alloc -= 2;
+      string += 3;
+      alloc -= 3;
+    }
+    else {
+      string++;
+      alloc--;
     }
 
     if(((ctrl == REJECT_CTRL) && (in < 0x20)) ||
        ((ctrl == REJECT_ZERO) && (in == 0))) {
-      free(ns);
+      Curl_safefree(*ostring);
       return CURLE_URL_MALFORMAT;
     }
 
-    ns[strindex++] = in;
-    string++;
+    *ns++ = in;
   }
-  ns[strindex] = 0; /* terminate it */
+  *ns = 0; /* terminate it */
 
   if(olen)
     /* store output size */
-    *olen = strindex;
-
-  /* store output string */
-  *ostring = ns;
+    *olen = ns - *ostring;
 
   return CURLE_OK;
 }
diff --git a/Utilities/cmcurl/lib/escape.h b/Utilities/cmcurl/lib/escape.h
index 61d4611..cdbb712 100644
--- a/Utilities/cmcurl/lib/escape.h
+++ b/Utilities/cmcurl/lib/escape.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/file.c b/Utilities/cmcurl/lib/file.c
index 3f642be..51c5d07 100644
--- a/Utilities/cmcurl/lib/file.c
+++ b/Utilities/cmcurl/lib/file.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/file.h b/Utilities/cmcurl/lib/file.h
index 826d453..4565525 100644
--- a/Utilities/cmcurl/lib/file.h
+++ b/Utilities/cmcurl/lib/file.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/fileinfo.c b/Utilities/cmcurl/lib/fileinfo.c
index 7bbf24b..409b38f 100644
--- a/Utilities/cmcurl/lib/fileinfo.c
+++ b/Utilities/cmcurl/lib/fileinfo.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2010 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/fileinfo.h b/Utilities/cmcurl/lib/fileinfo.h
index 5bad718..af44212 100644
--- a/Utilities/cmcurl/lib/fileinfo.h
+++ b/Utilities/cmcurl/lib/fileinfo.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2010 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/fopen.c b/Utilities/cmcurl/lib/fopen.c
index ad3691b..f710dbf 100644
--- a/Utilities/cmcurl/lib/fopen.c
+++ b/Utilities/cmcurl/lib/fopen.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -106,7 +106,6 @@
 
   free(tempstore);
 
-  *tempname = NULL;
   return result;
 }
 
diff --git a/Utilities/cmcurl/lib/fopen.h b/Utilities/cmcurl/lib/fopen.h
index 289e55f..e3a919d 100644
--- a/Utilities/cmcurl/lib/fopen.h
+++ b/Utilities/cmcurl/lib/fopen.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/formdata.c b/Utilities/cmcurl/lib/formdata.c
index b30e8de..2bdb9f2 100644
--- a/Utilities/cmcurl/lib/formdata.c
+++ b/Utilities/cmcurl/lib/formdata.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/formdata.h b/Utilities/cmcurl/lib/formdata.h
index c6c6397..caabb63 100644
--- a/Utilities/cmcurl/lib/formdata.h
+++ b/Utilities/cmcurl/lib/formdata.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/ftp.c b/Utilities/cmcurl/lib/ftp.c
index 874725b..ef9ec1e 100644
--- a/Utilities/cmcurl/lib/ftp.c
+++ b/Utilities/cmcurl/lib/ftp.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -61,6 +61,7 @@
 #include "strcase.h"
 #include "vtls/vtls.h"
 #include "cfilters.h"
+#include "cf-socket.h"
 #include "connect.h"
 #include "strerror.h"
 #include "inet_ntop.h"
@@ -285,8 +286,8 @@
   conn->bits.do_more = FALSE;
 
   (void)curlx_nonblock(s, TRUE); /* enable non-blocking */
-  /* Replace any filter on SECONDARY with one listeing on this socket */
-  result = Curl_conn_socket_accepted_set(data, conn, SECONDARYSOCKET, &s);
+  /* Replace any filter on SECONDARY with one listening on this socket */
+  result = Curl_conn_tcp_accepted_set(data, conn, SECONDARYSOCKET, &s);
   if(result)
     return result;
 
@@ -435,6 +436,12 @@
   bool connected;
 
   DEBUGF(infof(data, "ftp InitiateTransfer()"));
+  if(conn->bits.ftp_use_data_ssl && data->set.ftp_use_port &&
+     !Curl_conn_is_ssl(conn, SECONDARYSOCKET)) {
+    result = Curl_ssl_cfilter_add(data, conn, SECONDARYSOCKET);
+    if(result)
+      return result;
+  }
   result = Curl_conn_connect(data, SECONDARYSOCKET, TRUE, &connected);
   if(result || !connected)
     return result;
@@ -819,26 +826,11 @@
 
   if(FTP_STOP == ftpc->state) {
     int bits = GETSOCK_READSOCK(0);
-    bool any = FALSE;
 
     /* if stopped and still in this state, then we're also waiting for a
        connect on the secondary connection */
     socks[0] = conn->sock[FIRSTSOCKET];
-
-    if(!data->set.ftp_use_port) {
-      int s;
-      int i;
-      /* PORT is used to tell the server to connect to us, and during that we
-         don't do happy eyeballs, but we do if we connect to the server */
-      for(s = 1, i = 0; i<2; i++) {
-        if(conn->tempsock[i] != CURL_SOCKET_BAD) {
-          socks[s] = conn->tempsock[i];
-          bits |= GETSOCK_WRITESOCK(s++);
-          any = TRUE;
-        }
-      }
-    }
-    if(!any) {
+    if(conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD) {
       socks[1] = conn->sock[SECONDARYSOCKET];
       bits |= GETSOCK_WRITESOCK(1) | GETSOCK_READSOCK(1);
     }
@@ -1024,9 +1016,9 @@
 
     if(*addr != '\0') {
       /* attempt to get the address of the given interface name */
-      switch(Curl_if2ip(conn->ip_addr->ai_family,
+      switch(Curl_if2ip(conn->remote_addr->family,
 #ifdef ENABLE_IPV6
-                        Curl_ipv6_scope(conn->ip_addr->ai_addr),
+                        Curl_ipv6_scope(&conn->remote_addr->sa_addr),
                         conn->scope_id,
 #endif
                         addr, hbuf, sizeof(hbuf))) {
@@ -1097,7 +1089,7 @@
   portsock = CURL_SOCKET_BAD;
   error = 0;
   for(ai = res; ai; ai = ai->ai_next) {
-    if(Curl_socket(data, ai, NULL, &portsock)) {
+    if(Curl_socket_open(data, ai, NULL, conn->transport, &portsock)) {
       error = SOCKERRNO;
       continue;
     }
@@ -1266,9 +1258,8 @@
   /* store which command was sent */
   ftpc->count1 = fcmd;
 
-  /* Replace any filter on SECONDARY with one listeing on this socket */
-  result = Curl_conn_socket_accepted_set(data, conn, SECONDARYSOCKET,
-                                         &portsock);
+  /* Replace any filter on SECONDARY with one listening on this socket */
+  result = Curl_conn_tcp_listen_set(data, conn, SECONDARYSOCKET, &portsock);
   if(result)
     goto out;
   portsock = CURL_SOCKET_BAD; /* now held in filter */
@@ -1279,7 +1270,7 @@
     state(data, FTP_STOP);
   }
   if(portsock != CURL_SOCKET_BAD)
-    Curl_closesocket(data, conn, portsock);
+    Curl_socket_close(data, conn, portsock);
   free(addr);
   return result;
 }
@@ -1810,6 +1801,29 @@
   return conn->primary_ip;
 }
 
+static bool match_pasv_6nums(const char *p,
+                             unsigned int *array) /* 6 numbers */
+{
+  int i;
+  for(i = 0; i < 6; i++) {
+    unsigned long num;
+    char *endp;
+    if(i) {
+      if(*p != ',')
+        return FALSE;
+      p++;
+    }
+    if(!ISDIGIT(*p))
+      return FALSE;
+    num = strtoul(p, &endp, 10);
+    if(num > 255)
+      return FALSE;
+    array[i] = (unsigned int)num;
+    p = endp;
+  }
+  return TRUE;
+}
+
 static CURLcode ftp_state_pasv_resp(struct Curl_easy *data,
                                     int ftpcode)
 {
@@ -1829,27 +1843,18 @@
     /* positive EPSV response */
     char *ptr = strchr(str, '(');
     if(ptr) {
-      unsigned int num;
-      char separator[4];
+      char sep;
       ptr++;
-      if(5 == sscanf(ptr, "%c%c%c%u%c",
-                     &separator[0],
-                     &separator[1],
-                     &separator[2],
-                     &num,
-                     &separator[3])) {
-        const char sep1 = separator[0];
-        int i;
-
-        /* The four separators should be identical, or else this is an oddly
-           formatted reply and we bail out immediately. */
-        for(i = 1; i<4; i++) {
-          if(separator[i] != sep1) {
-            ptr = NULL; /* set to NULL to signal error */
-            break;
-          }
-        }
-        if(num > 0xffff) {
+      /* |||12345| */
+      sep = ptr[0];
+      /* the ISDIGIT() check here is because strtoul() accepts leading minus
+         etc */
+      if((ptr[1] == sep) && (ptr[2] == sep) && ISDIGIT(ptr[3])) {
+        char *endp;
+        unsigned long num = strtoul(&ptr[3], &endp, 10);
+        if(*endp != sep)
+          ptr = NULL;
+        else if(num > 0xffff) {
           failf(data, "Illegal port number in EPSV reply");
           return CURLE_FTP_WEIRD_PASV_REPLY;
         }
@@ -1871,8 +1876,7 @@
   else if((ftpc->count1 == 1) &&
           (ftpcode == 227)) {
     /* positive PASV response */
-    unsigned int ip[4] = {0, 0, 0, 0};
-    unsigned int port[2] = {0, 0};
+    unsigned int ip[6];
 
     /*
      * Scan for a sequence of six comma-separated numbers and use them as
@@ -1884,15 +1888,12 @@
      * "227 Entering passive mode. 127,0,0,1,4,51"
      */
     while(*str) {
-      if(6 == sscanf(str, "%u,%u,%u,%u,%u,%u",
-                     &ip[0], &ip[1], &ip[2], &ip[3],
-                     &port[0], &port[1]))
+      if(match_pasv_6nums(str, ip))
         break;
       str++;
     }
 
-    if(!*str || (ip[0] > 255) || (ip[1] > 255)  || (ip[2] > 255)  ||
-       (ip[3] > 255) || (port[0] > 255)  || (port[1] > 255) ) {
+    if(!*str) {
       failf(data, "Couldn't interpret the 227-response");
       return CURLE_FTP_WEIRD_227_FORMAT;
     }
@@ -1912,7 +1913,7 @@
     if(!ftpc->newhost)
       return CURLE_OUT_OF_MEMORY;
 
-    ftpc->newport = (unsigned short)(((port[0]<<8) + port[1]) & 0xffff);
+    ftpc->newport = (unsigned short)(((ip[4]<<8) + ip[5]) & 0xffff);
   }
   else if(ftpc->count1 == 0) {
     /* EPSV failed, move on to PASV */
@@ -1954,7 +1955,7 @@
 
     /* postponed address resolution in case of tcp fastopen */
     if(conn->bits.tcp_fastopen && !conn->bits.reuse && !ftpc->newhost[0]) {
-      Curl_conninfo_remote(data, conn, conn->sock[FIRSTSOCKET]);
+      Curl_conn_ev_update_info(data, conn);
       Curl_safefree(ftpc->newhost);
       ftpc->newhost = strdup(control_address(conn));
       if(!ftpc->newhost)
@@ -2047,6 +2048,30 @@
   return result;
 }
 
+static int twodigit(const char *p)
+{
+  return (p[0]-'0') * 10 + (p[1]-'0');
+}
+
+static bool ftp_213_date(const char *p, int *year, int *month, int *day,
+                         int *hour, int *minute, int *second)
+{
+  size_t len = strlen(p);
+  if(len < 14)
+    return FALSE;
+  *year = twodigit(&p[0]) * 100 + twodigit(&p[2]);
+  *month = twodigit(&p[4]);
+  *day = twodigit(&p[6]);
+  *hour = twodigit(&p[8]);
+  *minute = twodigit(&p[10]);
+  *second = twodigit(&p[12]);
+
+  if((*month > 12) || (*day > 31) || (*hour > 23) || (*minute > 59) ||
+     (*second > 60))
+    return FALSE;
+  return TRUE;
+}
+
 static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data,
                                     int ftpcode)
 {
@@ -2061,8 +2086,8 @@
       /* we got a time. Format should be: "YYYYMMDDHHMMSS[.sss]" where the
          last .sss part is optional and means fractions of a second */
       int year, month, day, hour, minute, second;
-      if(6 == sscanf(&data->state.buffer[4], "%04d%02d%02d%02d%02d%02d",
-                     &year, &month, &day, &hour, &minute, &second)) {
+      if(ftp_213_date(&data->state.buffer[4],
+                      &year, &month, &day, &hour, &minute, &second)) {
         /* we have a time, reformat it */
         char timebuf[24];
         msnprintf(timebuf, sizeof(timebuf),
@@ -2569,13 +2594,11 @@
 
 /* for USER and PASS responses */
 static CURLcode ftp_state_user_resp(struct Curl_easy *data,
-                                    int ftpcode,
-                                    ftpstate instate)
+                                    int ftpcode)
 {
   CURLcode result = CURLE_OK;
   struct connectdata *conn = data->conn;
   struct ftp_conn *ftpc = &conn->proto.ftpc;
-  (void)instate; /* no use for this yet */
 
   /* some need password anyway, and others just return 2xx ignored */
   if((ftpcode == 331) && (ftpc->state == FTP_USER)) {
@@ -2652,7 +2675,7 @@
   int ftpcode;
   struct ftp_conn *ftpc = &conn->proto.ftpc;
   struct pingpong *pp = &ftpc->pp;
-  static const char ftpauth[][4]  = { "SSL", "TLS" };
+  static const char * const ftpauth[] = { "SSL", "TLS" };
   size_t nread = 0;
 
   if(pp->sendleft)
@@ -2670,7 +2693,7 @@
         /* 230 User logged in - already! Take as 220 if TLS required. */
         if(data->set.use_ssl <= CURLUSESSL_TRY ||
            conn->bits.ftp_use_control_ssl)
-          return ftp_state_user_resp(data, ftpcode, ftpc->state);
+          return ftp_state_user_resp(data, ftpcode);
       }
       else if(ftpcode != 220) {
         failf(data, "Got a %03d ftp-server response when 220 was expected",
@@ -2742,7 +2765,7 @@
       if((ftpcode == 234) || (ftpcode == 334)) {
         /* this was BLOCKING, keep it so for now */
         bool done;
-        if(!Curl_conn_is_ssl(data, FIRSTSOCKET)) {
+        if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
           result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET);
           if(result) {
             /* we failed and bail out */
@@ -2775,7 +2798,7 @@
 
     case FTP_USER:
     case FTP_PASS:
-      result = ftp_state_user_resp(data, ftpcode, ftpc->state);
+      result = ftp_state_user_resp(data, ftpcode);
       break;
 
     case FTP_ACCT:
@@ -3238,7 +3261,7 @@
   if(data->state.wildcardmatch) {
     if(data->set.chunk_end && ftpc->file) {
       Curl_set_in_callback(data, true);
-      data->set.chunk_end(data->wildcard.customptr);
+      data->set.chunk_end(data->set.wildcardptr);
       Curl_set_in_callback(data, false);
     }
     ftpc->known_filesize = -1;
@@ -3745,7 +3768,7 @@
   char *last_slash;
   struct FTP *ftp = data->req.p.ftp;
   char *path = ftp->path;
-  struct WildcardData *wildcard = &(data->wildcard);
+  struct WildcardData *wildcard = data->wildcard;
   CURLcode result = CURLE_OK;
   struct ftp_wc *ftpwc = NULL;
 
@@ -3793,7 +3816,7 @@
     goto fail;
   }
 
-  wildcard->protdata = ftpwc; /* put it to the WildcardData tmp pointer */
+  wildcard->ftpwc = ftpwc; /* put it to the WildcardData tmp pointer */
   wildcard->dtor = wc_data_dtor;
 
   /* wildcard does not support NOCWD option (assert it?) */
@@ -3831,13 +3854,13 @@
   }
   Curl_safefree(wildcard->pattern);
   wildcard->dtor = ZERO_NULL;
-  wildcard->protdata = NULL;
+  wildcard->ftpwc = NULL;
   return result;
 }
 
 static CURLcode wc_statemach(struct Curl_easy *data)
 {
-  struct WildcardData * const wildcard = &(data->wildcard);
+  struct WildcardData * const wildcard = data->wildcard;
   struct connectdata *conn = data->conn;
   CURLcode result = CURLE_OK;
 
@@ -3854,7 +3877,7 @@
     case CURLWC_MATCHING: {
       /* In this state is LIST response successfully parsed, so lets restore
          previous WRITEFUNCTION callback and WRITEDATA pointer */
-      struct ftp_wc *ftpwc = wildcard->protdata;
+      struct ftp_wc *ftpwc = wildcard->ftpwc;
       data->set.fwrite_func = ftpwc->backup.write_function;
       data->set.out = ftpwc->backup.file_descriptor;
       ftpwc->backup.write_function = ZERO_NULL;
@@ -3893,7 +3916,7 @@
         long userresponse;
         Curl_set_in_callback(data, true);
         userresponse = data->set.chunk_bgn(
-          finfo, wildcard->customptr, (int)wildcard->filelist.size);
+          finfo, data->set.wildcardptr, (int)wildcard->filelist.size);
         Curl_set_in_callback(data, false);
         switch(userresponse) {
         case CURL_CHUNK_BGN_FUNC_SKIP:
@@ -3933,7 +3956,7 @@
     case CURLWC_SKIP: {
       if(data->set.chunk_end) {
         Curl_set_in_callback(data, true);
-        data->set.chunk_end(data->wildcard.customptr);
+        data->set.chunk_end(data->set.wildcardptr);
         Curl_set_in_callback(data, false);
       }
       Curl_llist_remove(&wildcard->filelist, wildcard->filelist.head, NULL);
@@ -3943,7 +3966,7 @@
     }
 
     case CURLWC_CLEAN: {
-      struct ftp_wc *ftpwc = wildcard->protdata;
+      struct ftp_wc *ftpwc = wildcard->ftpwc;
       result = CURLE_OK;
       if(ftpwc)
         result = Curl_ftp_parselist_geterror(ftpwc->parser);
@@ -3956,7 +3979,7 @@
     case CURLWC_ERROR:
     case CURLWC_CLEAR:
       if(wildcard->dtor)
-        wildcard->dtor(wildcard->protdata);
+        wildcard->dtor(wildcard->ftpwc);
       return result;
     }
   }
@@ -3983,8 +4006,8 @@
 
   if(data->state.wildcardmatch) {
     result = wc_statemach(data);
-    if(data->wildcard.state == CURLWC_SKIP ||
-      data->wildcard.state == CURLWC_DONE) {
+    if(data->wildcard->state == CURLWC_SKIP ||
+       data->wildcard->state == CURLWC_DONE) {
       /* do not call ftp_regular_transfer */
       return CURLE_OK;
     }
@@ -4070,6 +4093,8 @@
   }
 
   freedirs(ftpc);
+  Curl_safefree(ftpc->account);
+  Curl_safefree(ftpc->alternative_to_user);
   Curl_safefree(ftpc->prevpath);
   Curl_safefree(ftpc->server_os);
   Curl_pp_disconnect(pp);
@@ -4339,11 +4364,31 @@
   char *type;
   struct FTP *ftp;
   CURLcode result = CURLE_OK;
+  struct ftp_conn *ftpc = &conn->proto.ftpc;
 
-  data->req.p.ftp = ftp = calloc(sizeof(struct FTP), 1);
+  ftp = calloc(sizeof(struct FTP), 1);
   if(!ftp)
     return CURLE_OUT_OF_MEMORY;
 
+  /* clone connection related data that is FTP specific */
+  if(data->set.str[STRING_FTP_ACCOUNT]) {
+    ftpc->account = strdup(data->set.str[STRING_FTP_ACCOUNT]);
+    if(!ftpc->account) {
+      free(ftp);
+      return CURLE_OUT_OF_MEMORY;
+    }
+  }
+  if(data->set.str[STRING_FTP_ALTERNATIVE_TO_USER]) {
+    ftpc->alternative_to_user =
+      strdup(data->set.str[STRING_FTP_ALTERNATIVE_TO_USER]);
+    if(!ftpc->alternative_to_user) {
+      Curl_safefree(ftpc->account);
+      free(ftp);
+      return CURLE_OUT_OF_MEMORY;
+    }
+  }
+  data->req.p.ftp = ftp;
+
   ftp->path = &data->state.up.path[1]; /* don't include the initial slash */
 
   /* FTP URLs support an extension like ";type=<typecode>" that
@@ -4378,7 +4423,9 @@
   /* get some initial data into the ftp struct */
   ftp->transfer = PPTRANSFER_BODY;
   ftp->downloadsize = 0;
-  conn->proto.ftpc.known_filesize = -1; /* unknown size for now */
+  ftpc->known_filesize = -1; /* unknown size for now */
+  ftpc->use_ssl = data->set.use_ssl;
+  ftpc->ccc = data->set.ftp_ccc;
 
   return result;
 }
diff --git a/Utilities/cmcurl/lib/ftp.h b/Utilities/cmcurl/lib/ftp.h
index 7f6f432..977fc88 100644
--- a/Utilities/cmcurl/lib/ftp.h
+++ b/Utilities/cmcurl/lib/ftp.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -42,7 +42,7 @@
 /****************************************************************************
  * FTP unique setup
  ***************************************************************************/
-typedef enum {
+enum {
   FTP_STOP,    /* do nothing state, stops the state machine */
   FTP_WAIT220, /* waiting for the initial 220 response immediately after
                   a connect */
@@ -80,7 +80,8 @@
   FTP_STOR, /* generic state for STOR and APPE */
   FTP_QUIT,
   FTP_LAST  /* never used */
-} ftpstate;
+};
+typedef unsigned char ftpstate; /* use the enum values */
 
 struct ftp_parselist_data; /* defined later in ftplistparser.c */
 
@@ -119,41 +120,46 @@
    struct */
 struct ftp_conn {
   struct pingpong pp;
+  char *account;
+  char *alternative_to_user;
   char *entrypath; /* the PWD reply when we logged on */
   char *file;    /* url-decoded file name (or path) */
   char **dirs;   /* realloc()ed array for path components */
-  int dirdepth;  /* number of entries used in the 'dirs' array */
-  bool dont_check;  /* Set to TRUE to prevent the final (post-transfer)
-                       file size and 226/250 status check. It should still
-                       read the line, just ignore the result. */
-  bool ctl_valid;   /* Tells Curl_ftp_quit() whether or not to do anything. If
-                       the connection has timed out or been closed, this
-                       should be FALSE when it gets to Curl_ftp_quit() */
-  bool cwddone;     /* if it has been determined that the proper CWD combo
-                       already has been done */
-  int cwdcount;     /* number of CWD commands issued */
-  bool cwdfail;     /* set TRUE if a CWD command fails, as then we must prevent
-                       caching the current directory */
-  bool wait_data_conn; /* this is set TRUE if data connection is waited */
-  /* newhost is the (allocated) IP addr or host name to connect the data
-     connection to */
-  unsigned short newport;
   char *newhost;
   char *prevpath;   /* url-decoded conn->path from the previous transfer */
   char transfertype; /* set by ftp_transfertype for use by Curl_client_write()a
                         and others (A/I or zero) */
-  int count1; /* general purpose counter for the state machine */
-  int count2; /* general purpose counter for the state machine */
-  int count3; /* general purpose counter for the state machine */
-  ftpstate state; /* always use ftp.c:state() to change state! */
-  ftpstate state_saved; /* transfer type saved to be reloaded after
-                           data connection is established */
   curl_off_t retr_size_saved; /* Size of retrieved file saved */
   char *server_os;     /* The target server operating system. */
   curl_off_t known_filesize; /* file size is different from -1, if wildcard
                                 LIST parsing was done and wc_statemach set
                                 it */
+  int dirdepth;  /* number of entries used in the 'dirs' array */
+  int cwdcount;     /* number of CWD commands issued */
+  int count1; /* general purpose counter for the state machine */
+  int count2; /* general purpose counter for the state machine */
+  int count3; /* general purpose counter for the state machine */
+  /* newhost is the (allocated) IP addr or host name to connect the data
+     connection to */
+  unsigned short newport;
+  ftpstate state; /* always use ftp.c:state() to change state! */
+  ftpstate state_saved; /* transfer type saved to be reloaded after data
+                           connection is established */
+  unsigned char use_ssl;   /* if AUTH TLS is to be attempted etc, for FTP or
+                              IMAP or POP3 or others! (type: curl_usessl)*/
+  unsigned char ccc;       /* ccc level for this connection */
   BIT(ftp_trying_alternative);
+  BIT(dont_check);  /* Set to TRUE to prevent the final (post-transfer)
+                       file size and 226/250 status check. It should still
+                       read the line, just ignore the result. */
+  BIT(ctl_valid);   /* Tells Curl_ftp_quit() whether or not to do anything. If
+                       the connection has timed out or been closed, this
+                       should be FALSE when it gets to Curl_ftp_quit() */
+  BIT(cwddone);     /* if it has been determined that the proper CWD combo
+                       already has been done */
+  BIT(cwdfail);     /* set TRUE if a CWD command fails, as then we must prevent
+                       caching the current directory */
+  BIT(wait_data_conn); /* this is set TRUE if data connection is waited */
 };
 
 #define DEFAULT_ACCEPT_TIMEOUT   60000 /* milliseconds == one minute */
diff --git a/Utilities/cmcurl/lib/ftplistparser.c b/Utilities/cmcurl/lib/ftplistparser.c
index 3d529ef..39001e3 100644
--- a/Utilities/cmcurl/lib/ftplistparser.c
+++ b/Utilities/cmcurl/lib/ftplistparser.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -181,6 +181,43 @@
   } offsets;
 };
 
+static void fileinfo_dtor(void *user, void *element)
+{
+  (void)user;
+  Curl_fileinfo_cleanup(element);
+}
+
+CURLcode Curl_wildcard_init(struct WildcardData *wc)
+{
+  Curl_llist_init(&wc->filelist, fileinfo_dtor);
+  wc->state = CURLWC_INIT;
+
+  return CURLE_OK;
+}
+
+void Curl_wildcard_dtor(struct WildcardData **wcp)
+{
+  struct WildcardData *wc = *wcp;
+  if(!wc)
+    return;
+
+  if(wc->dtor) {
+    wc->dtor(wc->ftpwc);
+    wc->dtor = ZERO_NULL;
+    wc->ftpwc = NULL;
+  }
+  DEBUGASSERT(wc->ftpwc == NULL);
+
+  Curl_llist_destroy(&wc->filelist, NULL);
+  free(wc->path);
+  wc->path = NULL;
+  free(wc->pattern);
+  wc->pattern = NULL;
+  wc->state = CURLWC_INIT;
+  free(wc);
+  *wcp = NULL;
+}
+
 struct ftp_parselist_data *Curl_ftp_parselist_data_alloc(void)
 {
   return calloc(1, sizeof(struct ftp_parselist_data));
@@ -274,8 +311,8 @@
                                     struct fileinfo *infop)
 {
   curl_fnmatch_callback compare;
-  struct WildcardData *wc = &data->wildcard;
-  struct ftp_wc *ftpwc = wc->protdata;
+  struct WildcardData *wc = data->wildcard;
+  struct ftp_wc *ftpwc = wc->ftpwc;
   struct Curl_llist *llist = &wc->filelist;
   struct ftp_parselist_data *parser = ftpwc->parser;
   bool add = TRUE;
@@ -330,7 +367,7 @@
 {
   size_t bufflen = size*nmemb;
   struct Curl_easy *data = (struct Curl_easy *)connptr;
-  struct ftp_wc *ftpwc = data->wildcard.protdata;
+  struct ftp_wc *ftpwc = data->wildcard->ftpwc;
   struct ftp_parselist_data *parser = ftpwc->parser;
   struct fileinfo *infop;
   struct curl_fileinfo *finfo;
diff --git a/Utilities/cmcurl/lib/ftplistparser.h b/Utilities/cmcurl/lib/ftplistparser.h
index 0a80543..5ba1f6a 100644
--- a/Utilities/cmcurl/lib/ftplistparser.h
+++ b/Utilities/cmcurl/lib/ftplistparser.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -39,5 +39,39 @@
 
 void Curl_ftp_parselist_data_free(struct ftp_parselist_data **pl_data);
 
+/* list of wildcard process states */
+typedef enum {
+  CURLWC_CLEAR = 0,
+  CURLWC_INIT = 1,
+  CURLWC_MATCHING, /* library is trying to get list of addresses for
+                      downloading */
+  CURLWC_DOWNLOADING,
+  CURLWC_CLEAN, /* deallocate resources and reset settings */
+  CURLWC_SKIP,  /* skip over concrete file */
+  CURLWC_ERROR, /* error cases */
+  CURLWC_DONE   /* if is wildcard->state == CURLWC_DONE wildcard loop
+                   will end */
+} wildcard_states;
+
+typedef void (*wildcard_dtor)(void *ptr);
+
+/* struct keeping information about wildcard download process */
+struct WildcardData {
+  char *path; /* path to the directory, where we trying wildcard-match */
+  char *pattern; /* wildcard pattern */
+  struct Curl_llist filelist; /* llist with struct Curl_fileinfo */
+  struct ftp_wc *ftpwc; /* pointer to FTP wildcard data */
+  wildcard_dtor dtor;
+  unsigned char state; /* wildcard_states */
+};
+
+CURLcode Curl_wildcard_init(struct WildcardData *wc);
+void Curl_wildcard_dtor(struct WildcardData **wcp);
+
+struct Curl_easy;
+
+#else
+/* FTP is disabled */
+#define Curl_wildcard_dtor(x)
 #endif /* CURL_DISABLE_FTP */
 #endif /* HEADER_CURL_FTPLISTPARSER_H */
diff --git a/Utilities/cmcurl/lib/functypes.h b/Utilities/cmcurl/lib/functypes.h
index 8891b1d..075c02e 100644
--- a/Utilities/cmcurl/lib/functypes.h
+++ b/Utilities/cmcurl/lib/functypes.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/getenv.c b/Utilities/cmcurl/lib/getenv.c
index 5f00fd1..8069784 100644
--- a/Utilities/cmcurl/lib/getenv.c
+++ b/Utilities/cmcurl/lib/getenv.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/getinfo.c b/Utilities/cmcurl/lib/getinfo.c
index 3a24c65..826ffd0 100644
--- a/Utilities/cmcurl/lib/getinfo.c
+++ b/Utilities/cmcurl/lib/getinfo.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/getinfo.h b/Utilities/cmcurl/lib/getinfo.h
index 1b5e8c2..56bb440 100644
--- a/Utilities/cmcurl/lib/getinfo.h
+++ b/Utilities/cmcurl/lib/getinfo.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/gopher.c b/Utilities/cmcurl/lib/gopher.c
index 6fbb7de..4a11d93 100644
--- a/Utilities/cmcurl/lib/gopher.c
+++ b/Utilities/cmcurl/lib/gopher.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/gopher.h b/Utilities/cmcurl/lib/gopher.h
index 4ea269d..9e3365b 100644
--- a/Utilities/cmcurl/lib/gopher.h
+++ b/Utilities/cmcurl/lib/gopher.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/h2h3.c b/Utilities/cmcurl/lib/h2h3.c
index 3a9288d..3b21699 100644
--- a/Utilities/cmcurl/lib/h2h3.c
+++ b/Utilities/cmcurl/lib/h2h3.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -118,6 +118,7 @@
 CURLcode Curl_pseudo_headers(struct Curl_easy *data,
                              const char *mem, /* the request */
                              const size_t len /* size of request */,
+                             size_t* hdrlen /* opt size of headers read */,
                              struct h2h3req **hp)
 {
   struct connectdata *conn = data->conn;
@@ -291,6 +292,12 @@
     }
   }
 
+  if(hdrlen) {
+    /* Skip trailing CRLF */
+    end += 4;
+    *hdrlen = end - mem;
+  }
+
   hreq->entries = nheader;
   *hp = hreq;
 
diff --git a/Utilities/cmcurl/lib/h2h3.h b/Utilities/cmcurl/lib/h2h3.h
index c35b706..396c12c 100644
--- a/Utilities/cmcurl/lib/h2h3.h
+++ b/Utilities/cmcurl/lib/h2h3.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -51,6 +51,7 @@
 CURLcode Curl_pseudo_headers(struct Curl_easy *data,
                              const char *request,
                              const size_t len,
+                             size_t* hdrlen /* optional */,
                              struct h2h3req **hp);
 
 /*
diff --git a/Utilities/cmcurl/lib/hash.c b/Utilities/cmcurl/lib/hash.c
index b6a2a33..06ce92c 100644
--- a/Utilities/cmcurl/lib/hash.c
+++ b/Utilities/cmcurl/lib/hash.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/hash.h b/Utilities/cmcurl/lib/hash.h
index 5b59bf1..9cfffc2 100644
--- a/Utilities/cmcurl/lib/hash.h
+++ b/Utilities/cmcurl/lib/hash.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/headers.c b/Utilities/cmcurl/lib/headers.c
index 978c918..6cd7e31 100644
--- a/Utilities/cmcurl/lib/headers.c
+++ b/Utilities/cmcurl/lib/headers.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -38,14 +38,13 @@
 
 /* Generate the curl_header struct for the user. This function MUST assign all
    struct fields in the output struct. */
-static void copy_header_external(struct Curl_easy *data,
-                                 struct Curl_header_store *hs,
+static void copy_header_external(struct Curl_header_store *hs,
                                  size_t index,
                                  size_t amount,
                                  struct Curl_llist_element *e,
-                                 struct curl_header **hout)
+                                 struct curl_header *hout)
 {
-  struct curl_header *h = *hout = &data->state.headerout;
+  struct curl_header *h = hout;
   h->name = hs->name;
   h->value = hs->value;
   h->amount = amount;
@@ -118,7 +117,9 @@
       return CURLHE_MISSING;
   }
   /* this is the name we want */
-  copy_header_external(data, hs, nameindex, amount, e_pick, hout);
+  copy_header_external(hs, nameindex, amount, e_pick,
+                       &data->state.headerout[0]);
+  *hout = &data->state.headerout[0];
   return CURLHE_OK;
 }
 
@@ -132,7 +133,6 @@
   struct Curl_llist_element *pick;
   struct Curl_llist_element *e;
   struct Curl_header_store *hs;
-  struct curl_header *hout;
   size_t amount = 0;
   size_t index = 0;
 
@@ -179,8 +179,9 @@
       index = amount - 1;
   }
 
-  copy_header_external(data, hs, index, amount, pick, &hout);
-  return hout;
+  copy_header_external(hs, index, amount, pick,
+                       &data->state.headerout[1]);
+  return &data->state.headerout[1];
 }
 
 static CURLcode namevalue(char *header, size_t hlen, unsigned int type,
diff --git a/Utilities/cmcurl/lib/headers.h b/Utilities/cmcurl/lib/headers.h
index 96332db..a5229ea 100644
--- a/Utilities/cmcurl/lib/headers.h
+++ b/Utilities/cmcurl/lib/headers.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/hmac.c b/Utilities/cmcurl/lib/hmac.c
index dfb0db5..8d8de17 100644
--- a/Utilities/cmcurl/lib/hmac.c
+++ b/Utilities/cmcurl/lib/hmac.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/hostasyn.c b/Utilities/cmcurl/lib/hostasyn.c
index df50d13..2f6762c 100644
--- a/Utilities/cmcurl/lib/hostasyn.c
+++ b/Utilities/cmcurl/lib/hostasyn.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -78,7 +78,7 @@
         Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
 
       dns = Curl_cache_addr(data, ai,
-                            data->state.async.hostname,
+                            data->state.async.hostname, 0,
                             data->state.async.port);
       if(data->share)
         Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
diff --git a/Utilities/cmcurl/lib/hostip.c b/Utilities/cmcurl/lib/hostip.c
index dd427a2..d0dc2e8 100644
--- a/Utilities/cmcurl/lib/hostip.c
+++ b/Utilities/cmcurl/lib/hostip.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -167,18 +167,25 @@
 
 /*
  * Create a hostcache id string for the provided host + port, to be used by
- * the DNS caching. Without alloc.
+ * the DNS caching. Without alloc. Return length of the id string.
  */
-static void
-create_hostcache_id(const char *name, int port, char *ptr, size_t buflen)
+static size_t
+create_hostcache_id(const char *name,
+                    size_t nlen, /* 0 or actual name length */
+                    int port, char *ptr, size_t buflen)
 {
-  size_t len = strlen(name);
+  size_t len = nlen ? nlen : strlen(name);
+  size_t olen = 0;
+  DEBUGASSERT(buflen >= MAX_HOSTCACHE_LEN);
   if(len > (buflen - 7))
     len = buflen - 7;
   /* store and lower case the name */
-  while(len--)
+  while(len--) {
     *ptr++ = Curl_raw_tolower(*name++);
-  msnprintf(ptr, 7, ":%u", port);
+    olen++;
+  }
+  olen += msnprintf(ptr, 7, ":%u", port);
+  return olen;
 }
 
 struct hostcache_prune_data {
@@ -260,20 +267,18 @@
                                          int port)
 {
   struct Curl_dns_entry *dns = NULL;
-  size_t entry_len;
   char entry_id[MAX_HOSTCACHE_LEN];
 
   /* Create an entry id, based upon the hostname and port */
-  create_hostcache_id(hostname, port, entry_id, sizeof(entry_id));
-  entry_len = strlen(entry_id);
+  size_t entry_len = create_hostcache_id(hostname, 0, port,
+                                         entry_id, sizeof(entry_id));
 
   /* See if its already in our dns cache */
   dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len + 1);
 
   /* No entry found in cache, check if we might have a wildcard entry */
   if(!dns && data->state.wildcard_resolve) {
-    create_hostcache_id("*", port, entry_id, sizeof(entry_id));
-    entry_len = strlen(entry_id);
+    entry_len = create_hostcache_id("*", 1, port, entry_id, sizeof(entry_id));
 
     /* See if it's already in our dns cache */
     dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len + 1);
@@ -438,6 +443,7 @@
 Curl_cache_addr(struct Curl_easy *data,
                 struct Curl_addrinfo *addr,
                 const char *hostname,
+                size_t hostlen, /* length or zero */
                 int port)
 {
   char entry_id[MAX_HOSTCACHE_LEN];
@@ -461,8 +467,8 @@
   }
 
   /* Create an entry id, based upon the hostname and port */
-  create_hostcache_id(hostname, port, entry_id, sizeof(entry_id));
-  entry_len = strlen(entry_id);
+  entry_len = create_hostcache_id(hostname, hostlen, port,
+                                  entry_id, sizeof(entry_id));
 
   dns->inuse = 1;   /* the cache has the first reference */
   dns->addr = addr; /* this is the address(es) */
@@ -791,7 +797,7 @@
         Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
 
       /* we got a response, store it in the cache */
-      dns = Curl_cache_addr(data, addr, hostname, port);
+      dns = Curl_cache_addr(data, addr, hostname, 0, port);
 
       if(data->share)
         Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
@@ -1059,8 +1065,7 @@
 CURLcode Curl_loadhostpairs(struct Curl_easy *data)
 {
   struct curl_slist *hostp;
-  char hostname[256];
-  int port = 0;
+  char *host_end;
 
   /* Default is no wildcard found */
   data->state.wildcard_resolve = false;
@@ -1070,18 +1075,25 @@
     if(!hostp->data)
       continue;
     if(hostp->data[0] == '-') {
+      unsigned long num = 0;
       size_t entry_len;
+      size_t hlen = 0;
+      host_end = strchr(&hostp->data[1], ':');
 
-      if(2 != sscanf(hostp->data + 1, "%255[^:]:%d", hostname, &port)) {
-        infof(data, "Couldn't parse CURLOPT_RESOLVE removal entry '%s'",
+      if(host_end) {
+        hlen = host_end - &hostp->data[1];
+        num = strtoul(++host_end, NULL, 10);
+        if(!hlen || (num > 0xffff))
+          host_end = NULL;
+      }
+      if(!host_end) {
+        infof(data, "Bad syntax CURLOPT_RESOLVE removal entry '%s'",
               hostp->data);
         continue;
       }
-
       /* Create an entry id, based upon the hostname and port */
-      create_hostcache_id(hostname, port, entry_id, sizeof(entry_id));
-      entry_len = strlen(entry_id);
-
+      entry_len = create_hostcache_id(&hostp->data[1], hlen, (int)num,
+                                      entry_id, sizeof(entry_id));
       if(data->share)
         Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
 
@@ -1102,25 +1114,22 @@
       char *addr_begin;
       char *addr_end;
       char *port_ptr;
+      int port = 0;
       char *end_ptr;
       bool permanent = TRUE;
-      char *host_begin;
-      char *host_end;
       unsigned long tmp_port;
       bool error = true;
+      char *host_begin = hostp->data;
+      size_t hlen = 0;
 
-      host_begin = hostp->data;
       if(host_begin[0] == '+') {
         host_begin++;
         permanent = FALSE;
       }
       host_end = strchr(host_begin, ':');
-      if(!host_end ||
-         ((host_end - host_begin) >= (ptrdiff_t)sizeof(hostname)))
+      if(!host_end)
         goto err;
-
-      memcpy(hostname, host_begin, host_end - host_begin);
-      hostname[host_end - host_begin] = '\0';
+      hlen = host_end - host_begin;
 
       port_ptr = host_end + 1;
       tmp_port = strtoul(port_ptr, &end_ptr, 10);
@@ -1196,8 +1205,8 @@
       }
 
       /* Create an entry id, based upon the hostname and port */
-      create_hostcache_id(hostname, port, entry_id, sizeof(entry_id));
-      entry_len = strlen(entry_id);
+      entry_len = create_hostcache_id(host_begin, hlen, port,
+                                      entry_id, sizeof(entry_id));
 
       if(data->share)
         Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
@@ -1206,8 +1215,8 @@
       dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len + 1);
 
       if(dns) {
-        infof(data, "RESOLVE %s:%d is - old addresses discarded",
-              hostname, port);
+        infof(data, "RESOLVE %.*s:%d is - old addresses discarded",
+              (int)hlen, host_begin, port);
         /* delete old entry, there are two reasons for this
          1. old entry may have different addresses.
          2. even if entry with correct addresses is already in the cache,
@@ -1223,7 +1232,7 @@
       }
 
       /* put this new host in the cache */
-      dns = Curl_cache_addr(data, head, hostname, port);
+      dns = Curl_cache_addr(data, head, host_begin, hlen, port);
       if(dns) {
         if(permanent)
           dns->timestamp = 0; /* mark as permanent */
@@ -1239,13 +1248,13 @@
         Curl_freeaddrinfo(head);
         return CURLE_OUT_OF_MEMORY;
       }
-      infof(data, "Added %s:%d:%s to DNS cache%s",
-            hostname, port, addresses, permanent ? "" : " (non-permanent)");
+      infof(data, "Added %.*s:%d:%s to DNS cache%s",
+            (int)hlen, host_begin, port, addresses,
+            permanent ? "" : " (non-permanent)");
 
       /* Wildcard hostname */
-      if(hostname[0] == '*' && hostname[1] == '\0') {
-        infof(data, "RESOLVE %s:%d is wildcard, enabling wildcard checks",
-              hostname, port);
+      if((hlen == 1) && (host_begin[0] == '*')) {
+        infof(data, "RESOLVE *:%d using wildcard", port);
         data->state.wildcard_resolve = true;
       }
     }
diff --git a/Utilities/cmcurl/lib/hostip.h b/Utilities/cmcurl/lib/hostip.h
index 3b1d814..4b5481f 100644
--- a/Utilities/cmcurl/lib/hostip.h
+++ b/Utilities/cmcurl/lib/hostip.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -178,7 +178,7 @@
  */
 struct Curl_dns_entry *
 Curl_cache_addr(struct Curl_easy *data, struct Curl_addrinfo *addr,
-                const char *hostname, int port);
+                const char *hostname, size_t hostlen, int port);
 
 #ifndef INADDR_NONE
 #define CURL_INADDR_NONE (in_addr_t) ~0
diff --git a/Utilities/cmcurl/lib/hostip4.c b/Utilities/cmcurl/lib/hostip4.c
index 109bd1e..9140180 100644
--- a/Utilities/cmcurl/lib/hostip4.c
+++ b/Utilities/cmcurl/lib/hostip4.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/hostip6.c b/Utilities/cmcurl/lib/hostip6.c
index af8bc23..6b0ba55 100644
--- a/Utilities/cmcurl/lib/hostip6.c
+++ b/Utilities/cmcurl/lib/hostip6.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/hostsyn.c b/Utilities/cmcurl/lib/hostsyn.c
index 73d1e50..ca8b075 100644
--- a/Utilities/cmcurl/lib/hostsyn.c
+++ b/Utilities/cmcurl/lib/hostsyn.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/hsts.c b/Utilities/cmcurl/lib/hsts.c
index c449120..64cbae1 100644
--- a/Utilities/cmcurl/lib/hsts.c
+++ b/Utilities/cmcurl/lib/hsts.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2020 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -39,6 +39,7 @@
 #include "parsedate.h"
 #include "fopen.h"
 #include "rename.h"
+#include "share.h"
 
 /* The last 3 #include files should be in this order */
 #include "curl_printf.h"
@@ -425,14 +426,23 @@
   if(2 == rc) {
     time_t expires = strcmp(date, UNLIMITED) ? Curl_getdate_capped(date) :
       TIME_T_MAX;
-    CURLcode result;
+    CURLcode result = CURLE_OK;
     char *p = host;
     bool subdomain = FALSE;
+    struct stsentry *e;
     if(p[0] == '.') {
       p++;
       subdomain = TRUE;
     }
-    result = hsts_create(h, p, subdomain, expires);
+    /* only add it if not already present */
+    e = Curl_hsts(h, p, subdomain);
+    if(!e)
+      result = hsts_create(h, p, subdomain, expires);
+    else {
+      /* the same host name, use the largest expire time */
+      if(expires > e->expires)
+        e->expires = expires;
+    }
     if(result)
       return result;
   }
@@ -551,4 +561,18 @@
   return CURLE_OK;
 }
 
+void Curl_hsts_loadfiles(struct Curl_easy *data)
+{
+  struct curl_slist *l = data->set.hstslist;
+  if(l) {
+    Curl_share_lock(data, CURL_LOCK_DATA_HSTS, CURL_LOCK_ACCESS_SINGLE);
+
+    while(l) {
+      (void)Curl_hsts_loadfile(data, data->hsts, l->data);
+      l = l->next;
+    }
+    Curl_share_unlock(data, CURL_LOCK_DATA_HSTS);
+  }
+}
+
 #endif /* CURL_DISABLE_HTTP || CURL_DISABLE_HSTS */
diff --git a/Utilities/cmcurl/lib/hsts.h b/Utilities/cmcurl/lib/hsts.h
index 0e36a77..d3431a5 100644
--- a/Utilities/cmcurl/lib/hsts.h
+++ b/Utilities/cmcurl/lib/hsts.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2020 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -59,9 +59,11 @@
                             struct hsts *h, const char *file);
 CURLcode Curl_hsts_loadcb(struct Curl_easy *data,
                           struct hsts *h);
+void Curl_hsts_loadfiles(struct Curl_easy *data);
 #else
 #define Curl_hsts_cleanup(x)
 #define Curl_hsts_loadcb(x,y) CURLE_OK
 #define Curl_hsts_save(x,y,z)
+#define Curl_hsts_loadfiles(x)
 #endif /* CURL_DISABLE_HTTP || CURL_DISABLE_HSTS */
 #endif /* HEADER_CURL_HSTS_H */
diff --git a/Utilities/cmcurl/lib/http.c b/Utilities/cmcurl/lib/http.c
index 1b75022..faa486c 100644
--- a/Utilities/cmcurl/lib/http.c
+++ b/Utilities/cmcurl/lib/http.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -62,6 +62,7 @@
 #include "cookie.h"
 #include "vauth/vauth.h"
 #include "vtls/vtls.h"
+#include "vquic/vquic.h"
 #include "http_digest.h"
 #include "http_ntlm.h"
 #include "curl_ntlm_wb.h"
@@ -87,6 +88,7 @@
 #include "hsts.h"
 #include "ws.h"
 #include "c-hyper.h"
+#include "curl_ctype.h"
 
 /* The last 3 #include files should be in this order */
 #include "curl_printf.h"
@@ -150,7 +152,7 @@
   http_getsock_do,                      /* doing_getsock */
   ZERO_NULL,                            /* domore_getsock */
   ZERO_NULL,                            /* perform_getsock */
-  ZERO_NULL,                            /* disconnect */
+  Curl_ws_disconnect,                   /* disconnect */
   ZERO_NULL,                            /* readwrite */
   ZERO_NULL,                            /* connection_check */
   ZERO_NULL,                            /* attach connection */
@@ -204,7 +206,7 @@
   http_getsock_do,                      /* doing_getsock */
   ZERO_NULL,                            /* domore_getsock */
   ZERO_NULL,                            /* perform_getsock */
-  ZERO_NULL,                            /* disconnect */
+  Curl_ws_disconnect,                   /* disconnect */
   ZERO_NULL,                            /* readwrite */
   ZERO_NULL,                            /* connection_check */
   ZERO_NULL,                            /* attach connection */
@@ -218,41 +220,6 @@
 
 #endif
 
-static CURLcode h3_setup_conn(struct Curl_easy *data,
-                              struct connectdata *conn)
-{
-#ifdef ENABLE_QUIC
-  /* We want HTTP/3 directly, setup the filter chain ourself,
-   * overriding the default behaviour. */
-  DEBUGASSERT(conn->transport == TRNSPRT_QUIC);
-
-  if(!(conn->handler->flags & PROTOPT_SSL)) {
-    failf(data, "HTTP/3 requested for non-HTTPS URL");
-    return CURLE_URL_MALFORMAT;
-  }
-#ifndef CURL_DISABLE_PROXY
-  if(conn->bits.socksproxy) {
-    failf(data, "HTTP/3 is not supported over a SOCKS proxy");
-    return CURLE_URL_MALFORMAT;
-  }
-  if(conn->bits.httpproxy && conn->bits.tunnel_proxy) {
-    failf(data, "HTTP/3 is not supported over a HTTP proxy");
-    return CURLE_URL_MALFORMAT;
-  }
-#endif
-
-  DEBUGF(infof(data, "HTTP/3 direct conn setup(conn #%ld, index=%d)",
-         conn->connection_id, FIRSTSOCKET));
-  return Curl_conn_socket_set(data, conn, FIRSTSOCKET);
-
-#else /* ENABLE_QUIC */
-  (void)conn;
-  (void)data;
-  DEBUGF(infof(data, "QUIC is not supported in this build"));
-  return CURLE_NOT_BUILT_IN;
-#endif /* !ENABLE_QUIC */
-}
-
 static CURLcode http_setup_conn(struct Curl_easy *data,
                                 struct connectdata *conn)
 {
@@ -267,20 +234,14 @@
 
   Curl_mime_initpart(&http->form);
   data->req.p.http = http;
+  connkeep(conn, "HTTP default");
 
-  if(data->state.httpwant == CURL_HTTP_VERSION_3) {
-    conn->transport = TRNSPRT_QUIC;
+  if(data->state.httpwant == CURL_HTTP_VERSION_3ONLY) {
+    CURLcode result = Curl_conn_may_http3(data, conn);
+    if(result)
+      return result;
   }
 
-  if(conn->transport == TRNSPRT_QUIC) {
-    return h3_setup_conn(data, conn);
-  }
-  else {
-    if(!CONN_INUSE(conn))
-      /* if not already multi-using, setup connection details */
-      Curl_http2_setup_conn(conn);
-    Curl_http2_setup_req(data);
-  }
   return CURLE_OK;
 }
 
@@ -1256,8 +1217,8 @@
                            size_t nitems,
                            void *userp)
 {
-  struct Curl_easy *data = (struct Curl_easy *)userp;
-  struct HTTP *http = data->req.p.http;
+  struct HTTP *http = (struct HTTP *)userp;
+  struct Curl_easy *data = http->backup.data;
   size_t fullsize = size * nitems;
 
   if(!http->postsize)
@@ -1309,6 +1270,7 @@
  */
 CURLcode Curl_buffer_send(struct dynbuf *in,
                           struct Curl_easy *data,
+                          struct HTTP *http,
                           /* add the number of sent bytes to this
                              counter */
                           curl_off_t *bytes_written,
@@ -1321,14 +1283,13 @@
   char *ptr;
   size_t size;
   struct connectdata *conn = data->conn;
-  struct HTTP *http = data->req.p.http;
   size_t sendsize;
   curl_socket_t sockfd;
   size_t headersize;
 
   DEBUGASSERT(socketindex <= SECONDARYSOCKET);
 
-  sockfd = conn->sock[socketindex];
+  sockfd = Curl_conn_get_socket(data, socketindex);
 
   /* The looping below is required since we use non-blocking sockets, but due
      to the circumstances we will just loop and try again and again etc */
@@ -1456,10 +1417,11 @@
         http->backup.fread_in = data->state.in;
         http->backup.postdata = http->postdata;
         http->backup.postsize = http->postsize;
+        http->backup.data = data;
 
         /* set the new pointers for the request-sending */
         data->state.fread_func = (curl_read_callback)readmoredata;
-        data->state.in = (void *)data;
+        data->state.in = (void *)http;
         http->postdata = ptr;
         http->postsize = (curl_off_t)size;
 
@@ -1468,7 +1430,6 @@
 
         http->send_buffer = *in; /* copy the whole struct */
         http->sending = HTTPSEND_REQUEST;
-
         return CURLE_OK;
       }
       http->sending = HTTPSEND_BODY;
@@ -1579,8 +1540,8 @@
                            curl_socket_t *socks)
 {
   /* write mode */
-  (void)data;
-  socks[0] = conn->sock[FIRSTSOCKET];
+  (void)conn;
+  socks[0] = Curl_conn_get_socket(data, FIRSTSOCKET);
   return GETSOCK_WRITESOCK(0);
 }
 
@@ -1610,8 +1571,6 @@
     return CURLE_OK;
 
   Curl_dyn_free(&http->send_buffer);
-  Curl_http2_done(data, premature);
-  Curl_quic_done(data, premature);
   Curl_mime_cleanpart(&http->form);
   Curl_dyn_reset(&data->state.headerb);
   Curl_hyper_done(data);
@@ -1664,17 +1623,10 @@
 static const char *get_http_string(const struct Curl_easy *data,
                                    const struct connectdata *conn)
 {
-#ifdef ENABLE_QUIC
-  if((data->state.httpwant == CURL_HTTP_VERSION_3) ||
-     (conn->httpversion == 30))
+  if(Curl_conn_is_http3(data, conn, FIRSTSOCKET))
     return "3";
-#endif
-
-#ifdef USE_NGHTTP2
-  if(conn->proto.httpc.h2)
+  if(Curl_conn_is_http2(data, conn, FIRSTSOCKET))
     return "2";
-#endif
-
   if(Curl_use_http_1_1plus(data, conn))
     return "1.1";
 
@@ -2359,7 +2311,7 @@
   curl_off_t included_body = 0;
 #else
   /* from this point down, this function should not be used */
-#define Curl_buffer_send(a,b,c,d,e) CURLE_OK
+#define Curl_buffer_send(a,b,c,d,e,f) CURLE_OK
 #endif
   CURLcode result = CURLE_OK;
   struct HTTP *http = data->req.p.http;
@@ -2388,7 +2340,16 @@
         return result;
     }
 
-    if(http->postsize) {
+    /* For really small puts we don't use Expect: headers at all, and for
+       the somewhat bigger ones we allow the app to disable it. Just make
+       sure that the expect100header is always set to the preferred value
+       here. */
+    ptr = Curl_checkheaders(data, STRCONST("Expect"));
+    if(ptr) {
+      data->state.expect100header =
+        Curl_compareheader(ptr, STRCONST("Expect:"), STRCONST("100-continue"));
+    }
+    else if(http->postsize > EXPECT_100_THRESHOLD || http->postsize < 0) {
       result = expect100(data, conn, r);
       if(result)
         return result;
@@ -2403,7 +2364,8 @@
     Curl_pgrsSetUploadSize(data, http->postsize);
 
     /* this sends the buffer and frees all the buffer resources */
-    result = Curl_buffer_send(r, data, &data->info.request_size, 0,
+    result = Curl_buffer_send(r, data, data->req.p.http,
+                              &data->info.request_size, 0,
                               FIRSTSOCKET);
     if(result)
       failf(data, "Failed sending PUT request");
@@ -2424,7 +2386,8 @@
       if(result)
         return result;
 
-      result = Curl_buffer_send(r, data, &data->info.request_size, 0,
+      result = Curl_buffer_send(r, data, data->req.p.http,
+                                &data->info.request_size, 0,
                                 FIRSTSOCKET);
       if(result)
         failf(data, "Failed sending POST request");
@@ -2440,8 +2403,7 @@
        we don't upload data chunked, as RFC2616 forbids us to set both
        kinds of headers (Transfer-Encoding: chunked and Content-Length) */
     if(http->postsize != -1 && !data->req.upload_chunky &&
-       (conn->bits.authneg ||
-        !Curl_checkheaders(data, STRCONST("Content-Length")))) {
+       (!Curl_checkheaders(data, STRCONST("Content-Length")))) {
       /* we allow replacing this header if not during auth negotiation,
          although it isn't very wise to actually set your own */
       result = Curl_dyn_addf(r,
@@ -2495,7 +2457,8 @@
     http->sending = HTTPSEND_BODY;
 
     /* this sends the buffer and frees all the buffer resources */
-    result = Curl_buffer_send(r, data, &data->info.request_size, 0,
+    result = Curl_buffer_send(r, data, data->req.p.http,
+                              &data->info.request_size, 0,
                               FIRSTSOCKET);
     if(result)
       failf(data, "Failed sending POST request");
@@ -2561,7 +2524,7 @@
 
       /* In HTTP2, we send request body in DATA frame regardless of
          its size. */
-      if(conn->httpversion != 20 &&
+      if(conn->httpversion < 20 &&
          !data->state.expect100header &&
          (http->postsize < MAX_INITIAL_POST_SIZE)) {
         /* if we don't use expect: 100  AND
@@ -2612,11 +2575,10 @@
       else {
         /* A huge POST coming up, do data separate from the request */
         http->postdata = data->set.postfields;
-
         http->sending = HTTPSEND_BODY;
-
+        http->backup.data = data;
         data->state.fread_func = (curl_read_callback)readmoredata;
-        data->state.in = (void *)data;
+        data->state.in = (void *)http;
 
         /* set the upload size to the progress meter */
         Curl_pgrsSetUploadSize(data, http->postsize);
@@ -2655,7 +2617,8 @@
       }
     }
     /* issue the request */
-    result = Curl_buffer_send(r, data, &data->info.request_size, included_body,
+    result = Curl_buffer_send(r, data, data->req.p.http,
+                              &data->info.request_size, included_body,
                               FIRSTSOCKET);
 
     if(result)
@@ -2671,7 +2634,8 @@
       return result;
 
     /* issue the request */
-    result = Curl_buffer_send(r, data, &data->info.request_size, 0,
+    result = Curl_buffer_send(r, data, data->req.p.http,
+                              &data->info.request_size, 0,
                               FIRSTSOCKET);
     if(result)
       failf(data, "Failed sending HTTP request");
@@ -3021,50 +2985,27 @@
      the rest of the request in the PERFORM phase. */
   *done = TRUE;
 
-  if(conn->transport != TRNSPRT_QUIC) {
-    if(conn->httpversion < 20) { /* unless the connection is re-used and
-                                    already http2 */
-      switch(conn->alpn) {
-      case CURL_HTTP_VERSION_2:
-        conn->httpversion = 20; /* we know we're on HTTP/2 now */
-
-        result = Curl_http2_switched(data, NULL, 0);
-        if(result)
-          return result;
-        break;
-      case CURL_HTTP_VERSION_1_1:
-        /* continue with HTTP/1.1 when explicitly requested */
-        break;
-      default:
-        /* Check if user wants to use HTTP/2 with clear TCP */
-#ifdef USE_NGHTTP2
-        if(data->state.httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) {
-#ifndef CURL_DISABLE_PROXY
-          if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) {
-            /* We don't support HTTP/2 proxies yet. Also it's debatable
-               whether or not this setting should apply to HTTP/2 proxies. */
-            infof(data, "Ignoring HTTP/2 prior knowledge due to proxy");
-            break;
-          }
-#endif
-          DEBUGF(infof(data, "HTTP/2 over clean TCP"));
-          conn->httpversion = 20;
-
-          result = Curl_http2_switched(data, NULL, 0);
-          if(result)
-            return result;
-        }
-#endif
-        break;
-      }
-    }
-    else {
-      /* prepare for an http2 request */
-      result = Curl_http2_setup(data, conn);
+  switch(conn->alpn) {
+  case CURL_HTTP_VERSION_3:
+    DEBUGASSERT(Curl_conn_is_http3(data, conn, FIRSTSOCKET));
+    break;
+  case CURL_HTTP_VERSION_2:
+    DEBUGASSERT(Curl_conn_is_http2(data, conn, FIRSTSOCKET));
+    break;
+  case CURL_HTTP_VERSION_1_1:
+    /* continue with HTTP/1.1 when explicitly requested */
+    break;
+  default:
+    /* Check if user wants to use HTTP/2 with clear TCP */
+    if(Curl_http2_may_switch(data, conn, FIRSTSOCKET)) {
+      DEBUGF(infof(data, "HTTP/2 over clean TCP"));
+      result = Curl_http2_switch(data, conn, FIRSTSOCKET);
       if(result)
         return result;
     }
+    break;
   }
+
   http = data->req.p.http;
   DEBUGASSERT(http);
 
@@ -3224,7 +3165,7 @@
   }
 
   if(!(conn->handler->flags&PROTOPT_SSL) &&
-     conn->httpversion != 20 &&
+     conn->httpversion < 20 &&
      (data->state.httpwant == CURL_HTTP_VERSION_2)) {
     /* append HTTP2 upgrade magic stuff to the HTTP request if it isn't done
        over SSL */
@@ -3236,8 +3177,10 @@
   }
 
   result = Curl_http_cookies(data, conn, &req);
+#ifdef USE_WEBSOCKETS
   if(!result && conn->handler->protocol&(CURLPROTO_WS|CURLPROTO_WSS))
     result = Curl_ws_request(data, &req);
+#endif
   if(!result)
     result = Curl_add_timecondition(data, &req);
   if(!result)
@@ -3282,7 +3225,7 @@
     }
   }
 
-  if((conn->httpversion == 20) && data->req.upload_chunky)
+  if((conn->httpversion >= 20) && data->req.upload_chunky)
     /* upload_chunky was set above to set up the request in a chunky fashion,
        but is disabled here again to avoid that the chunked encoded version is
        actually used when sending the request body over h2 */
@@ -3669,7 +3612,8 @@
 #endif
             )) {
     /* the ALPN of the current request */
-    enum alpnid id = (conn->httpversion == 20) ? ALPN_h2 : ALPN_h1;
+    enum alpnid id = (conn->httpversion == 30)? ALPN_h3 :
+                       (conn->httpversion == 20) ? ALPN_h2 : ALPN_h1;
     result = Curl_altsvc_parse(data, data->asi,
                                headp + strlen("Alt-Svc:"),
                                id, conn->host.name,
@@ -3963,7 +3907,8 @@
 
             /* switch to http2 now. The bytes after response headers
                are also processed here, otherwise they are lost. */
-            result = Curl_http2_switched(data, k->str, *nread);
+            result = Curl_http2_upgrade(data, conn, FIRSTSOCKET,
+                                        k->str, *nread);
             if(result)
               return result;
             *nread = 0;
@@ -3971,7 +3916,7 @@
 #ifdef USE_WEBSOCKETS
           else if(k->upgr101 == UPGR101_WS) {
             /* verify the response */
-            result = Curl_ws_accept(data);
+            result = Curl_ws_accept(data, k->str, *nread);
             if(result)
               return result;
             k->header = FALSE; /* no more header to parse! */
@@ -4191,11 +4136,8 @@
            stream.  In order to do this, we keep reading until we
            close the stream. */
         if(0 == k->maxdownload
-#if defined(USE_NGHTTP2)
-           && !((conn->handler->protocol & PROTO_FAMILY_HTTP) &&
-                conn->httpversion == 20)
-#endif
-           )
+           && !Curl_conn_is_http2(data, conn, FIRSTSOCKET)
+           && !Curl_conn_is_http3(data, conn, FIRSTSOCKET))
           *stop_reading = TRUE;
 
         if(*stop_reading) {
@@ -4220,11 +4162,7 @@
     if(!k->headerline++) {
       /* This is the first header, it MUST be the error code line
          or else we consider this to be the body right away! */
-      int httpversion_major;
-      int rtspversion_major;
-      int nc = 0;
-#define HEADER1 headp /* no conversion needed, just use headp */
-
+      bool fine_statusline = FALSE;
       if(conn->handler->protocol & PROTO_FAMILY_HTTP) {
         /*
          * https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2
@@ -4233,39 +4171,60 @@
          * says. We allow any three-digit number here, but we cannot make
          * guarantees on future behaviors since it isn't within the protocol.
          */
-        char separator;
-        char twoorthree[2];
         int httpversion = 0;
-        char digit4 = 0;
-        nc = sscanf(HEADER1,
-                    " HTTP/%1d.%1d%c%3d%c",
-                    &httpversion_major,
-                    &httpversion,
-                    &separator,
-                    &k->httpcode,
-                    &digit4);
+        char *p = headp;
 
-        if(nc == 1 && httpversion_major >= 2 &&
-           2 == sscanf(HEADER1, " HTTP/%1[23] %d", twoorthree, &k->httpcode)) {
-          conn->httpversion = 0;
-          nc = 4;
-          separator = ' ';
+        while(*p && ISBLANK(*p))
+          p++;
+        if(!strncmp(p, "HTTP/", 5)) {
+          p += 5;
+          switch(*p) {
+          case '1':
+            p++;
+            if((p[0] == '.') && (p[1] == '0' || p[1] == '1')) {
+              if(ISBLANK(p[2])) {
+                httpversion = 10 + (p[1] - '0');
+                p += 3;
+                if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) {
+                  k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 +
+                    (p[2] - '0');
+                  p += 3;
+                  if(ISSPACE(*p))
+                    fine_statusline = TRUE;
+                }
+              }
+            }
+            if(!fine_statusline) {
+              failf(data, "Unsupported HTTP/1 subversion in response");
+              return CURLE_UNSUPPORTED_PROTOCOL;
+            }
+            break;
+          case '2':
+          case '3':
+            if(!ISBLANK(p[1]))
+              break;
+            httpversion = (*p - '0') * 10;
+            p += 2;
+            if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) {
+              k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 +
+                (p[2] - '0');
+              p += 3;
+              if(!ISSPACE(*p))
+                break;
+              fine_statusline = TRUE;
+            }
+            break;
+          default: /* unsupported */
+            failf(data, "Unsupported HTTP version in response");
+            return CURLE_UNSUPPORTED_PROTOCOL;
+          }
         }
 
-        /* There can only be a 4th response code digit stored in 'digit4' if
-           all the other fields were parsed and stored first, so nc is 5 when
-           digit4 a digit.
-
-           The sscanf() line above will also allow zero-prefixed and negative
-           numbers, so we check for that too here.
-        */
-        else if(ISDIGIT(digit4) || (nc >= 4 && k->httpcode < 100)) {
-          failf(data, "Unsupported response code in HTTP response");
-          return CURLE_UNSUPPORTED_PROTOCOL;
-        }
-
-        if((nc >= 4) && (' ' == separator)) {
-          httpversion += 10 * httpversion_major;
+        if(fine_statusline) {
+          if(k->httpcode < 100) {
+            failf(data, "Unsupported response code in HTTP response");
+            return CURLE_UNSUPPORTED_PROTOCOL;
+          }
           switch(httpversion) {
           case 10:
           case 11:
@@ -4290,54 +4249,52 @@
           }
           if(conn->httpversion < 20) {
             conn->bundle->multiuse = BUNDLE_NO_MULTIUSE;
-            infof(data, "Mark bundle as not supporting multiuse");
           }
         }
-        else if(!nc) {
-          /* this is the real world, not a Nirvana
-             NCSA 1.5.x returns this crap when asked for HTTP/1.1
-          */
-          nc = sscanf(HEADER1, " HTTP %3d", &k->httpcode);
-          conn->httpversion = 10;
-
+        else {
           /* If user has set option HTTP200ALIASES,
              compare header line against list of aliases
           */
-          if(!nc) {
-            statusline check =
-              checkhttpprefix(data,
-                              Curl_dyn_ptr(&data->state.headerb),
-                              Curl_dyn_len(&data->state.headerb));
-            if(check == STATUS_DONE) {
-              nc = 1;
-              k->httpcode = 200;
-              conn->httpversion = 10;
-            }
+          statusline check =
+            checkhttpprefix(data,
+                            Curl_dyn_ptr(&data->state.headerb),
+                            Curl_dyn_len(&data->state.headerb));
+          if(check == STATUS_DONE) {
+            fine_statusline = TRUE;
+            k->httpcode = 200;
+            conn->httpversion = 10;
           }
         }
-        else {
-          failf(data, "Unsupported HTTP version in response");
-          return CURLE_UNSUPPORTED_PROTOCOL;
-        }
       }
       else if(conn->handler->protocol & CURLPROTO_RTSP) {
-        char separator;
-        int rtspversion;
-        nc = sscanf(HEADER1,
-                    " RTSP/%1d.%1d%c%3d",
-                    &rtspversion_major,
-                    &rtspversion,
-                    &separator,
-                    &k->httpcode);
-        if((nc == 4) && (' ' == separator)) {
-          conn->httpversion = 11; /* For us, RTSP acts like HTTP 1.1 */
-        }
-        else {
-          nc = 0;
+        char *p = headp;
+        while(*p && ISBLANK(*p))
+          p++;
+        if(!strncmp(p, "RTSP/", 5)) {
+          p += 5;
+          if(ISDIGIT(*p)) {
+            p++;
+            if((p[0] == '.') && ISDIGIT(p[1])) {
+              if(ISBLANK(p[2])) {
+                p += 3;
+                if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) {
+                  k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 +
+                    (p[2] - '0');
+                  p += 3;
+                  if(ISSPACE(*p)) {
+                    fine_statusline = TRUE;
+                    conn->httpversion = 11; /* RTSP acts like HTTP 1.1 */
+                  }
+                }
+              }
+            }
+          }
+          if(!fine_statusline)
+            return CURLE_WEIRD_SERVER_REPLY;
         }
       }
 
-      if(nc) {
+      if(fine_statusline) {
         result = Curl_http_statusline(data, conn);
         if(result)
           return result;
diff --git a/Utilities/cmcurl/lib/http.h b/Utilities/cmcurl/lib/http.h
index ecfe4ee..444abc0 100644
--- a/Utilities/cmcurl/lib/http.h
+++ b/Utilities/cmcurl/lib/http.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -24,6 +24,11 @@
  *
  ***************************************************************************/
 #include "curl_setup.h"
+
+#if defined(USE_MSH3) && !defined(_WIN32)
+#include <pthread.h>
+#endif
+
 #include "ws.h"
 
 typedef enum {
@@ -37,11 +42,7 @@
 
 #ifndef CURL_DISABLE_HTTP
 
-#ifdef USE_NGHTTP2
-#include <nghttp2/nghttp2.h>
-#endif
-
-#if defined(_WIN32) && defined(ENABLE_QUIC)
+#if defined(ENABLE_QUIC) || defined(USE_NGHTTP2)
 #include <stdint.h>
 #endif
 
@@ -73,8 +74,10 @@
                              const struct connectdata *conn,
                              const char *thisheader,
                              const size_t thislen);
+struct HTTP; /* see below */
 CURLcode Curl_buffer_send(struct dynbuf *in,
                           struct Curl_easy *data,
+                          struct HTTP *http,
                           curl_off_t *bytes_written,
                           curl_off_t included_body_bytes,
                           int socketindex);
@@ -179,29 +182,6 @@
 struct h3out; /* see ngtcp2 */
 #endif
 
-#ifdef USE_MSH3
-#ifdef _WIN32
-#define msh3_lock CRITICAL_SECTION
-#define msh3_lock_initialize(lock) InitializeCriticalSection(lock)
-#define msh3_lock_uninitialize(lock) DeleteCriticalSection(lock)
-#define msh3_lock_acquire(lock) EnterCriticalSection(lock)
-#define msh3_lock_release(lock) LeaveCriticalSection(lock)
-#else /* !_WIN32 */
-#include <pthread.h>
-#define msh3_lock pthread_mutex_t
-#define msh3_lock_initialize(lock) { \
-  pthread_mutexattr_t attr; \
-  pthread_mutexattr_init(&attr); \
-  pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); \
-  pthread_mutex_init(lock, &attr); \
-  pthread_mutexattr_destroy(&attr); \
-}
-#define msh3_lock_uninitialize(lock) pthread_mutex_destroy(lock)
-#define msh3_lock_acquire(lock) pthread_mutex_lock(lock)
-#define msh3_lock_release(lock) pthread_mutex_unlock(lock)
-#endif /* _WIN32 */
-#endif /* USE_MSH3 */
-
 /****************************************************************************
  * HTTP unique setup
  ***************************************************************************/
@@ -220,6 +200,7 @@
     void *fread_in;           /* backup storage for fread_in pointer */
     const char *postdata;
     curl_off_t postsize;
+    struct Curl_easy *data;
   } backup;
 
   enum {
@@ -258,7 +239,6 @@
 #if defined(USE_NGHTTP2) || defined(USE_NGHTTP3)
   bool bodystarted;
   int status_code; /* HTTP status code */
-  bool closed; /* TRUE on HTTP2 stream close */
   char *mem;     /* points to a buffer in memory to store received data */
   size_t len;    /* size of the buffer 'mem' points to */
   size_t memlen; /* size of data copied to mem */
@@ -268,6 +248,8 @@
   const uint8_t *upload_mem; /* points to a buffer to read from */
   size_t upload_len; /* size of the buffer 'upload_mem' points to */
   curl_off_t upload_left; /* number of bytes left to upload */
+  bool closed; /* TRUE on stream close */
+  bool reset;  /* TRUE on stream reset */
 #endif
 
 #ifdef ENABLE_QUIC
@@ -278,20 +260,25 @@
   bool firstheader;  /* FALSE until headers arrive */
   bool firstbody;  /* FALSE until body arrives */
   bool h3req;    /* FALSE until request is issued */
-#endif
+#endif /* !USE_MSH3 */
   bool upload_done;
-#endif
+#endif /* ENABLE_QUIC */
 #ifdef USE_NGHTTP3
-  size_t unacked_window;
+  size_t recv_buf_nonflow; /* buffered bytes, not counting for flow control */
   struct h3out *h3out; /* per-stream buffers for upload */
   struct dynbuf overflow; /* excess data received during a single Curl_read */
-#endif
+#endif /* USE_NGHTTP3 */
 #ifdef USE_MSH3
   struct MSH3_REQUEST *req;
-  msh3_lock recv_lock;
+#ifdef _WIN32
+  CRITICAL_SECTION recv_lock;
+#else /* !_WIN32 */
+  pthread_mutex_t recv_lock;
+#endif /* _WIN32 */
   /* Receive Buffer (Headers and Data) */
   uint8_t* recv_buf;
   size_t recv_buf_alloc;
+  size_t recv_buf_max;
   /* Receive Headers */
   size_t recv_header_len;
   bool recv_header_complete;
@@ -300,53 +287,13 @@
   bool recv_data_complete;
   /* General Receive Error */
   CURLcode recv_error;
-#endif
-};
-
-#ifdef USE_NGHTTP2
-/* h2 settings for this connection */
-struct h2settings {
-  uint32_t max_concurrent_streams;
-  bool enable_push;
-};
-#endif
-
-struct http_conn {
-#ifdef USE_NGHTTP2
-#define H2_BINSETTINGS_LEN 80
-  uint8_t binsettings[H2_BINSETTINGS_LEN];
-  size_t  binlen; /* length of the binsettings data */
-
-  /* We associate the connectdata struct with the connection, but we need to
-     make sure we can identify the current "driving" transfer. This is a
-     work-around for the lack of nghttp2_session_set_user_data() in older
-     nghttp2 versions that we want to support. (Added in 1.31.0) */
-  struct Curl_easy *trnsfr;
-
-  nghttp2_session *h2;
-  Curl_send *send_underlying; /* underlying send Curl_send callback */
-  Curl_recv *recv_underlying; /* underlying recv Curl_recv callback */
-  char *inbuf; /* buffer to receive data from underlying socket */
-  size_t inbuflen; /* number of bytes filled in inbuf */
-  size_t nread_inbuf; /* number of bytes read from in inbuf */
-  /* We need separate buffer for transmission and reception because we
-     may call nghttp2_session_send() after the
-     nghttp2_session_mem_recv() but mem buffer is still not full. In
-     this case, we wrongly sends the content of mem buffer if we share
-     them for both cases. */
-  int32_t pause_stream_id; /* stream ID which paused
-                              nghttp2_session_mem_recv */
-  size_t drain_total; /* sum of all stream's UrlState.drain */
-
-  /* this is a hash of all individual streams (Curl_easy structs) */
-  struct h2settings settings;
-
-  /* list of settings that will be sent */
-  nghttp2_settings_entry local_settings[3];
-  size_t local_settings_num;
-#else
-  int unused; /* prevent a compiler warning */
-#endif
+#endif /* USE_MSH3 */
+#ifdef USE_QUICHE
+  bool h3_got_header; /* TRUE when h3 stream has recvd some HEADER */
+  bool h3_recving_data; /* TRUE when h3 stream is reading DATA */
+  bool h3_body_pending; /* TRUE when h3 stream may have more body DATA */
+  struct h3_event_node *pending;
+#endif /* USE_QUICHE */
 };
 
 CURLcode Curl_http_size(struct Curl_easy *data);
diff --git a/Utilities/cmcurl/lib/http2.c b/Utilities/cmcurl/lib/http2.c
index b9d3245..b0ce87d 100644
--- a/Utilities/cmcurl/lib/http2.c
+++ b/Utilities/cmcurl/lib/http2.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -35,6 +35,7 @@
 #include "strcase.h"
 #include "multiif.h"
 #include "url.h"
+#include "cfilters.h"
 #include "connect.h"
 #include "strtoofft.h"
 #include "strdup.h"
@@ -63,303 +64,389 @@
 
 #define HTTP2_HUGE_WINDOW_SIZE (32 * 1024 * 1024) /* 32 MB */
 
-#ifdef DEBUG_HTTP2
-#define H2BUGF(x) x
+
+#define H2_SETTINGS_IV_LEN  3
+#define H2_BINSETTINGS_LEN 80
+
+static int populate_settings(nghttp2_settings_entry *iv,
+                             struct Curl_easy *data)
+{
+  iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+  iv[0].value = Curl_multi_max_concurrent_streams(data->multi);
+
+  iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+  iv[1].value = HTTP2_HUGE_WINDOW_SIZE;
+
+  iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
+  iv[2].value = data->multi->push_cb != NULL;
+
+  return 3;
+}
+
+static size_t populate_binsettings(uint8_t *binsettings,
+                                   struct Curl_easy *data)
+{
+  nghttp2_settings_entry iv[H2_SETTINGS_IV_LEN];
+  int ivlen;
+
+  ivlen = populate_settings(iv, data);
+  /* this returns number of bytes it wrote */
+  return nghttp2_pack_settings_payload(binsettings, H2_BINSETTINGS_LEN,
+                                       iv, ivlen);
+}
+
+struct cf_h2_ctx {
+  nghttp2_session *h2;
+  uint32_t max_concurrent_streams;
+  /* The easy handle used in the current filter call, cleared at return */
+  struct cf_call_data call_data;
+
+  char *inbuf; /* buffer to receive data from underlying socket */
+  size_t inbuflen; /* number of bytes filled in inbuf */
+  size_t nread_inbuf; /* number of bytes read from in inbuf */
+
+  struct dynbuf outbuf;
+
+  /* We need separate buffer for transmission and reception because we
+     may call nghttp2_session_send() after the
+     nghttp2_session_mem_recv() but mem buffer is still not full. In
+     this case, we wrongly sends the content of mem buffer if we share
+     them for both cases. */
+  int32_t pause_stream_id; /* stream ID which paused
+                              nghttp2_session_mem_recv */
+  size_t drain_total; /* sum of all stream's UrlState.drain */
+  int32_t goaway_error;
+  int32_t last_stream_id;
+  BIT(goaway);
+  BIT(enable_push);
+};
+
+/* How to access `call_data` from a cf_h2 filter */
+#define CF_CTX_CALL_DATA(cf)  \
+  ((struct cf_h2_ctx *)(cf)->ctx)->call_data
+
+
+static void cf_h2_ctx_clear(struct cf_h2_ctx *ctx)
+{
+  struct cf_call_data save = ctx->call_data;
+
+  if(ctx->h2) {
+    nghttp2_session_del(ctx->h2);
+  }
+  free(ctx->inbuf);
+  Curl_dyn_free(&ctx->outbuf);
+  memset(ctx, 0, sizeof(*ctx));
+  ctx->call_data = save;
+}
+
+static void cf_h2_ctx_free(struct cf_h2_ctx *ctx)
+{
+  if(ctx) {
+    cf_h2_ctx_clear(ctx);
+    free(ctx);
+  }
+}
+
+static int h2_client_new(struct Curl_cfilter *cf,
+                         nghttp2_session_callbacks *cbs)
+{
+  struct cf_h2_ctx *ctx = cf->ctx;
+
+#if NGHTTP2_VERSION_NUM < 0x013200
+  /* before 1.50.0 */
+  return nghttp2_session_client_new(&ctx->h2, cbs, cf);
 #else
-#define H2BUGF(x) do { } while(0)
+  nghttp2_option *o;
+  int rc = nghttp2_option_new(&o);
+  if(rc)
+    return rc;
+  /* turn off RFC 9113 leading and trailing white spaces validation against
+     HTTP field value. */
+  nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(o, 1);
+  rc = nghttp2_session_client_new2(&ctx->h2, cbs, cf, o);
+  nghttp2_option_del(o);
+  return rc;
 #endif
-
-static ssize_t http2_recv(struct Curl_easy *data, int sockindex,
-                          char *mem, size_t len, CURLcode *err);
-static bool http2_connisdead(struct Curl_easy *data,
-                             struct connectdata *conn);
-static int h2_session_send(struct Curl_easy *data,
-                           nghttp2_session *h2);
-static int h2_process_pending_input(struct Curl_easy *data,
-                                    struct http_conn *httpc,
-                                    CURLcode *err);
-
-/*
- * Curl_http2_init_state() is called when the easy handle is created and
- * allows for HTTP/2 specific init of state.
- */
-void Curl_http2_init_state(struct UrlState *state)
-{
-  state->stream_weight = NGHTTP2_DEFAULT_WEIGHT;
 }
 
+static ssize_t send_callback(nghttp2_session *h2,
+                             const uint8_t *mem, size_t length, int flags,
+                             void *userp);
+static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
+                         void *userp);
+static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
+                              int32_t stream_id,
+                              const uint8_t *mem, size_t len, void *userp);
+static int on_stream_close(nghttp2_session *session, int32_t stream_id,
+                           uint32_t error_code, void *userp);
+static int on_begin_headers(nghttp2_session *session,
+                            const nghttp2_frame *frame, void *userp);
+static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
+                     const uint8_t *name, size_t namelen,
+                     const uint8_t *value, size_t valuelen,
+                     uint8_t flags,
+                     void *userp);
+static int error_callback(nghttp2_session *session, const char *msg,
+                          size_t len, void *userp);
+
 /*
- * Curl_http2_init_userset() is called when the easy handle is created and
- * allows for HTTP/2 specific user-set fields.
+ * multi_connchanged() is called to tell that there is a connection in
+ * this multi handle that has changed state (multiplexing become possible, the
+ * number of allowed streams changed or similar), and a subsequent use of this
+ * multi handle should move CONNECT_PEND handles back to CONNECT to have them
+ * retry.
  */
-void Curl_http2_init_userset(struct UserDefined *set)
+static void multi_connchanged(struct Curl_multi *multi)
 {
-  set->stream_weight = NGHTTP2_DEFAULT_WEIGHT;
+  multi->recheckstate = TRUE;
 }
 
-static int http2_getsock(struct Curl_easy *data,
-                         struct connectdata *conn,
-                         curl_socket_t *sock)
+static CURLcode http2_data_setup(struct Curl_cfilter *cf,
+                                 struct Curl_easy *data)
 {
-  const struct http_conn *c = &conn->proto.httpc;
-  struct SingleRequest *k = &data->req;
-  int bitmap = GETSOCK_BLANK;
   struct HTTP *stream = data->req.p.http;
 
-  sock[0] = conn->sock[FIRSTSOCKET];
+  (void)cf;
+  DEBUGASSERT(stream);
+  DEBUGASSERT(data->state.buffer);
 
-  if(!(k->keepon & KEEP_RECV_PAUSE))
-    /* Unless paused - in an HTTP/2 connection we can basically always get a
-       frame so we should always be ready for one */
-    bitmap |= GETSOCK_READSOCK(FIRSTSOCKET);
+  stream->stream_id = -1;
 
-  /* we're (still uploading OR the HTTP/2 layer wants to send data) AND
-     there's a window to send data in */
-  if((((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND) ||
-      nghttp2_session_want_write(c->h2)) &&
-     (nghttp2_session_get_remote_window_size(c->h2) &&
-      nghttp2_session_get_stream_remote_window_size(c->h2,
-                                                    stream->stream_id)))
-    bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET);
+  Curl_dyn_init(&stream->header_recvbuf, DYN_H2_HEADERS);
+  Curl_dyn_init(&stream->trailer_recvbuf, DYN_H2_TRAILERS);
 
-  return bitmap;
+  stream->bodystarted = FALSE;
+  stream->status_code = -1;
+  stream->pausedata = NULL;
+  stream->pauselen = 0;
+  stream->closed = FALSE;
+  stream->close_handled = FALSE;
+  stream->memlen = 0;
+  stream->error = NGHTTP2_NO_ERROR;
+  stream->upload_left = 0;
+  stream->upload_mem = NULL;
+  stream->upload_len = 0;
+  stream->mem = data->state.buffer;
+  stream->len = data->set.buffer_size;
+
+  return CURLE_OK;
 }
 
 /*
+ * Initialize the cfilter context
+ */
+static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf,
+                               struct Curl_easy *data,
+                               bool via_h1_upgrade)
+{
+  struct cf_h2_ctx *ctx = cf->ctx;
+  struct HTTP *stream = data->req.p.http;
+  CURLcode result = CURLE_OUT_OF_MEMORY;
+  int rc;
+  nghttp2_session_callbacks *cbs = NULL;
+
+  DEBUGASSERT(!ctx->h2);
+  ctx->inbuf = malloc(H2_BUFSIZE);
+  if(!ctx->inbuf)
+      goto out;
+  /* we want to aggregate small frames, SETTINGS, PRIO, UPDATES */
+  Curl_dyn_init(&ctx->outbuf, 4*1024);
+
+  rc = nghttp2_session_callbacks_new(&cbs);
+  if(rc) {
+    failf(data, "Couldn't initialize nghttp2 callbacks");
+    goto out;
+  }
+
+  nghttp2_session_callbacks_set_send_callback(cbs, send_callback);
+  nghttp2_session_callbacks_set_on_frame_recv_callback(cbs, on_frame_recv);
+  nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+    cbs, on_data_chunk_recv);
+  nghttp2_session_callbacks_set_on_stream_close_callback(cbs, on_stream_close);
+  nghttp2_session_callbacks_set_on_begin_headers_callback(
+    cbs, on_begin_headers);
+  nghttp2_session_callbacks_set_on_header_callback(cbs, on_header);
+  nghttp2_session_callbacks_set_error_callback(cbs, error_callback);
+
+  /* The nghttp2 session is not yet setup, do it */
+  rc = h2_client_new(cf, cbs);
+  if(rc) {
+    failf(data, "Couldn't initialize nghttp2");
+    goto out;
+  }
+  ctx->max_concurrent_streams = DEFAULT_MAX_CONCURRENT_STREAMS;
+
+  result = http2_data_setup(cf, data);
+  if(result)
+    goto out;
+
+  if(via_h1_upgrade) {
+    /* HTTP/1.1 Upgrade issued. H2 Settings have already been submitted
+     * in the H1 request and we upgrade from there. This stream
+     * is opened implicitly as #1. */
+    uint8_t binsettings[H2_BINSETTINGS_LEN];
+    size_t  binlen; /* length of the binsettings data */
+
+    binlen = populate_binsettings(binsettings, data);
+
+    stream->stream_id = 1;
+    /* queue SETTINGS frame (again) */
+    rc = nghttp2_session_upgrade2(ctx->h2, binsettings, binlen,
+                                  data->state.httpreq == HTTPREQ_HEAD,
+                                  NULL);
+    if(rc) {
+      failf(data, "nghttp2_session_upgrade2() failed: %s(%d)",
+            nghttp2_strerror(rc), rc);
+      result = CURLE_HTTP2;
+      goto out;
+    }
+
+    rc = nghttp2_session_set_stream_user_data(ctx->h2, stream->stream_id,
+                                              data);
+    if(rc) {
+      infof(data, "http/2: failed to set user_data for stream %u",
+            stream->stream_id);
+      DEBUGASSERT(0);
+    }
+  }
+  else {
+    nghttp2_settings_entry iv[H2_SETTINGS_IV_LEN];
+    int ivlen;
+
+    /* H2 Settings need to be submitted. Stream is not open yet. */
+    DEBUGASSERT(stream->stream_id == -1);
+
+    ivlen = populate_settings(iv, data);
+    rc = nghttp2_submit_settings(ctx->h2, NGHTTP2_FLAG_NONE,
+                                 iv, ivlen);
+    if(rc) {
+      failf(data, "nghttp2_submit_settings() failed: %s(%d)",
+            nghttp2_strerror(rc), rc);
+      result = CURLE_HTTP2;
+      goto out;
+    }
+  }
+
+  rc = nghttp2_session_set_local_window_size(ctx->h2, NGHTTP2_FLAG_NONE, 0,
+                                             HTTP2_HUGE_WINDOW_SIZE);
+  if(rc) {
+    failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)",
+          nghttp2_strerror(rc), rc);
+    result = CURLE_HTTP2;
+    goto out;
+  }
+
+  /* all set, traffic will be send on connect */
+  result = CURLE_OK;
+
+out:
+  if(cbs)
+    nghttp2_session_callbacks_del(cbs);
+  return result;
+}
+
+static CURLcode  h2_session_send(struct Curl_cfilter *cf,
+                                 struct Curl_easy *data);
+static int h2_process_pending_input(struct Curl_cfilter *cf,
+                                    struct Curl_easy *data,
+                                    CURLcode *err);
+
+/*
  * http2_stream_free() free HTTP2 stream related data
  */
-static void http2_stream_free(struct HTTP *http)
+static void http2_stream_free(struct HTTP *stream)
 {
-  if(http) {
-    Curl_dyn_free(&http->header_recvbuf);
-    for(; http->push_headers_used > 0; --http->push_headers_used) {
-      free(http->push_headers[http->push_headers_used - 1]);
+  if(stream) {
+    Curl_dyn_free(&stream->header_recvbuf);
+    for(; stream->push_headers_used > 0; --stream->push_headers_used) {
+      free(stream->push_headers[stream->push_headers_used - 1]);
     }
-    free(http->push_headers);
-    http->push_headers = NULL;
+    free(stream->push_headers);
+    stream->push_headers = NULL;
   }
 }
 
 /*
- * Disconnects *a* connection used for HTTP/2. It might be an old one from the
- * connection cache and not the "main" one. Don't touch the easy handle!
+ * Returns nonzero if current HTTP/2 session should be closed.
  */
-
-static CURLcode http2_disconnect(struct Curl_easy *data,
-                                 struct connectdata *conn,
-                                 bool dead_connection)
+static int should_close_session(struct cf_h2_ctx *ctx)
 {
-  struct http_conn *c = &conn->proto.httpc;
-  (void)dead_connection;
-#ifndef DEBUG_HTTP2
-  (void)data;
-#endif
-
-  H2BUGF(infof(data, "HTTP/2 DISCONNECT starts now"));
-
-  nghttp2_session_del(c->h2);
-  Curl_safefree(c->inbuf);
-
-  H2BUGF(infof(data, "HTTP/2 DISCONNECT done"));
-
-  return CURLE_OK;
+  return ctx->drain_total == 0 && !nghttp2_session_want_read(ctx->h2) &&
+    !nghttp2_session_want_write(ctx->h2);
 }
 
 /*
  * The server may send us data at any point (e.g. PING frames). Therefore,
  * we cannot assume that an HTTP/2 socket is dead just because it is readable.
  *
- * Instead, if it is readable, run Curl_connalive() to peek at the socket
+ * Check the lower filters first and, if successful, peek at the socket
  * and distinguish between closed and data.
  */
-static bool http2_connisdead(struct Curl_easy *data, struct connectdata *conn)
+static bool http2_connisalive(struct Curl_cfilter *cf, struct Curl_easy *data,
+                              bool *input_pending)
 {
-  int sval;
-  bool dead = TRUE;
+  struct cf_h2_ctx *ctx = cf->ctx;
+  bool alive = TRUE;
 
-  if(conn->bits.close)
-    return TRUE;
+  *input_pending = FALSE;
+  if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending))
+    return FALSE;
 
-  sval = SOCKET_READABLE(conn->sock[FIRSTSOCKET], 0);
-  if(sval == 0) {
-    /* timeout */
-    dead = FALSE;
-  }
-  else if(sval & CURL_CSELECT_ERR) {
-    /* socket is in an error state */
-    dead = TRUE;
-  }
-  else if(sval & CURL_CSELECT_IN) {
-    /* readable with no error. could still be closed */
-    dead = !Curl_connalive(data, conn);
-    if(!dead) {
-      /* This happens before we've sent off a request and the connection is
-         not in use by any other transfer, there shouldn't be any data here,
-         only "protocol frames" */
-      CURLcode result;
-      struct http_conn *httpc = &conn->proto.httpc;
-      ssize_t nread = -1;
-      if(httpc->recv_underlying)
-        /* if called "too early", this pointer isn't setup yet! */
-        nread = ((Curl_recv *)httpc->recv_underlying)(
-          data, FIRSTSOCKET, httpc->inbuf, H2_BUFSIZE, &result);
-      if(nread != -1) {
-        H2BUGF(infof(data,
-                     "%d bytes stray data read before trying h2 connection",
-                     (int)nread));
-        httpc->nread_inbuf = 0;
-        httpc->inbuflen = nread;
-        if(h2_process_pending_input(data, httpc, &result) < 0)
-          /* immediate error, considered dead */
-          dead = TRUE;
-      }
-      else
-        /* the read failed so let's say this is dead anyway */
-        dead = TRUE;
-    }
-  }
+  if(*input_pending) {
+    /* This happens before we've sent off a request and the connection is
+       not in use by any other transfer, there shouldn't be any data here,
+       only "protocol frames" */
+    CURLcode result;
+    ssize_t nread = -1;
 
-  return dead;
-}
-
-/*
- * Set the transfer that is currently using this HTTP/2 connection.
- */
-static void set_transfer(struct http_conn *c,
-                         struct Curl_easy *data)
-{
-  c->trnsfr = data;
-}
-
-/*
- * Get the transfer that is currently using this HTTP/2 connection.
- */
-static struct Curl_easy *get_transfer(struct http_conn *c)
-{
-  DEBUGASSERT(c && c->trnsfr);
-  return c->trnsfr;
-}
-
-static unsigned int http2_conncheck(struct Curl_easy *data,
-                                    struct connectdata *conn,
-                                    unsigned int checks_to_perform)
-{
-  unsigned int ret_val = CONNRESULT_NONE;
-  struct http_conn *c = &conn->proto.httpc;
-  int rc;
-  bool send_frames = false;
-
-  if(checks_to_perform & CONNCHECK_ISDEAD) {
-    if(http2_connisdead(data, conn))
-      ret_val |= CONNRESULT_DEAD;
-  }
-
-  if(checks_to_perform & CONNCHECK_KEEPALIVE) {
-    struct curltime now = Curl_now();
-    timediff_t elapsed = Curl_timediff(now, conn->keepalive);
-
-    if(elapsed > data->set.upkeep_interval_ms) {
-      /* Perform an HTTP/2 PING */
-      rc = nghttp2_submit_ping(c->h2, 0, ZERO_NULL);
-      if(!rc) {
-        /* Successfully added a PING frame to the session. Need to flag this
-           so the frame is sent. */
-        send_frames = true;
-      }
+    *input_pending = FALSE;
+    Curl_attach_connection(data, cf->conn);
+    nread = Curl_conn_cf_recv(cf->next, data,
+                              ctx->inbuf, H2_BUFSIZE, &result);
+    if(nread != -1) {
+      DEBUGF(LOG_CF(data, cf, "%d bytes stray data read before trying "
+                    "h2 connection", (int)nread));
+      ctx->nread_inbuf = 0;
+      ctx->inbuflen = nread;
+      if(h2_process_pending_input(cf, data, &result) < 0)
+        /* immediate error, considered dead */
+        alive = FALSE;
       else {
-       failf(data, "nghttp2_submit_ping() failed: %s(%d)",
-             nghttp2_strerror(rc), rc);
+        alive = !should_close_session(ctx);
       }
-
-      conn->keepalive = now;
     }
+    else {
+      /* the read failed so let's say this is dead anyway */
+      alive = FALSE;
+    }
+    Curl_detach_connection(data);
   }
 
-  if(send_frames) {
-    set_transfer(c, data); /* set the transfer */
-    rc = nghttp2_session_send(c->h2);
-    if(rc)
-      failf(data, "nghttp2_session_send() failed: %s(%d)",
-            nghttp2_strerror(rc), rc);
+  return alive;
+}
+
+static CURLcode http2_send_ping(struct Curl_cfilter *cf,
+                                struct Curl_easy *data)
+{
+  struct cf_h2_ctx *ctx = cf->ctx;
+  int rc;
+
+  rc = nghttp2_submit_ping(ctx->h2, 0, ZERO_NULL);
+  if(rc) {
+    failf(data, "nghttp2_submit_ping() failed: %s(%d)",
+          nghttp2_strerror(rc), rc);
+   return CURLE_HTTP2;
   }
 
-  return ret_val;
+  rc = nghttp2_session_send(ctx->h2);
+  if(rc) {
+    failf(data, "nghttp2_session_send() failed: %s(%d)",
+          nghttp2_strerror(rc), rc);
+    return CURLE_SEND_ERROR;
+  }
+  return CURLE_OK;
 }
 
-/* called from http_setup_conn */
-void Curl_http2_setup_req(struct Curl_easy *data)
-{
-  struct HTTP *http = data->req.p.http;
-  http->bodystarted = FALSE;
-  http->status_code = -1;
-  http->pausedata = NULL;
-  http->pauselen = 0;
-  http->closed = FALSE;
-  http->close_handled = FALSE;
-  http->mem = NULL;
-  http->len = 0;
-  http->memlen = 0;
-  http->error = NGHTTP2_NO_ERROR;
-}
-
-/* called from http_setup_conn */
-void Curl_http2_setup_conn(struct connectdata *conn)
-{
-  conn->proto.httpc.settings.max_concurrent_streams =
-    DEFAULT_MAX_CONCURRENT_STREAMS;
-}
-
-/*
- * HTTP2 handler interface. This isn't added to the general list of protocols
- * but will be used at run-time when the protocol is dynamically switched from
- * HTTP to HTTP2.
- */
-static const struct Curl_handler Curl_handler_http2 = {
-  "HTTP",                               /* scheme */
-  ZERO_NULL,                            /* setup_connection */
-  Curl_http,                            /* do_it */
-  Curl_http_done,                       /* done */
-  ZERO_NULL,                            /* do_more */
-  ZERO_NULL,                            /* connect_it */
-  ZERO_NULL,                            /* connecting */
-  ZERO_NULL,                            /* doing */
-  http2_getsock,                        /* proto_getsock */
-  http2_getsock,                        /* doing_getsock */
-  ZERO_NULL,                            /* domore_getsock */
-  http2_getsock,                        /* perform_getsock */
-  http2_disconnect,                     /* disconnect */
-  ZERO_NULL,                            /* readwrite */
-  http2_conncheck,                      /* connection_check */
-  ZERO_NULL,                            /* attach connection */
-  PORT_HTTP,                            /* defport */
-  CURLPROTO_HTTP,                       /* protocol */
-  CURLPROTO_HTTP,                       /* family */
-  PROTOPT_STREAM                        /* flags */
-};
-
-static const struct Curl_handler Curl_handler_http2_ssl = {
-  "HTTPS",                              /* scheme */
-  ZERO_NULL,                            /* setup_connection */
-  Curl_http,                            /* do_it */
-  Curl_http_done,                       /* done */
-  ZERO_NULL,                            /* do_more */
-  ZERO_NULL,                            /* connect_it */
-  ZERO_NULL,                            /* connecting */
-  ZERO_NULL,                            /* doing */
-  http2_getsock,                        /* proto_getsock */
-  http2_getsock,                        /* doing_getsock */
-  ZERO_NULL,                            /* domore_getsock */
-  http2_getsock,                        /* perform_getsock */
-  http2_disconnect,                     /* disconnect */
-  ZERO_NULL,                            /* readwrite */
-  http2_conncheck,                      /* connection_check */
-  ZERO_NULL,                            /* attach connection */
-  PORT_HTTP,                            /* defport */
-  CURLPROTO_HTTPS,                      /* protocol */
-  CURLPROTO_HTTP,                       /* family */
-  PROTOPT_SSL | PROTOPT_STREAM          /* flags */
-};
-
 /*
  * Store nghttp2 version info in this buffer.
  */
@@ -369,31 +456,75 @@
   (void)msnprintf(p, len, "nghttp2/%s", h2->version_str);
 }
 
+static CURLcode flush_output(struct Curl_cfilter *cf,
+                             struct Curl_easy *data)
+{
+  struct cf_h2_ctx *ctx = cf->ctx;
+  size_t buflen = Curl_dyn_len(&ctx->outbuf);
+  ssize_t written;
+  CURLcode result;
+
+  if(!buflen)
+    return CURLE_OK;
+
+  DEBUGF(LOG_CF(data, cf, "h2 conn flush %zu bytes", buflen));
+  written = Curl_conn_cf_send(cf->next, data, Curl_dyn_ptr(&ctx->outbuf),
+                              buflen, &result);
+  if(written < 0) {
+    return result;
+  }
+  if((size_t)written < buflen) {
+    Curl_dyn_tail(&ctx->outbuf, buflen - (size_t)written);
+    return CURLE_AGAIN;
+  }
+  else {
+    Curl_dyn_reset(&ctx->outbuf);
+  }
+  return CURLE_OK;
+}
+
 /*
  * The implementation of nghttp2_send_callback type. Here we write |data| with
  * size |length| to the network and return the number of bytes actually
  * written. See the documentation of nghttp2_send_callback for the details.
  */
 static ssize_t send_callback(nghttp2_session *h2,
-                             const uint8_t *mem, size_t length, int flags,
+                             const uint8_t *buf, size_t blen, int flags,
                              void *userp)
 {
-  struct connectdata *conn = (struct connectdata *)userp;
-  struct http_conn *c = &conn->proto.httpc;
-  struct Curl_easy *data = get_transfer(c);
+  struct Curl_cfilter *cf = userp;
+  struct cf_h2_ctx *ctx = cf->ctx;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
   ssize_t written;
   CURLcode result = CURLE_OK;
+  size_t buflen = Curl_dyn_len(&ctx->outbuf);
 
   (void)h2;
   (void)flags;
+  DEBUGASSERT(data);
 
-  if(!c->send_underlying)
-    /* called before setup properly! */
-    return NGHTTP2_ERR_CALLBACK_FAILURE;
+  if(blen < 1024 && (buflen + blen + 1 < ctx->outbuf.toobig)) {
+    result = Curl_dyn_addn(&ctx->outbuf, buf, blen);
+    if(result) {
+      failf(data, "Failed to add data to output buffer");
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+    return blen;
+  }
+  if(buflen) {
+    /* not adding, flush buffer */
+    result = flush_output(cf, data);
+    if(result) {
+      if(result == CURLE_AGAIN) {
+        return NGHTTP2_ERR_WOULDBLOCK;
+      }
+      failf(data, "Failed sending HTTP2 data");
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+  }
 
-  written = ((Curl_send*)c->send_underlying)(data, FIRSTSOCKET,
-                                             mem, length, &result);
-
+  DEBUGF(LOG_CF(data, cf, "h2 conn send %zu bytes", blen));
+  written = Curl_conn_cf_send(cf->next, data, buf, blen, &result);
   if(result == CURLE_AGAIN) {
     return NGHTTP2_ERR_WOULDBLOCK;
   }
@@ -467,26 +598,33 @@
 /*
  * This specific transfer on this connection has been "drained".
  */
-static void drained_transfer(struct Curl_easy *data,
-                             struct http_conn *httpc)
+static void drained_transfer(struct Curl_cfilter *cf,
+                             struct Curl_easy *data)
 {
-  DEBUGASSERT(httpc->drain_total >= data->state.drain);
-  httpc->drain_total -= data->state.drain;
-  data->state.drain = 0;
+  if(data->state.drain) {
+    struct cf_h2_ctx *ctx = cf->ctx;
+    DEBUGASSERT(ctx->drain_total > 0);
+    ctx->drain_total--;
+    data->state.drain = 0;
+  }
 }
 
 /*
  * Mark this transfer to get "drained".
  */
-static void drain_this(struct Curl_easy *data,
-                       struct http_conn *httpc)
+static void drain_this(struct Curl_cfilter *cf,
+                       struct Curl_easy *data)
 {
-  data->state.drain++;
-  httpc->drain_total++;
-  DEBUGASSERT(httpc->drain_total >= data->state.drain);
+  if(!data->state.drain) {
+    struct cf_h2_ctx *ctx = cf->ctx;
+    data->state.drain = 1;
+    ctx->drain_total++;
+    DEBUGASSERT(ctx->drain_total > 0);
+  }
 }
 
-static struct Curl_easy *duphandle(struct Curl_easy *data)
+static struct Curl_easy *h2_duphandle(struct Curl_cfilter *cf,
+                                      struct Curl_easy *data)
 {
   struct Curl_easy *second = curl_easy_duphandle(data);
   if(second) {
@@ -497,9 +635,8 @@
     }
     else {
       second->req.p.http = http;
-      Curl_dyn_init(&http->header_recvbuf, DYN_H2_HEADERS);
-      Curl_http2_setup_req(second);
-      second->state.stream_weight = data->state.stream_weight;
+      http2_data_setup(cf, second);
+      second->state.priority.weight = data->state.priority.weight;
     }
   }
   return second;
@@ -559,22 +696,23 @@
   return 0;
 }
 
-static int push_promise(struct Curl_easy *data,
-                        struct connectdata *conn,
+static int push_promise(struct Curl_cfilter *cf,
+                        struct Curl_easy *data,
                         const nghttp2_push_promise *frame)
 {
+  struct cf_h2_ctx *ctx = cf->ctx;
   int rv; /* one of the CURL_PUSH_* defines */
-  H2BUGF(infof(data, "PUSH_PROMISE received, stream %u",
-               frame->promised_stream_id));
+
+  DEBUGF(LOG_CF(data, cf, "[h2sid=%u] PUSH_PROMISE received",
+                frame->promised_stream_id));
   if(data->multi->push_cb) {
     struct HTTP *stream;
     struct HTTP *newstream;
     struct curl_pushheaders heads;
     CURLMcode rc;
-    struct http_conn *httpc;
     size_t i;
     /* clone the parent */
-    struct Curl_easy *newhandle = duphandle(data);
+    struct Curl_easy *newhandle = h2_duphandle(cf, data);
     if(!newhandle) {
       infof(data, "failed to duplicate handle");
       rv = CURL_PUSH_DENY; /* FAIL HARD */
@@ -584,7 +722,7 @@
     heads.data = data;
     heads.frame = frame;
     /* ask the application */
-    H2BUGF(infof(data, "Got PUSH_PROMISE, ask application"));
+    DEBUGF(LOG_CF(data, cf, "Got PUSH_PROMISE, ask application"));
 
     stream = data->req.p.http;
     if(!stream) {
@@ -630,7 +768,7 @@
 
     /* approved, add to the multi handle and immediately switch to PERFORM
        state with the given connection !*/
-    rc = Curl_multi_add_perform(data->multi, newhandle, conn);
+    rc = Curl_multi_add_perform(data->multi, newhandle, cf->conn);
     if(rc) {
       infof(data, "failed to add handle to multi");
       http2_stream_free(newhandle->req.p.http);
@@ -640,8 +778,7 @@
       goto fail;
     }
 
-    httpc = &conn->proto.httpc;
-    rv = nghttp2_session_set_stream_user_data(httpc->h2,
+    rv = nghttp2_session_set_stream_user_data(ctx->h2,
                                               frame->promised_stream_id,
                                               newhandle);
     if(rv) {
@@ -655,84 +792,85 @@
     Curl_dyn_init(&newstream->trailer_recvbuf, DYN_H2_TRAILERS);
   }
   else {
-    H2BUGF(infof(data, "Got PUSH_PROMISE, ignore it"));
+    DEBUGF(LOG_CF(data, cf, "Got PUSH_PROMISE, ignore it"));
     rv = CURL_PUSH_DENY;
   }
   fail:
   return rv;
 }
 
-/*
- * multi_connchanged() is called to tell that there is a connection in
- * this multi handle that has changed state (multiplexing become possible, the
- * number of allowed streams changed or similar), and a subsequent use of this
- * multi handle should move CONNECT_PEND handles back to CONNECT to have them
- * retry.
- */
-static void multi_connchanged(struct Curl_multi *multi)
-{
-  multi->recheckstate = TRUE;
-}
-
 static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
                          void *userp)
 {
-  struct connectdata *conn = (struct connectdata *)userp;
-  struct http_conn *httpc = &conn->proto.httpc;
+  struct Curl_cfilter *cf = userp;
+  struct cf_h2_ctx *ctx = cf->ctx;
   struct Curl_easy *data_s = NULL;
   struct HTTP *stream = NULL;
-  struct Curl_easy *data = get_transfer(httpc);
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
   int rv;
   size_t left, ncopy;
   int32_t stream_id = frame->hd.stream_id;
   CURLcode result;
 
+  DEBUGASSERT(data);
   if(!stream_id) {
     /* stream ID zero is for connection-oriented stuff */
-    if(frame->hd.type == NGHTTP2_SETTINGS) {
-      uint32_t max_conn = httpc->settings.max_concurrent_streams;
-      H2BUGF(infof(data, "Got SETTINGS"));
-      httpc->settings.max_concurrent_streams =
-        nghttp2_session_get_remote_settings(
+    DEBUGASSERT(data);
+    switch(frame->hd.type) {
+    case NGHTTP2_SETTINGS: {
+      uint32_t max_conn = ctx->max_concurrent_streams;
+      DEBUGF(LOG_CF(data, cf, "recv frame SETTINGS"));
+      ctx->max_concurrent_streams = nghttp2_session_get_remote_settings(
           session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
-      httpc->settings.enable_push =
-        nghttp2_session_get_remote_settings(
-          session, NGHTTP2_SETTINGS_ENABLE_PUSH);
-      H2BUGF(infof(data, "MAX_CONCURRENT_STREAMS == %d",
-                   httpc->settings.max_concurrent_streams));
-      H2BUGF(infof(data, "ENABLE_PUSH == %s",
-                   httpc->settings.enable_push?"TRUE":"false"));
-      if(max_conn != httpc->settings.max_concurrent_streams) {
+      ctx->enable_push = nghttp2_session_get_remote_settings(
+          session, NGHTTP2_SETTINGS_ENABLE_PUSH) != 0;
+      DEBUGF(LOG_CF(data, cf, "MAX_CONCURRENT_STREAMS == %d",
+                    ctx->max_concurrent_streams));
+      DEBUGF(LOG_CF(data, cf, "ENABLE_PUSH == %s",
+                    ctx->enable_push ? "TRUE" : "false"));
+      if(data && max_conn != ctx->max_concurrent_streams) {
         /* only signal change if the value actually changed */
-        infof(data,
-              "Connection state changed (MAX_CONCURRENT_STREAMS == %u)!",
-              httpc->settings.max_concurrent_streams);
+        DEBUGF(LOG_CF(data, cf, "MAX_CONCURRENT_STREAMS now %u",
+                      ctx->max_concurrent_streams));
         multi_connchanged(data->multi);
       }
+      break;
+    }
+    case NGHTTP2_GOAWAY:
+      ctx->goaway = TRUE;
+      ctx->goaway_error = frame->goaway.error_code;
+      ctx->last_stream_id = frame->goaway.last_stream_id;
+      if(data) {
+        infof(data, "recveived GOAWAY, error=%d, last_stream=%u",
+                    ctx->goaway_error, ctx->last_stream_id);
+        multi_connchanged(data->multi);
+      }
+      break;
+    case NGHTTP2_WINDOW_UPDATE:
+      DEBUGF(LOG_CF(data, cf, "recv frame WINDOW_UPDATE"));
+      break;
+    default:
+      DEBUGF(LOG_CF(data, cf, "recv frame %x on 0", frame->hd.type));
     }
     return 0;
   }
   data_s = nghttp2_session_get_stream_user_data(session, stream_id);
   if(!data_s) {
-    H2BUGF(infof(data,
-                 "No Curl_easy associated with stream: %u",
-                 stream_id));
+    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] No Curl_easy associated",
+                  stream_id));
     return 0;
   }
 
   stream = data_s->req.p.http;
   if(!stream) {
-    H2BUGF(infof(data_s, "No proto pointer for stream: %u",
-                 stream_id));
+    DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] No proto pointer", stream_id));
     return NGHTTP2_ERR_CALLBACK_FAILURE;
   }
 
-  H2BUGF(infof(data_s, "on_frame_recv() header %x stream %u",
-               frame->hd.type, stream_id));
-
   switch(frame->hd.type) {
   case NGHTTP2_DATA:
-    /* If body started on this stream, then receiving DATA is illegal. */
+    /* If !body started on this stream, then receiving DATA is illegal. */
+    DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv frame DATA", stream_id));
     if(!stream->bodystarted) {
       rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
                                      stream_id, NGHTTP2_PROTOCOL_ERROR);
@@ -741,8 +879,17 @@
         return NGHTTP2_ERR_CALLBACK_FAILURE;
       }
     }
+    if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+      /* Stream has ended. If there is pending data, ensure that read
+         will occur to consume it. */
+      if(!data->state.drain && stream->memlen) {
+        drain_this(cf, data_s);
+        Curl_expire(data, 0, EXPIRE_RUN_NOW);
+      }
+    }
     break;
   case NGHTTP2_HEADERS:
+    DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv frame HEADERS", stream_id));
     if(stream->bodystarted) {
       /* Only valid HEADERS after body started is trailer HEADERS.  We
          buffer them in on_header callback. */
@@ -776,19 +923,18 @@
     stream->nread_header_recvbuf += ncopy;
 
     DEBUGASSERT(stream->mem);
-    H2BUGF(infof(data_s, "Store %zu bytes headers from stream %u at %p",
-                 ncopy, stream_id, stream->mem));
+    DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] %zu header bytes, at %p",
+                  stream_id, ncopy, (void *)stream->mem));
 
     stream->len -= ncopy;
     stream->memlen += ncopy;
 
-    drain_this(data_s, httpc);
-    /* if we receive data for another handle, wake that up */
-    if(get_transfer(httpc) != data_s)
-      Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
+    drain_this(cf, data_s);
+    Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
     break;
   case NGHTTP2_PUSH_PROMISE:
-    rv = push_promise(data_s, conn, &frame->push_promise);
+    DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv PUSH_PROMISE", stream_id));
+    rv = push_promise(cf, data_s, &frame->push_promise);
     if(rv) { /* deny! */
       int h2;
       DEBUGASSERT((rv > CURL_PUSH_OK) && (rv <= CURL_PUSH_ERROROUT));
@@ -798,14 +944,32 @@
       if(nghttp2_is_fatal(h2))
         return NGHTTP2_ERR_CALLBACK_FAILURE;
       else if(rv == CURL_PUSH_ERROROUT) {
-        DEBUGF(infof(data_s, "Fail the parent stream (too)"));
+        DEBUGF(LOG_CF(data_s, cf, "Fail the parent stream (too)"));
         return NGHTTP2_ERR_CALLBACK_FAILURE;
       }
     }
     break;
+  case NGHTTP2_RST_STREAM:
+    DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv RST", stream_id));
+    stream->closed = TRUE;
+    stream->reset = TRUE;
+    drain_this(cf, data);
+    Curl_expire(data, 0, EXPIRE_RUN_NOW);
+    break;
+  case NGHTTP2_WINDOW_UPDATE:
+    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv WINDOW_UPDATE", stream_id));
+    if((data_s->req.keepon & KEEP_SEND_HOLD) &&
+       (data_s->req.keepon & KEEP_SEND)) {
+      data_s->req.keepon &= ~KEEP_SEND_HOLD;
+      drain_this(cf, data_s);
+      Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
+      DEBUGF(LOG_CF(data, cf, "[h2sid=%u] un-holding after win update",
+                    stream_id));
+    }
+    break;
   default:
-    H2BUGF(infof(data_s, "Got frame type %x for stream %u",
-                 frame->hd.type, stream_id));
+    DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv frame %x",
+                  stream_id, frame->hd.type));
     break;
   }
   return 0;
@@ -815,15 +979,15 @@
                               int32_t stream_id,
                               const uint8_t *mem, size_t len, void *userp)
 {
+  struct Curl_cfilter *cf = userp;
+  struct cf_h2_ctx *ctx = cf->ctx;
   struct HTTP *stream;
   struct Curl_easy *data_s;
   size_t nread;
-  struct connectdata *conn = (struct connectdata *)userp;
-  struct http_conn *httpc = &conn->proto.httpc;
-  (void)session;
   (void)flags;
 
   DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
+  DEBUGASSERT(CF_DATA_CURRENT(cf));
 
   /* get the stream from the hash based on Stream ID */
   data_s = nghttp2_session_get_stream_user_data(session, stream_id);
@@ -831,8 +995,8 @@
     /* Receiving a Stream ID not in the hash should not happen - unless
        we have aborted a transfer artificially and there were more data
        in the pipeline. Silently ignore. */
-    H2BUGF(fprintf(stderr, "Data for stream %u but it doesn't exist\n",
-                   stream_id));
+    DEBUGF(LOG_CF(CF_DATA_CURRENT(cf), cf, "[h2sid=%u] Data for unknown",
+                  stream_id));
     return 0;
   }
 
@@ -846,34 +1010,24 @@
   stream->len -= nread;
   stream->memlen += nread;
 
-  drain_this(data_s, &conn->proto.httpc);
-
   /* if we receive data for another handle, wake that up */
-  if(get_transfer(httpc) != data_s)
+  if(CF_DATA_CURRENT(cf) != data_s) {
+    drain_this(cf, data_s);
     Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
+  }
 
-  H2BUGF(infof(data_s, "%zu data received for stream %u "
-               "(%zu left in buffer %p, total %zu)",
-               nread, stream_id,
-               stream->len, stream->mem,
-               stream->memlen));
+  DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] %zu DATA recvd, "
+                "(buffer now holds %zu, %zu still free in %p)",
+                stream_id, nread,
+                stream->memlen, stream->len, (void *)stream->mem));
 
   if(nread < len) {
     stream->pausedata = mem + nread;
     stream->pauselen = len - nread;
-    H2BUGF(infof(data_s, "NGHTTP2_ERR_PAUSE - %zu bytes out of buffer"
-                 ", stream %u",
-                 len - nread, stream_id));
-    data_s->conn->proto.httpc.pause_stream_id = stream_id;
-
-    return NGHTTP2_ERR_PAUSE;
-  }
-
-  /* pause execution of nghttp2 if we received data for another handle
-     in order to process them first. */
-  if(get_transfer(httpc) != data_s) {
-    data_s->conn->proto.httpc.pause_stream_id = stream_id;
-
+    DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] %zu not recvd -> NGHTTP2_ERR_PAUSE",
+                  stream_id, len - nread));
+    ctx->pause_stream_id = stream_id;
+    drain_this(cf, data_s);
     return NGHTTP2_ERR_PAUSE;
   }
 
@@ -883,65 +1037,66 @@
 static int on_stream_close(nghttp2_session *session, int32_t stream_id,
                            uint32_t error_code, void *userp)
 {
+  struct Curl_cfilter *cf = userp;
+  struct cf_h2_ctx *ctx = cf->ctx;
   struct Curl_easy *data_s;
   struct HTTP *stream;
-  struct connectdata *conn = (struct connectdata *)userp;
   int rv;
   (void)session;
-  (void)stream_id;
 
-  if(stream_id) {
-    struct http_conn *httpc;
-    /* get the stream from the hash based on Stream ID, stream ID zero is for
-       connection-oriented stuff */
-    data_s = nghttp2_session_get_stream_user_data(session, stream_id);
-    if(!data_s) {
-      /* We could get stream ID not in the hash.  For example, if we
-         decided to reject stream (e.g., PUSH_PROMISE). */
-      return 0;
-    }
-    H2BUGF(infof(data_s, "on_stream_close(), %s (err %d), stream %u",
-                 nghttp2_http2_strerror(error_code), error_code, stream_id));
-    stream = data_s->req.p.http;
-    if(!stream)
-      return NGHTTP2_ERR_CALLBACK_FAILURE;
-
-    stream->closed = TRUE;
-    httpc = &conn->proto.httpc;
-    drain_this(data_s, httpc);
-    Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
-    stream->error = error_code;
-
-    /* remove the entry from the hash as the stream is now gone */
-    rv = nghttp2_session_set_stream_user_data(session, stream_id, 0);
-    if(rv) {
-      infof(data_s, "http/2: failed to clear user_data for stream %u",
-            stream_id);
-      DEBUGASSERT(0);
-    }
-    if(stream_id == httpc->pause_stream_id) {
-      H2BUGF(infof(data_s, "Stopped the pause stream"));
-      httpc->pause_stream_id = 0;
-    }
-    H2BUGF(infof(data_s, "Removed stream %u hash", stream_id));
-    stream->stream_id = 0; /* cleared */
+  /* get the stream from the hash based on Stream ID, stream ID zero is for
+     connection-oriented stuff */
+  data_s = stream_id?
+             nghttp2_session_get_stream_user_data(session, stream_id) : NULL;
+  if(!data_s) {
+    return 0;
   }
+  stream = data_s->req.p.http;
+  DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] on_stream_close(), %s (err %d)",
+                stream_id, nghttp2_http2_strerror(error_code), error_code));
+  if(!stream)
+    return NGHTTP2_ERR_CALLBACK_FAILURE;
+
+  stream->closed = TRUE;
+  stream->error = error_code;
+  if(stream->error)
+    stream->reset = TRUE;
+
+  if(CF_DATA_CURRENT(cf) != data_s) {
+    drain_this(cf, data_s);
+    Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
+  }
+
+  /* remove `data_s` from the nghttp2 stream */
+  rv = nghttp2_session_set_stream_user_data(session, stream_id, 0);
+  if(rv) {
+    infof(data_s, "http/2: failed to clear user_data for stream %u",
+          stream_id);
+    DEBUGASSERT(0);
+  }
+  if(stream_id == ctx->pause_stream_id) {
+    DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] closed the pause stream",
+                  stream_id));
+    ctx->pause_stream_id = 0;
+  }
+  DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] closed now", stream_id));
   return 0;
 }
 
 static int on_begin_headers(nghttp2_session *session,
                             const nghttp2_frame *frame, void *userp)
 {
+  struct Curl_cfilter *cf = userp;
   struct HTTP *stream;
   struct Curl_easy *data_s = NULL;
-  (void)userp;
 
+  (void)cf;
   data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
   if(!data_s) {
     return 0;
   }
 
-  H2BUGF(infof(data_s, "on_begin_headers() was called"));
+  DEBUGF(LOG_CF(data_s, cf, "on_begin_headers() was called"));
 
   if(frame->hd.type != NGHTTP2_HEADERS) {
     return 0;
@@ -989,11 +1144,10 @@
                      uint8_t flags,
                      void *userp)
 {
+  struct Curl_cfilter *cf = userp;
   struct HTTP *stream;
   struct Curl_easy *data_s;
   int32_t stream_id = frame->hd.stream_id;
-  struct connectdata *conn = (struct connectdata *)userp;
-  struct http_conn *httpc = &conn->proto.httpc;
   CURLcode result;
   (void)flags;
 
@@ -1020,13 +1174,14 @@
     if(!strcmp(H2H3_PSEUDO_AUTHORITY, (const char *)name)) {
       /* pseudo headers are lower case */
       int rc = 0;
-      char *check = aprintf("%s:%d", conn->host.name, conn->remote_port);
+      char *check = aprintf("%s:%d", cf->conn->host.name,
+                            cf->conn->remote_port);
       if(!check)
         /* no memory */
         return NGHTTP2_ERR_CALLBACK_FAILURE;
       if(!strcasecompare(check, (const char *)value) &&
-         ((conn->remote_port != conn->given->defport) ||
-          !strcasecompare(conn->host.name, (const char *)value))) {
+         ((cf->conn->remote_port != cf->conn->given->defport) ||
+          !strcasecompare(cf->conn->host.name, (const char *)value))) {
         /* This is push is not for the same authority that was asked for in
          * the URL. RFC 7540 section 8.2 says: "A client MUST treat a
          * PUSH_PROMISE for which the server is not authoritative as a stream
@@ -1075,11 +1230,13 @@
 
   if(stream->bodystarted) {
     /* This is a trailer */
-    H2BUGF(infof(data_s, "h2 trailer: %.*s: %.*s", namelen, name, valuelen,
-                 value));
+    DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] trailer: %.*s: %.*s",
+                  stream->stream_id,
+                  (int)namelen, name,
+                  (int)valuelen, value));
     result = Curl_dyn_addf(&stream->trailer_recvbuf,
-                           "%.*s: %.*s\r\n", namelen, name,
-                           valuelen, value);
+                           "%.*s: %.*s\r\n", (int)namelen, name,
+                           (int)valuelen, value);
     if(result)
       return NGHTTP2_ERR_CALLBACK_FAILURE;
 
@@ -1110,11 +1267,11 @@
     if(result)
       return NGHTTP2_ERR_CALLBACK_FAILURE;
     /* if we receive data for another handle, wake that up */
-    if(get_transfer(httpc) != data_s)
+    if(CF_DATA_CURRENT(cf) != data_s)
       Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
 
-    H2BUGF(infof(data_s, "h2 status: HTTP/2 %03d (easy %p)",
-                 stream->status_code, data_s));
+    DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] status: HTTP/2 %03d",
+                  stream->stream_id, stream->status_code));
     return 0;
   }
 
@@ -1134,11 +1291,13 @@
   if(result)
     return NGHTTP2_ERR_CALLBACK_FAILURE;
   /* if we receive data for another handle, wake that up */
-  if(get_transfer(httpc) != data_s)
+  if(CF_DATA_CURRENT(cf) != data_s)
     Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
 
-  H2BUGF(infof(data_s, "h2 header: %.*s: %.*s", namelen, name, valuelen,
-               value));
+  DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] header: %.*s: %.*s",
+                stream->stream_id,
+                (int)namelen, name,
+                (int)valuelen, value));
 
   return 0; /* 0 is successful */
 }
@@ -1150,12 +1309,13 @@
                                          nghttp2_data_source *source,
                                          void *userp)
 {
+  struct Curl_cfilter *cf = userp;
   struct Curl_easy *data_s;
   struct HTTP *stream = NULL;
   size_t nread;
   (void)source;
-  (void)userp;
 
+  (void)cf;
   if(stream_id) {
     /* get the stream from the hash based on Stream ID, stream ID zero is for
        connection-oriented stuff */
@@ -1186,9 +1346,8 @@
   else if(nread == 0)
     return NGHTTP2_ERR_DEFERRED;
 
-  H2BUGF(infof(data_s, "data_source_read_callback: "
-               "returns %zu bytes stream %u",
-               nread, stream_id));
+  DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] data_source_read_callback: "
+                "returns %zu bytes", stream_id, nread));
 
   return nread;
 }
@@ -1207,149 +1366,59 @@
 }
 #endif
 
-static void populate_settings(struct Curl_easy *data,
-                              struct http_conn *httpc)
+static void http2_data_done(struct Curl_cfilter *cf,
+                            struct Curl_easy *data, bool premature)
 {
-  nghttp2_settings_entry *iv = httpc->local_settings;
-
-  iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
-  iv[0].value = Curl_multi_max_concurrent_streams(data->multi);
-
-  iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
-  iv[1].value = HTTP2_HUGE_WINDOW_SIZE;
-
-  iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
-  iv[2].value = data->multi->push_cb != NULL;
-
-  httpc->local_settings_num = 3;
-}
-
-void Curl_http2_done(struct Curl_easy *data, bool premature)
-{
-  struct HTTP *http = data->req.p.http;
-  struct http_conn *httpc = &data->conn->proto.httpc;
+  struct cf_h2_ctx *ctx = cf->ctx;
+  struct HTTP *stream = data->req.p.http;
 
   /* there might be allocated resources done before this got the 'h2' pointer
      setup */
-  Curl_dyn_free(&http->header_recvbuf);
-  Curl_dyn_free(&http->trailer_recvbuf);
-  if(http->push_headers) {
+  Curl_dyn_free(&stream->header_recvbuf);
+  Curl_dyn_free(&stream->trailer_recvbuf);
+  if(stream->push_headers) {
     /* if they weren't used and then freed before */
-    for(; http->push_headers_used > 0; --http->push_headers_used) {
-      free(http->push_headers[http->push_headers_used - 1]);
+    for(; stream->push_headers_used > 0; --stream->push_headers_used) {
+      free(stream->push_headers[stream->push_headers_used - 1]);
     }
-    free(http->push_headers);
-    http->push_headers = NULL;
+    free(stream->push_headers);
+    stream->push_headers = NULL;
   }
 
-  if(!(data->conn->handler->protocol&PROTO_FAMILY_HTTP) ||
-     !httpc->h2) /* not HTTP/2 ? */
+  if(!ctx || !ctx->h2)
     return;
 
   /* do this before the reset handling, as that might clear ->stream_id */
-  if(http->stream_id == httpc->pause_stream_id) {
-    H2BUGF(infof(data, "DONE the pause stream (%u)", http->stream_id));
-    httpc->pause_stream_id = 0;
+  if(stream->stream_id && stream->stream_id == ctx->pause_stream_id) {
+    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] DONE, the pause stream",
+                  stream->stream_id));
+    ctx->pause_stream_id = 0;
   }
-  if(premature || (!http->closed && http->stream_id)) {
+
+  (void)premature;
+  if(!stream->closed && stream->stream_id) {
     /* RST_STREAM */
-    set_transfer(httpc, data); /* set the transfer */
-    H2BUGF(infof(data, "RST stream %u", http->stream_id));
-    if(!nghttp2_submit_rst_stream(httpc->h2, NGHTTP2_FLAG_NONE,
-                                  http->stream_id, NGHTTP2_STREAM_CLOSED))
-      (void)nghttp2_session_send(httpc->h2);
+    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] RST", stream->stream_id));
+    if(!nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE,
+                                  stream->stream_id, NGHTTP2_STREAM_CLOSED))
+      (void)nghttp2_session_send(ctx->h2);
   }
 
   if(data->state.drain)
-    drained_transfer(data, httpc);
+    drained_transfer(cf, data);
 
   /* -1 means unassigned and 0 means cleared */
-  if(http->stream_id > 0) {
-    int rv = nghttp2_session_set_stream_user_data(httpc->h2,
-                                                  http->stream_id, 0);
+  if(nghttp2_session_get_stream_user_data(ctx->h2, stream->stream_id)) {
+    int rv = nghttp2_session_set_stream_user_data(ctx->h2,
+                                                  stream->stream_id, 0);
     if(rv) {
       infof(data, "http/2: failed to clear user_data for stream %u",
-            http->stream_id);
+            stream->stream_id);
       DEBUGASSERT(0);
     }
-    set_transfer(httpc, NULL);
-    http->stream_id = 0;
   }
 }
 
-static int client_new(struct connectdata *conn,
-                      nghttp2_session_callbacks *callbacks)
-{
-#if NGHTTP2_VERSION_NUM < 0x013200
-  /* before 1.50.0 */
-  return nghttp2_session_client_new(&conn->proto.httpc.h2, callbacks, conn);
-#else
-  nghttp2_option *o;
-  int rc = nghttp2_option_new(&o);
-  if(rc)
-    return rc;
-  /* turn off RFC 9113 leading and trailing white spaces validation against
-     HTTP field value. */
-  nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(o, 1);
-  rc = nghttp2_session_client_new2(&conn->proto.httpc.h2, callbacks, conn,
-                                   o);
-  nghttp2_option_del(o);
-  return rc;
-#endif
-}
-
-/*
- * Initialize nghttp2 for a Curl connection
- */
-static CURLcode http2_init(struct Curl_easy *data, struct connectdata *conn)
-{
-  if(!conn->proto.httpc.h2) {
-    int rc;
-    nghttp2_session_callbacks *callbacks;
-
-    conn->proto.httpc.inbuf = malloc(H2_BUFSIZE);
-    if(!conn->proto.httpc.inbuf)
-      return CURLE_OUT_OF_MEMORY;
-
-    rc = nghttp2_session_callbacks_new(&callbacks);
-
-    if(rc) {
-      failf(data, "Couldn't initialize nghttp2 callbacks");
-      return CURLE_OUT_OF_MEMORY; /* most likely at least */
-    }
-
-    /* nghttp2_send_callback */
-    nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
-    /* nghttp2_on_frame_recv_callback */
-    nghttp2_session_callbacks_set_on_frame_recv_callback
-      (callbacks, on_frame_recv);
-    /* nghttp2_on_data_chunk_recv_callback */
-    nghttp2_session_callbacks_set_on_data_chunk_recv_callback
-      (callbacks, on_data_chunk_recv);
-    /* nghttp2_on_stream_close_callback */
-    nghttp2_session_callbacks_set_on_stream_close_callback
-      (callbacks, on_stream_close);
-    /* nghttp2_on_begin_headers_callback */
-    nghttp2_session_callbacks_set_on_begin_headers_callback
-      (callbacks, on_begin_headers);
-    /* nghttp2_on_header_callback */
-    nghttp2_session_callbacks_set_on_header_callback(callbacks, on_header);
-
-    nghttp2_session_callbacks_set_error_callback(callbacks, error_callback);
-
-    /* The nghttp2 session is not yet setup, do it */
-    rc = client_new(conn, callbacks);
-
-    nghttp2_session_callbacks_del(callbacks);
-
-    if(rc) {
-      failf(data, "Couldn't initialize nghttp2");
-      return CURLE_OUT_OF_MEMORY; /* most likely at least */
-    }
-  }
-  return CURLE_OK;
-}
-
 /*
  * Append headers to ask for an HTTP1.1 to HTTP2 upgrade.
  */
@@ -1357,26 +1426,18 @@
                                     struct Curl_easy *data)
 {
   CURLcode result;
-  ssize_t binlen;
   char *base64;
   size_t blen;
-  struct connectdata *conn = data->conn;
   struct SingleRequest *k = &data->req;
-  uint8_t *binsettings = conn->proto.httpc.binsettings;
-  struct http_conn *httpc = &conn->proto.httpc;
+  uint8_t binsettings[H2_BINSETTINGS_LEN];
+  size_t  binlen; /* length of the binsettings data */
 
-  populate_settings(data, httpc);
-
-  /* this returns number of bytes it wrote */
-  binlen = nghttp2_pack_settings_payload(binsettings, H2_BINSETTINGS_LEN,
-                                         httpc->local_settings,
-                                         httpc->local_settings_num);
+  binlen = populate_binsettings(binsettings, data);
   if(binlen <= 0) {
     failf(data, "nghttp2 unexpectedly failed on pack_settings_payload");
     Curl_dyn_free(req);
     return CURLE_FAILED_INIT;
   }
-  conn->proto.httpc.binlen = binlen;
 
   result = Curl_base64url_encode((const char *)binsettings, binlen,
                                  &base64, &blen);
@@ -1398,79 +1459,70 @@
 }
 
 /*
- * Returns nonzero if current HTTP/2 session should be closed.
- */
-static int should_close_session(struct http_conn *httpc)
-{
-  return httpc->drain_total == 0 && !nghttp2_session_want_read(httpc->h2) &&
-    !nghttp2_session_want_write(httpc->h2);
-}
-
-/*
  * h2_process_pending_input() processes pending input left in
  * httpc->inbuf.  Then, call h2_session_send() to send pending data.
  * This function returns 0 if it succeeds, or -1 and error code will
  * be assigned to *err.
  */
-static int h2_process_pending_input(struct Curl_easy *data,
-                                    struct http_conn *httpc,
+static int h2_process_pending_input(struct Curl_cfilter *cf,
+                                    struct Curl_easy *data,
                                     CURLcode *err)
 {
+  struct cf_h2_ctx *ctx = cf->ctx;
   ssize_t nread;
-  char *inbuf;
   ssize_t rv;
 
-  nread = httpc->inbuflen - httpc->nread_inbuf;
-  inbuf = httpc->inbuf + httpc->nread_inbuf;
+  nread = ctx->inbuflen - ctx->nread_inbuf;
+  if(nread) {
+    char *inbuf = ctx->inbuf + ctx->nread_inbuf;
 
-  set_transfer(httpc, data); /* set the transfer */
-  rv = nghttp2_session_mem_recv(httpc->h2, (const uint8_t *)inbuf, nread);
-  if(rv < 0) {
-    failf(data,
-          "h2_process_pending_input: nghttp2_session_mem_recv() returned "
-          "%zd:%s", rv, nghttp2_strerror((int)rv));
-    *err = CURLE_RECV_ERROR;
-    return -1;
+    rv = nghttp2_session_mem_recv(ctx->h2, (const uint8_t *)inbuf, nread);
+    if(rv < 0) {
+      failf(data,
+            "h2_process_pending_input: nghttp2_session_mem_recv() returned "
+            "%zd:%s", rv, nghttp2_strerror((int)rv));
+      *err = CURLE_RECV_ERROR;
+      return -1;
+    }
+
+    if(nread == rv) {
+      DEBUGF(LOG_CF(data, cf, "all data in connection buffer processed"));
+      ctx->inbuflen = 0;
+      ctx->nread_inbuf = 0;
+    }
+    else {
+      ctx->nread_inbuf += rv;
+      DEBUGF(LOG_CF(data, cf, "h2_process_pending_input: %zu bytes left "
+                    "in connection buffer",
+                   ctx->inbuflen - ctx->nread_inbuf));
+    }
   }
 
-  if(nread == rv) {
-    H2BUGF(infof(data,
-                 "h2_process_pending_input: All data in connection buffer "
-                 "processed"));
-    httpc->inbuflen = 0;
-    httpc->nread_inbuf = 0;
-  }
-  else {
-    httpc->nread_inbuf += rv;
-    H2BUGF(infof(data,
-                 "h2_process_pending_input: %zu bytes left in connection "
-                 "buffer",
-                 httpc->inbuflen - httpc->nread_inbuf));
-  }
-
-  rv = h2_session_send(data, httpc->h2);
+  rv = h2_session_send(cf, data);
   if(rv) {
     *err = CURLE_SEND_ERROR;
     return -1;
   }
 
-  if(nghttp2_session_check_request_allowed(httpc->h2) == 0) {
+  if(nghttp2_session_check_request_allowed(ctx->h2) == 0) {
     /* No more requests are allowed in the current session, so
        the connection may not be reused. This is set when a
        GOAWAY frame has been received or when the limit of stream
        identifiers has been reached. */
-    connclose(data->conn, "http/2: No new requests allowed");
+    connclose(cf->conn, "http/2: No new requests allowed");
   }
 
-  if(should_close_session(httpc)) {
+  if(should_close_session(ctx)) {
     struct HTTP *stream = data->req.p.http;
-    H2BUGF(infof(data,
+    DEBUGF(LOG_CF(data, cf,
                  "h2_process_pending_input: nothing to do in this session"));
-    if(stream->error)
+    if(stream->reset)
+      *err = CURLE_PARTIAL_FILE;
+    else if(stream->error)
       *err = CURLE_HTTP2;
     else {
       /* not an error per se, but should still close the connection */
-      connclose(data->conn, "GOAWAY received");
+      connclose(cf->conn, "GOAWAY received");
       *err = CURLE_OK;
     }
     return -1;
@@ -1478,79 +1530,70 @@
   return 0;
 }
 
-/*
- * Called from transfer.c:done_sending when we stop uploading.
- */
-CURLcode Curl_http2_done_sending(struct Curl_easy *data,
-                                 struct connectdata *conn)
+static CURLcode http2_data_done_send(struct Curl_cfilter *cf,
+                                     struct Curl_easy *data)
 {
+  struct cf_h2_ctx *ctx = cf->ctx;
   CURLcode result = CURLE_OK;
+  struct HTTP *stream = data->req.p.http;
 
-  if((conn->handler == &Curl_handler_http2_ssl) ||
-     (conn->handler == &Curl_handler_http2)) {
-    /* make sure this is only attempted for HTTP/2 transfers */
-    struct HTTP *stream = data->req.p.http;
-    struct http_conn *httpc = &conn->proto.httpc;
-    nghttp2_session *h2 = httpc->h2;
+  if(!ctx || !ctx->h2)
+    goto out;
 
-    if(stream->upload_left) {
-      /* If the stream still thinks there's data left to upload. */
+  if(stream->upload_left) {
+    /* If the stream still thinks there's data left to upload. */
+    stream->upload_left = 0; /* DONE! */
 
-      stream->upload_left = 0; /* DONE! */
+    /* resume sending here to trigger the callback to get called again so
+       that it can signal EOF to nghttp2 */
+    (void)nghttp2_session_resume_data(ctx->h2, stream->stream_id);
+    (void)h2_process_pending_input(cf, data, &result);
+  }
 
-      /* resume sending here to trigger the callback to get called again so
-         that it can signal EOF to nghttp2 */
-      (void)nghttp2_session_resume_data(h2, stream->stream_id);
-      (void)h2_process_pending_input(data, httpc, &result);
-    }
+  /* If nghttp2 still has pending frames unsent */
+  if(nghttp2_session_want_write(ctx->h2)) {
+    struct SingleRequest *k = &data->req;
+    int rv;
 
-    /* If nghttp2 still has pending frames unsent */
-    if(nghttp2_session_want_write(h2)) {
-      struct SingleRequest *k = &data->req;
-      int rv;
+    DEBUGF(LOG_CF(data, cf, "HTTP/2 still wants to send data"));
 
-      H2BUGF(infof(data, "HTTP/2 still wants to send data (easy %p)", data));
+    /* and attempt to send the pending frames */
+    rv = h2_session_send(cf, data);
+    if(rv)
+      result = CURLE_SEND_ERROR;
 
-      /* and attempt to send the pending frames */
-      rv = h2_session_send(data, h2);
-      if(rv)
-        result = CURLE_SEND_ERROR;
-
-      if(nghttp2_session_want_write(h2)) {
-         /* re-set KEEP_SEND to make sure we are called again */
-         k->keepon |= KEEP_SEND;
-      }
+    if(nghttp2_session_want_write(ctx->h2)) {
+       /* re-set KEEP_SEND to make sure we are called again */
+       k->keepon |= KEEP_SEND;
     }
   }
+
+out:
   return result;
 }
 
-static ssize_t http2_handle_stream_close(struct connectdata *conn,
+static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf,
                                          struct Curl_easy *data,
                                          struct HTTP *stream, CURLcode *err)
 {
-  struct http_conn *httpc = &conn->proto.httpc;
+  struct cf_h2_ctx *ctx = cf->ctx;
 
-  if(httpc->pause_stream_id == stream->stream_id) {
-    httpc->pause_stream_id = 0;
+  if(ctx->pause_stream_id == stream->stream_id) {
+    ctx->pause_stream_id = 0;
   }
 
-  drained_transfer(data, httpc);
+  drained_transfer(cf, data);
 
-  if(httpc->pause_stream_id == 0) {
-    if(h2_process_pending_input(data, httpc, err) != 0) {
+  if(ctx->pause_stream_id == 0) {
+    if(h2_process_pending_input(cf, data, err) != 0) {
       return -1;
     }
   }
 
-  DEBUGASSERT(data->state.drain == 0);
-
-  /* Reset to FALSE to prevent infinite loop in readwrite_data function. */
-  stream->closed = FALSE;
   if(stream->error == NGHTTP2_REFUSED_STREAM) {
-    H2BUGF(infof(data, "REFUSED_STREAM (%u), try again on a new connection",
-                 stream->stream_id));
-    connclose(conn, "REFUSED_STREAM"); /* don't use this anymore */
+    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] REFUSED_STREAM, try again on a new "
+                  "connection", stream->stream_id));
+    connclose(cf->conn, "REFUSED_STREAM"); /* don't use this anymore */
     data->state.refused_stream = TRUE;
     *err = CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */
     return -1;
@@ -1562,6 +1605,11 @@
     *err = CURLE_HTTP2_STREAM;
     return -1;
   }
+  else if(stream->reset) {
+    failf(data, "HTTP/2 stream %u was reset", stream->stream_id);
+    *err = stream->bodystarted? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR;
+    return -1;
+  }
 
   if(!stream->bodystarted) {
     failf(data, "HTTP/2 stream %u was closed cleanly, but before getting "
@@ -1597,10 +1645,24 @@
 
   stream->close_handled = TRUE;
 
-  H2BUGF(infof(data, "http2_recv returns 0, http2_handle_stream_close"));
+  DEBUGF(LOG_CF(data, cf, "[h2sid=%u] closed cleanly", stream->stream_id));
   return 0;
 }
 
+static int sweight_wanted(const struct Curl_easy *data)
+{
+  /* 0 weight is not set by user and we take the nghttp2 default one */
+  return data->set.priority.weight?
+    data->set.priority.weight : NGHTTP2_DEFAULT_WEIGHT;
+}
+
+static int sweight_in_effect(const struct Curl_easy *data)
+{
+  /* 0 weight is not set by user and we take the nghttp2 default one */
+  return data->state.priority.weight?
+    data->state.priority.weight : NGHTTP2_DEFAULT_WEIGHT;
+}
+
 /*
  * h2_pri_spec() fills in the pri_spec struct, used by nghttp2 to send weight
  * and dependency to the peer. It also stores the updated values in the state
@@ -1610,14 +1672,14 @@
 static void h2_pri_spec(struct Curl_easy *data,
                         nghttp2_priority_spec *pri_spec)
 {
-  struct HTTP *depstream = (data->set.stream_depends_on?
-                            data->set.stream_depends_on->req.p.http:NULL);
+  struct Curl_data_priority *prio = &data->set.priority;
+  struct HTTP *depstream = (prio->parent?
+                            prio->parent->req.p.http:NULL);
   int32_t depstream_id = depstream? depstream->stream_id:0;
-  nghttp2_priority_spec_init(pri_spec, depstream_id, data->set.stream_weight,
-                             data->set.stream_depends_e);
-  data->state.stream_weight = data->set.stream_weight;
-  data->state.stream_depends_e = data->set.stream_depends_e;
-  data->state.stream_depends_on = data->set.stream_depends_on;
+  nghttp2_priority_spec_init(pri_spec, depstream_id,
+                             sweight_wanted(data),
+                             data->set.priority.exclusive);
+  data->state.priority = *prio;
 }
 
 /*
@@ -1625,53 +1687,81 @@
  * dependency settings and if so it submits a PRIORITY frame with the updated
  * info.
  */
-static int h2_session_send(struct Curl_easy *data,
-                           nghttp2_session *h2)
+static CURLcode h2_session_send(struct Curl_cfilter *cf,
+                                struct Curl_easy *data)
 {
+  struct cf_h2_ctx *ctx = cf->ctx;
   struct HTTP *stream = data->req.p.http;
-  struct http_conn *httpc = &data->conn->proto.httpc;
-  set_transfer(httpc, data);
-  if((data->set.stream_weight != data->state.stream_weight) ||
-     (data->set.stream_depends_e != data->state.stream_depends_e) ||
-     (data->set.stream_depends_on != data->state.stream_depends_on) ) {
+  int rv = 0;
+
+  if((sweight_wanted(data) != sweight_in_effect(data)) ||
+     (data->set.priority.exclusive != data->state.priority.exclusive) ||
+     (data->set.priority.parent != data->state.priority.parent) ) {
     /* send new weight and/or dependency */
     nghttp2_priority_spec pri_spec;
-    int rv;
 
     h2_pri_spec(data, &pri_spec);
-
-    H2BUGF(infof(data, "Queuing PRIORITY on stream %u (easy %p)",
-                 stream->stream_id, data));
+    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] Queuing PRIORITY",
+                  stream->stream_id));
     DEBUGASSERT(stream->stream_id != -1);
-    rv = nghttp2_submit_priority(h2, NGHTTP2_FLAG_NONE, stream->stream_id,
-                                 &pri_spec);
+    rv = nghttp2_submit_priority(ctx->h2, NGHTTP2_FLAG_NONE,
+                                 stream->stream_id, &pri_spec);
     if(rv)
-      return rv;
+      goto out;
   }
 
-  return nghttp2_session_send(h2);
+  rv = nghttp2_session_send(ctx->h2);
+out:
+  if(nghttp2_is_fatal(rv)) {
+    DEBUGF(LOG_CF(data, cf, "nghttp2_session_send error (%s)%d",
+                  nghttp2_strerror(rv), rv));
+    return CURLE_SEND_ERROR;
+  }
+  return flush_output(cf, data);
 }
 
-static ssize_t http2_recv(struct Curl_easy *data, int sockindex,
-                          char *mem, size_t len, CURLcode *err)
+static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
+                          char *buf, size_t len, CURLcode *err)
 {
-  ssize_t nread;
-  struct connectdata *conn = data->conn;
-  struct http_conn *httpc = &conn->proto.httpc;
+  struct cf_h2_ctx *ctx = cf->ctx;
   struct HTTP *stream = data->req.p.http;
+  ssize_t nread = -1;
+  struct cf_call_data save;
+  bool conn_is_closed = FALSE;
 
-  (void)sockindex; /* we always do HTTP2 on sockindex 0 */
+  CF_DATA_SAVE(save, cf, data);
 
-  if(should_close_session(httpc)) {
-    H2BUGF(infof(data,
-                 "http2_recv: nothing to do in this session"));
-    if(conn->bits.close) {
+  /* If the h2 session has told us to GOAWAY with an error AND
+   * indicated the highest stream id it has processes AND
+   * the stream we are trying to read has a higher id, this
+   * means we will most likely not receive any more for it.
+   * Treat this as if the server explicitly had RST the stream */
+  if((ctx->goaway && ctx->goaway_error &&
+      ctx->last_stream_id > 0 &&
+      ctx->last_stream_id < stream->stream_id)) {
+    stream->reset = TRUE;
+  }
+
+  /* If a stream is RST, it does not matter what state the h2 session
+   * is in, our answer to receiving data is always the same. */
+  if(stream->reset) {
+    *err = stream->bodystarted? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR;
+    nread = -1;
+    goto out;
+  }
+
+  if(should_close_session(ctx)) {
+    DEBUGF(LOG_CF(data, cf, "http2_recv: nothing to do in this session"));
+    if(cf->conn->bits.close) {
       /* already marked for closure, return OK and we're done */
+      drained_transfer(cf, data);
       *err = CURLE_OK;
-      return 0;
+      nread = 0;
+      goto out;
     }
     *err = CURLE_HTTP2;
-    return -1;
+    nread = -1;
+    goto out;
   }
 
   /* Nullify here because we call nghttp2_session_send() and they
@@ -1690,74 +1780,67 @@
     size_t left =
       Curl_dyn_len(&stream->header_recvbuf) - stream->nread_header_recvbuf;
     size_t ncopy = CURLMIN(len, left);
-    memcpy(mem, Curl_dyn_ptr(&stream->header_recvbuf) +
+    memcpy(buf, Curl_dyn_ptr(&stream->header_recvbuf) +
            stream->nread_header_recvbuf, ncopy);
     stream->nread_header_recvbuf += ncopy;
 
-    H2BUGF(infof(data, "http2_recv: Got %d bytes from header_recvbuf",
-                 (int)ncopy));
-    return ncopy;
+    DEBUGF(LOG_CF(data, cf, "recv: Got %d bytes from header_recvbuf",
+                  (int)ncopy));
+    nread = ncopy;
+    goto out;
   }
 
-  H2BUGF(infof(data, "http2_recv: easy %p (stream %u) win %u/%u",
-               data, stream->stream_id,
-               nghttp2_session_get_local_window_size(httpc->h2),
-               nghttp2_session_get_stream_local_window_size(httpc->h2,
-                                                            stream->stream_id)
+  DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_recv: win %u/%u",
+                stream->stream_id,
+                nghttp2_session_get_local_window_size(ctx->h2),
+                nghttp2_session_get_stream_local_window_size(ctx->h2,
+                                                             stream->stream_id)
            ));
 
-  if((data->state.drain) && stream->memlen) {
-    H2BUGF(infof(data, "http2_recv: DRAIN %zu bytes stream %u (%p => %p)",
-                 stream->memlen, stream->stream_id,
-                 stream->mem, mem));
-    if(mem != stream->mem) {
+  if(stream->memlen) {
+    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv: DRAIN %zu bytes (%p => %p)",
+                  stream->stream_id, stream->memlen,
+                  (void *)stream->mem, (void *)buf));
+    if(buf != stream->mem) {
       /* if we didn't get the same buffer this time, we must move the data to
          the beginning */
-      memmove(mem, stream->mem, stream->memlen);
+      memmove(buf, stream->mem, stream->memlen);
       stream->len = len - stream->memlen;
-      stream->mem = mem;
+      stream->mem = buf;
     }
-    if(httpc->pause_stream_id == stream->stream_id && !stream->pausedata) {
+
+    if(ctx->pause_stream_id == stream->stream_id && !stream->pausedata) {
       /* We have paused nghttp2, but we have no pause data (see
          on_data_chunk_recv). */
-      httpc->pause_stream_id = 0;
-      if(h2_process_pending_input(data, httpc, err) != 0) {
-        return -1;
+      ctx->pause_stream_id = 0;
+      if(h2_process_pending_input(cf, data, err) != 0) {
+        nread = -1;
+        goto out;
       }
     }
   }
   else if(stream->pausedata) {
-    DEBUGASSERT(httpc->pause_stream_id == stream->stream_id);
+    DEBUGASSERT(ctx->pause_stream_id == stream->stream_id);
     nread = CURLMIN(len, stream->pauselen);
-    memcpy(mem, stream->pausedata, nread);
+    memcpy(buf, stream->pausedata, nread);
 
     stream->pausedata += nread;
     stream->pauselen -= nread;
+    drain_this(cf, data);
 
     if(stream->pauselen == 0) {
-      H2BUGF(infof(data, "Unpaused by stream %u", stream->stream_id));
-      DEBUGASSERT(httpc->pause_stream_id == stream->stream_id);
-      httpc->pause_stream_id = 0;
+      DEBUGF(LOG_CF(data, cf, "[h2sid=%u] Unpaused", stream->stream_id));
+      DEBUGASSERT(ctx->pause_stream_id == stream->stream_id);
+      ctx->pause_stream_id = 0;
 
       stream->pausedata = NULL;
       stream->pauselen = 0;
-
-      /* When NGHTTP2_ERR_PAUSE is returned from
-         data_source_read_callback, we might not process DATA frame
-         fully.  Calling nghttp2_session_mem_recv() again will
-         continue to process DATA frame, but if there is no incoming
-         frames, then we have to call it again with 0-length data.
-         Without this, on_stream_close callback will not be called,
-         and stream could be hanged. */
-      if(h2_process_pending_input(data, httpc, err) != 0) {
-        return -1;
-      }
     }
-    H2BUGF(infof(data, "http2_recv: returns unpaused %zd bytes on stream %u",
-                 nread, stream->stream_id));
-    return nread;
+    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv: returns unpaused %zd bytes",
+                  stream->stream_id, nread));
+    goto out;
   }
-  else if(httpc->pause_stream_id) {
+  else if(ctx->pause_stream_id) {
     /* If a stream paused nghttp2_session_mem_recv previously, and has
        not processed all data, it still refers to the buffer in
        nghttp2_session.  If we call nghttp2_session_mem_recv(), we may
@@ -1766,156 +1849,192 @@
        socket is not read.  But it seems that usually streams are
        notified with its drain property, and socket is read again
        quickly. */
-    if(stream->closed)
+    if(stream->closed) {
       /* closed overrides paused */
-      return 0;
-    H2BUGF(infof(data, "stream %u is paused, pause id: %u",
-                 stream->stream_id, httpc->pause_stream_id));
+      drained_transfer(cf, data);
+      nread = 0;
+      goto out;
+    }
+    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] is paused, pause h2sid: %u",
+                  stream->stream_id, ctx->pause_stream_id));
     *err = CURLE_AGAIN;
-    return -1;
+    nread = -1;
+    goto out;
   }
   else {
-    /* remember where to store incoming data for this stream and how big the
-       buffer is */
-    stream->mem = mem;
+    /* We have nothing buffered for `data` and no other stream paused
+     * the processing of incoming data, we can therefore read new data
+     * from the network.
+     * If DATA is coming for this stream, we want to store it ad the
+     * `buf` passed in right away - saving us a copy.
+     */
+    stream->mem = buf;
     stream->len = len;
     stream->memlen = 0;
 
-    if(httpc->inbuflen == 0) {
-      nread = ((Curl_recv *)httpc->recv_underlying)(
-        data, FIRSTSOCKET, httpc->inbuf, H2_BUFSIZE, err);
-
-      if(nread == -1) {
-        if(*err != CURLE_AGAIN)
-          failf(data, "Failed receiving HTTP2 data");
-        else if(stream->closed)
-          /* received when the stream was already closed! */
-          return http2_handle_stream_close(conn, data, stream, err);
-
+    if(ctx->inbuflen > 0) {
+      DEBUGF(LOG_CF(data, cf, "[h2sid=%u] %zd bytes in inbuf",
+                    stream->stream_id, ctx->inbuflen - ctx->nread_inbuf));
+      if(h2_process_pending_input(cf, data, err))
         return -1;
-      }
+    }
 
-      if(nread == 0) {
-        if(!stream->closed) {
-          /* This will happen when the server or proxy server is SIGKILLed
-             during data transfer. We should emit an error since our data
-             received may be incomplete. */
-          failf(data, "HTTP/2 stream %u was not closed cleanly before"
-                " end of the underlying stream",
-                stream->stream_id);
-          *err = CURLE_HTTP2_STREAM;
-          return -1;
+    while(stream->memlen == 0 &&       /* have no data for this stream */
+          !stream->closed &&           /* and it is not closed/reset */
+          !ctx->pause_stream_id &&     /* we are not paused either */
+          ctx->inbuflen == 0 &&       /* and out input buffer is empty */
+          !conn_is_closed) {          /* and connection is not closed */
+      /* Receive data from the "lower" filters */
+      nread = Curl_conn_cf_recv(cf->next, data, ctx->inbuf, H2_BUFSIZE, err);
+      if(nread < 0) {
+        DEBUGASSERT(*err);
+        if(*err == CURLE_AGAIN) {
+          break;
         }
-
-        H2BUGF(infof(data, "end of stream"));
-        *err = CURLE_OK;
-        return 0;
+        failf(data, "Failed receiving HTTP2 data");
+        conn_is_closed = TRUE;
       }
-
-      H2BUGF(infof(data, "nread=%zd", nread));
-
-      httpc->inbuflen = nread;
-
-      DEBUGASSERT(httpc->nread_inbuf == 0);
-    }
-    else {
-      nread = httpc->inbuflen - httpc->nread_inbuf;
-      (void)nread;  /* silence warning, used in debug */
-      H2BUGF(infof(data, "Use data left in connection buffer, nread=%zd",
-                   nread));
+      else if(nread == 0) {
+        DEBUGF(LOG_CF(data, cf, "[h2sid=%u] underlying connection is closed",
+                      stream->stream_id));
+        conn_is_closed = TRUE;
+      }
+      else {
+        DEBUGF(LOG_CF(data, cf, "[h2sid=%u] read %zd from connection",
+                      stream->stream_id, nread));
+        ctx->inbuflen = nread;
+        DEBUGASSERT(ctx->nread_inbuf == 0);
+        if(h2_process_pending_input(cf, data, err))
+          return -1;
+      }
     }
 
-    if(h2_process_pending_input(data, httpc, err))
-      return -1;
   }
+
   if(stream->memlen) {
     ssize_t retlen = stream->memlen;
-    H2BUGF(infof(data, "http2_recv: returns %zd for stream %u",
-                 retlen, stream->stream_id));
+
+    /* TODO: all this buffer handling is very brittle */
+    stream->len += stream->memlen;
     stream->memlen = 0;
 
-    if(httpc->pause_stream_id == stream->stream_id) {
+    if(ctx->pause_stream_id == stream->stream_id) {
       /* data for this stream is returned now, but this stream caused a pause
          already so we need it called again asap */
-      H2BUGF(infof(data, "Data returned for PAUSED stream %u",
-                   stream->stream_id));
-    }
-    else if(!stream->closed) {
-      drained_transfer(data, httpc);
-    }
-    else
-      /* this stream is closed, trigger a another read ASAP to detect that */
+      DEBUGF(LOG_CF(data, cf, "[h2sid=%u] Data returned for PAUSED stream",
+                    stream->stream_id));
+      drain_this(cf, data);
       Curl_expire(data, 0, EXPIRE_RUN_NOW);
+    }
+    else if(stream->closed) {
+      if(stream->reset || stream->error) {
+        nread = http2_handle_stream_close(cf, data, stream, err);
+        goto out;
+      }
+      /* this stream is closed, trigger a another read ASAP to detect that */
+      DEBUGF(LOG_CF(data, cf, "[h2sid=%u] is closed now, run again",
+                    stream->stream_id));
+      drain_this(cf, data);
+      Curl_expire(data, 0, EXPIRE_RUN_NOW);
+    }
+    else {
+      drained_transfer(cf, data);
+    }
 
-    return retlen;
+    *err = CURLE_OK;
+    nread = retlen;
+    goto out;
   }
-  if(stream->closed)
-    return http2_handle_stream_close(conn, data, stream, err);
+
+  if(conn_is_closed && !stream->closed) {
+    /* underlying connection is closed and we have nothing for the stream.
+     * Treat this as a RST */
+    stream->closed = stream->reset = TRUE;
+      failf(data, "HTTP/2 stream %u was not closed cleanly before"
+            " end of the underlying connection",
+            stream->stream_id);
+  }
+
+  if(stream->closed) {
+    nread = http2_handle_stream_close(cf, data, stream, err);
+    goto out;
+  }
+
+  if(!data->state.drain && Curl_conn_cf_data_pending(cf->next, data)) {
+    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] pending data, set drain",
+                  stream->stream_id));
+    drain_this(cf, data);
+  }
   *err = CURLE_AGAIN;
-  H2BUGF(infof(data, "http2_recv returns AGAIN for stream %u",
-               stream->stream_id));
-  return -1;
+  nread = -1;
+out:
+  DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_recv -> %zd, %d",
+                stream->stream_id, nread, *err));
+  CF_DATA_RESTORE(cf, save);
+  return nread;
 }
 
-static ssize_t http2_send(struct Curl_easy *data, int sockindex,
-                          const void *mem, size_t len, CURLcode *err)
+static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
+                          const void *buf, size_t len, CURLcode *err)
 {
   /*
    * Currently, we send request in this function, but this function is also
    * used to send request body. It would be nice to add dedicated function for
    * request.
    */
+  struct cf_h2_ctx *ctx = cf->ctx;
   int rv;
-  struct connectdata *conn = data->conn;
-  struct http_conn *httpc = &conn->proto.httpc;
   struct HTTP *stream = data->req.p.http;
   nghttp2_nv *nva = NULL;
   size_t nheader;
   nghttp2_data_provider data_prd;
   int32_t stream_id;
-  nghttp2_session *h2 = httpc->h2;
   nghttp2_priority_spec pri_spec;
   CURLcode result;
   struct h2h3req *hreq;
+  struct cf_call_data save;
+  ssize_t nwritten;
 
-  (void)sockindex;
-
-  H2BUGF(infof(data, "http2_send len=%zu", len));
+  CF_DATA_SAVE(save, cf, data);
+  DEBUGF(LOG_CF(data, cf, "cf_send(len=%zu) start", len));
 
   if(stream->stream_id != -1) {
     if(stream->close_handled) {
       infof(data, "stream %u closed", stream->stream_id);
       *err = CURLE_HTTP2_STREAM;
-      return -1;
+      nwritten = -1;
+      goto out;
     }
     else if(stream->closed) {
-      return http2_handle_stream_close(conn, data, stream, err);
+      nwritten = http2_handle_stream_close(cf, data, stream, err);
+      goto out;
     }
     /* If stream_id != -1, we have dispatched request HEADERS, and now
        are going to send or sending request body in DATA frame */
-    stream->upload_mem = mem;
+    stream->upload_mem = buf;
     stream->upload_len = len;
-    rv = nghttp2_session_resume_data(h2, stream->stream_id);
+    rv = nghttp2_session_resume_data(ctx->h2, stream->stream_id);
     if(nghttp2_is_fatal(rv)) {
       *err = CURLE_SEND_ERROR;
-      return -1;
+      nwritten = -1;
+      goto out;
     }
-    rv = h2_session_send(data, h2);
-    if(nghttp2_is_fatal(rv)) {
-      *err = CURLE_SEND_ERROR;
-      return -1;
+    result = h2_session_send(cf, data);
+    if(result) {
+      *err = result;
+      nwritten = -1;
+      goto out;
     }
-    len -= stream->upload_len;
 
-    /* Nullify here because we call nghttp2_session_send() and they
-       might refer to the old buffer. */
+    nwritten = (ssize_t)len - (ssize_t)stream->upload_len;
     stream->upload_mem = NULL;
     stream->upload_len = 0;
 
-    if(should_close_session(httpc)) {
-      H2BUGF(infof(data, "http2_send: nothing to do in this session"));
+    if(should_close_session(ctx)) {
+      DEBUGF(LOG_CF(data, cf, "send: nothing to do in this session"));
       *err = CURLE_HTTP2;
-      return -1;
+      nwritten = -1;
+      goto out;
     }
 
     if(stream->upload_left) {
@@ -1923,29 +2042,40 @@
          following API will make nghttp2_session_want_write() return
          nonzero if remote window allows it, which then libcurl checks
          socket is writable or not.  See http2_perform_getsock(). */
-      nghttp2_session_resume_data(h2, stream->stream_id);
+      nghttp2_session_resume_data(ctx->h2, stream->stream_id);
     }
 
-#ifdef DEBUG_HTTP2
-    if(!len) {
-      infof(data, "http2_send: easy %p (stream %u) win %u/%u",
-            data, stream->stream_id,
-            nghttp2_session_get_remote_window_size(httpc->h2),
-            nghttp2_session_get_stream_remote_window_size(httpc->h2,
-                                                          stream->stream_id)
-        );
-
+    if(!nwritten) {
+      size_t rwin = nghttp2_session_get_stream_remote_window_size(ctx->h2,
+                                                          stream->stream_id);
+      DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_send: win %u/%zu",
+             stream->stream_id,
+             nghttp2_session_get_remote_window_size(ctx->h2), rwin));
+        if(rwin == 0) {
+          /* We cannot upload more as the stream's remote window size
+           * is 0. We need to receive WIN_UPDATEs before we can continue.
+           */
+          data->req.keepon |= KEEP_SEND_HOLD;
+          DEBUGF(LOG_CF(data, cf, "[h2sid=%u] holding send as remote flow "
+                 "window is exhausted", stream->stream_id));
+        }
     }
-    infof(data, "http2_send returns %zu for stream %u", len,
-          stream->stream_id);
-#endif
-    return len;
+    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_send returns %zd ",
+           stream->stream_id, nwritten));
+
+    /* handled writing BODY for open stream. */
+    goto out;
   }
-
-  result = Curl_pseudo_headers(data, mem, len, &hreq);
+  /* Stream has not been opened yet. `buf` is expected to contain
+   * request headers. */
+  /* TODO: this assumes that the `buf` and `len` we are called with
+   * is *all* HEADERs and no body. We have no way to determine here
+   * if that is indeed the case. */
+  result = Curl_pseudo_headers(data, buf, len, NULL, &hreq);
   if(result) {
     *err = result;
-    return -1;
+    nwritten = -1;
+    goto out;
   }
   nheader = hreq->entries;
 
@@ -1953,7 +2083,8 @@
   if(!nva) {
     Curl_pseudo_free(hreq);
     *err = CURLE_OUT_OF_MEMORY;
-    return -1;
+    nwritten = -1;
+    goto out;
   }
   else {
     unsigned int i;
@@ -1969,8 +2100,8 @@
 
   h2_pri_spec(data, &pri_spec);
 
-  H2BUGF(infof(data, "http2_send request allowed %d (easy handle %p)",
-               nghttp2_session_check_request_allowed(h2), (void *)data));
+  DEBUGF(LOG_CF(data, cf, "send request allowed %d (easy handle %p)",
+                nghttp2_session_check_request_allowed(ctx->h2), (void *)data));
 
   switch(data->state.httpreq) {
   case HTTPREQ_POST:
@@ -1985,42 +2116,43 @@
 
     data_prd.read_callback = data_source_read_callback;
     data_prd.source.ptr = NULL;
-    stream_id = nghttp2_submit_request(h2, &pri_spec, nva, nheader,
+    stream_id = nghttp2_submit_request(ctx->h2, &pri_spec, nva, nheader,
                                        &data_prd, data);
     break;
   default:
-    stream_id = nghttp2_submit_request(h2, &pri_spec, nva, nheader,
+    stream_id = nghttp2_submit_request(ctx->h2, &pri_spec, nva, nheader,
                                        NULL, data);
   }
 
   Curl_safefree(nva);
 
   if(stream_id < 0) {
-    H2BUGF(infof(data,
-                 "http2_send() nghttp2_submit_request error (%s)%u",
-                 nghttp2_strerror(stream_id), stream_id));
+    DEBUGF(LOG_CF(data, cf, "send: nghttp2_submit_request error (%s)%u",
+                  nghttp2_strerror(stream_id), stream_id));
     *err = CURLE_SEND_ERROR;
-    return -1;
+    nwritten = -1;
+    goto out;
   }
 
   infof(data, "Using Stream ID: %u (easy handle %p)",
         stream_id, (void *)data);
   stream->stream_id = stream_id;
+  /* See TODO above. We assume that the whole buf was consumed by
+   * generating the request headers. */
+  nwritten = len;
 
-  rv = h2_session_send(data, h2);
-  if(rv) {
-    H2BUGF(infof(data,
-                 "http2_send() nghttp2_session_send error (%s)%d",
-                 nghttp2_strerror(rv), rv));
-
-    *err = CURLE_SEND_ERROR;
-    return -1;
+  result = h2_session_send(cf, data);
+  if(result) {
+    *err = result;
+    nwritten = -1;
+    goto out;
   }
 
-  if(should_close_session(httpc)) {
-    H2BUGF(infof(data, "http2_send: nothing to do in this session"));
+  if(should_close_session(ctx)) {
+    DEBUGF(LOG_CF(data, cf, "send: nothing to do in this session"));
     *err = CURLE_HTTP2;
-    return -1;
+    nwritten = -1;
+    goto out;
   }
 
   /* If whole HEADERS frame was sent off to the underlying socket, the nghttp2
@@ -2030,169 +2162,126 @@
      results that no writable socket check is performed. To workaround this,
      we issue nghttp2_session_resume_data() here to bring back DATA
      transmission from deferred state. */
-  nghttp2_session_resume_data(h2, stream->stream_id);
+  nghttp2_session_resume_data(ctx->h2, stream->stream_id);
 
-  return len;
+out:
+  CF_DATA_RESTORE(cf, save);
+  return nwritten;
 }
 
-CURLcode Curl_http2_setup(struct Curl_easy *data,
-                          struct connectdata *conn)
+static int cf_h2_get_select_socks(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  curl_socket_t *sock)
 {
-  CURLcode result;
-  struct http_conn *httpc = &conn->proto.httpc;
+  struct cf_h2_ctx *ctx = cf->ctx;
+  struct SingleRequest *k = &data->req;
   struct HTTP *stream = data->req.p.http;
+  int bitmap = GETSOCK_BLANK;
+  struct cf_call_data save;
 
-  DEBUGASSERT(data->state.buffer);
+  CF_DATA_SAVE(save, cf, data);
+  sock[0] = Curl_conn_cf_get_socket(cf, data);
 
-  stream->stream_id = -1;
+  if(!(k->keepon & KEEP_RECV_PAUSE))
+    /* Unless paused - in an HTTP/2 connection we can basically always get a
+       frame so we should always be ready for one */
+    bitmap |= GETSOCK_READSOCK(0);
 
-  Curl_dyn_init(&stream->header_recvbuf, DYN_H2_HEADERS);
-  Curl_dyn_init(&stream->trailer_recvbuf, DYN_H2_TRAILERS);
+  /* we're (still uploading OR the HTTP/2 layer wants to send data) AND
+     there's a window to send data in */
+  if((((k->keepon & KEEP_SENDBITS) == KEEP_SEND) ||
+      nghttp2_session_want_write(ctx->h2)) &&
+     (nghttp2_session_get_remote_window_size(ctx->h2) &&
+      nghttp2_session_get_stream_remote_window_size(ctx->h2,
+                                                    stream->stream_id)))
+    bitmap |= GETSOCK_WRITESOCK(0);
 
-  stream->upload_left = 0;
-  stream->upload_mem = NULL;
-  stream->upload_len = 0;
-  stream->mem = data->state.buffer;
-  stream->len = data->set.buffer_size;
-
-  multi_connchanged(data->multi);
-  /* below this point only connection related inits are done, which only needs
-     to be done once per connection */
-
-  if((conn->handler == &Curl_handler_http2_ssl) ||
-     (conn->handler == &Curl_handler_http2))
-    return CURLE_OK; /* already done */
-
-  if(conn->handler->flags & PROTOPT_SSL)
-    conn->handler = &Curl_handler_http2_ssl;
-  else
-    conn->handler = &Curl_handler_http2;
-
-  result = http2_init(data, conn);
-  if(result) {
-    Curl_dyn_free(&stream->header_recvbuf);
-    return result;
-  }
-
-  infof(data, "Using HTTP2, server supports multiplexing");
-
-  conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
-  conn->httpversion = 20;
-  conn->bundle->multiuse = BUNDLE_MULTIPLEX;
-
-  httpc->inbuflen = 0;
-  httpc->nread_inbuf = 0;
-
-  httpc->pause_stream_id = 0;
-  httpc->drain_total = 0;
-
-  return CURLE_OK;
+  CF_DATA_RESTORE(cf, save);
+  return bitmap;
 }
 
-CURLcode Curl_http2_switched(struct Curl_easy *data,
-                             const char *mem, size_t nread)
+
+static CURLcode cf_h2_connect(struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
+                              bool blocking, bool *done)
 {
-  CURLcode result;
-  struct connectdata *conn = data->conn;
-  struct http_conn *httpc = &conn->proto.httpc;
-  int rv;
-  struct HTTP *stream = data->req.p.http;
+  struct cf_h2_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OK;
+  struct cf_call_data save;
 
-  result = Curl_http2_setup(data, conn);
-  if(result)
-    return result;
-
-  httpc->recv_underlying = conn->recv[FIRSTSOCKET];
-  httpc->send_underlying = conn->send[FIRSTSOCKET];
-  conn->recv[FIRSTSOCKET] = http2_recv;
-  conn->send[FIRSTSOCKET] = http2_send;
-
-  if(data->req.upgr101 == UPGR101_RECEIVED) {
-    /* stream 1 is opened implicitly on upgrade */
-    stream->stream_id = 1;
-    /* queue SETTINGS frame (again) */
-    rv = nghttp2_session_upgrade2(httpc->h2, httpc->binsettings, httpc->binlen,
-                                  data->state.httpreq == HTTPREQ_HEAD, NULL);
-    if(rv) {
-      failf(data, "nghttp2_session_upgrade2() failed: %s(%d)",
-            nghttp2_strerror(rv), rv);
-      return CURLE_HTTP2;
-    }
-
-    rv = nghttp2_session_set_stream_user_data(httpc->h2,
-                                              stream->stream_id,
-                                              data);
-    if(rv) {
-      infof(data, "http/2: failed to set user_data for stream %u",
-            stream->stream_id);
-      DEBUGASSERT(0);
-    }
-  }
-  else {
-    populate_settings(data, httpc);
-
-    /* stream ID is unknown at this point */
-    stream->stream_id = -1;
-    rv = nghttp2_submit_settings(httpc->h2, NGHTTP2_FLAG_NONE,
-                                 httpc->local_settings,
-                                 httpc->local_settings_num);
-    if(rv) {
-      failf(data, "nghttp2_submit_settings() failed: %s(%d)",
-            nghttp2_strerror(rv), rv);
-      return CURLE_HTTP2;
-    }
-  }
-
-  rv = nghttp2_session_set_local_window_size(httpc->h2, NGHTTP2_FLAG_NONE, 0,
-                                             HTTP2_HUGE_WINDOW_SIZE);
-  if(rv) {
-    failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)",
-          nghttp2_strerror(rv), rv);
-    return CURLE_HTTP2;
-  }
-
-  /* we are going to copy mem to httpc->inbuf.  This is required since
-     mem is part of buffer pointed by stream->mem, and callbacks
-     called by nghttp2_session_mem_recv() will write stream specific
-     data into stream->mem, overwriting data already there. */
-  if(H2_BUFSIZE < nread) {
-    failf(data, "connection buffer size is too small to store data following "
-          "HTTP Upgrade response header: buflen=%d, datalen=%zu",
-          H2_BUFSIZE, nread);
-    return CURLE_HTTP2;
-  }
-
-  infof(data, "Copying HTTP/2 data in stream buffer to connection buffer"
-        " after upgrade: len=%zu",
-        nread);
-
-  if(nread)
-    memcpy(httpc->inbuf, mem, nread);
-
-  httpc->inbuflen = nread;
-
-  DEBUGASSERT(httpc->nread_inbuf == 0);
-
-  if(-1 == h2_process_pending_input(data, httpc, &result))
-    return CURLE_HTTP2;
-
-  return CURLE_OK;
-}
-
-CURLcode Curl_http2_stream_pause(struct Curl_easy *data, bool pause)
-{
-  DEBUGASSERT(data);
-  DEBUGASSERT(data->conn);
-  /* if it isn't HTTP/2, we're done */
-  if(!(data->conn->handler->protocol & PROTO_FAMILY_HTTP) ||
-     !data->conn->proto.httpc.h2)
+  if(cf->connected) {
+    *done = TRUE;
     return CURLE_OK;
+  }
+
+  /* Connect the lower filters first */
+  if(!cf->next->connected) {
+    result = Curl_conn_cf_connect(cf->next, data, blocking, done);
+    if(result || !*done)
+      return result;
+  }
+
+  *done = FALSE;
+
+  CF_DATA_SAVE(save, cf, data);
+  if(!ctx->h2) {
+    result = cf_h2_ctx_init(cf, data, FALSE);
+    if(result)
+      goto out;
+  }
+
+  if(-1 == h2_process_pending_input(cf, data, &result)) {
+    result = CURLE_HTTP2;
+    goto out;
+  }
+
+  *done = TRUE;
+  cf->connected = TRUE;
+  result = CURLE_OK;
+
+out:
+  CF_DATA_RESTORE(cf, save);
+  return result;
+}
+
+static void cf_h2_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_h2_ctx *ctx = cf->ctx;
+
+  if(ctx) {
+    struct cf_call_data save;
+
+    CF_DATA_SAVE(save, cf, data);
+    cf_h2_ctx_clear(ctx);
+    CF_DATA_RESTORE(cf, save);
+  }
+}
+
+static void cf_h2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_h2_ctx *ctx = cf->ctx;
+
+  (void)data;
+  if(ctx) {
+    cf_h2_ctx_free(ctx);
+    cf->ctx = NULL;
+  }
+}
+
+static CURLcode http2_data_pause(struct Curl_cfilter *cf,
+                                 struct Curl_easy *data,
+                                 bool pause)
+{
+  struct cf_h2_ctx *ctx = cf->ctx;
+
+  DEBUGASSERT(data);
 #ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE
-  else {
+  if(ctx && ctx->h2) {
     struct HTTP *stream = data->req.p.http;
-    struct http_conn *httpc = &data->conn->proto.httpc;
     uint32_t window = !pause * HTTP2_HUGE_WINDOW_SIZE;
-    int rv = nghttp2_session_set_local_window_size(httpc->h2,
+    CURLcode result;
+
+    int rv = nghttp2_session_set_local_window_size(ctx->h2,
                                                    NGHTTP2_FLAG_NONE,
                                                    stream->stream_id,
                                                    window);
@@ -2203,9 +2292,9 @@
     }
 
     /* make sure the window update gets sent */
-    rv = h2_session_send(data, httpc->h2);
-    if(rv)
-      return CURLE_SEND_ERROR;
+    result = h2_session_send(cf, data);
+    if(result)
+      return result;
 
     DEBUGF(infof(data, "Set HTTP/2 window size to %u for stream %u",
                  window, stream->stream_id));
@@ -2214,7 +2303,7 @@
     {
       /* read out the stream local window again */
       uint32_t window2 =
-        nghttp2_session_get_stream_local_window_size(httpc->h2,
+        nghttp2_session_get_stream_local_window_size(ctx->h2,
                                                      stream->stream_id);
       DEBUGF(infof(data, "HTTP/2 window size is now %u for stream %u",
                    window2, stream->stream_id));
@@ -2225,86 +2314,327 @@
   return CURLE_OK;
 }
 
-CURLcode Curl_http2_add_child(struct Curl_easy *parent,
-                              struct Curl_easy *child,
-                              bool exclusive)
+static CURLcode cf_h2_cntrl(struct Curl_cfilter *cf,
+                            struct Curl_easy *data,
+                            int event, int arg1, void *arg2)
 {
-  if(parent) {
-    struct Curl_http2_dep **tail;
-    struct Curl_http2_dep *dep = calloc(1, sizeof(struct Curl_http2_dep));
-    if(!dep)
-      return CURLE_OUT_OF_MEMORY;
-    dep->data = child;
+  CURLcode result = CURLE_OK;
+  struct cf_call_data save;
 
-    if(parent->set.stream_dependents && exclusive) {
-      struct Curl_http2_dep *node = parent->set.stream_dependents;
-      while(node) {
-        node->data->set.stream_depends_on = child;
-        node = node->next;
-      }
+  (void)arg2;
 
-      tail = &child->set.stream_dependents;
-      while(*tail)
-        tail = &(*tail)->next;
-
-      DEBUGASSERT(!*tail);
-      *tail = parent->set.stream_dependents;
-      parent->set.stream_dependents = 0;
-    }
-
-    tail = &parent->set.stream_dependents;
-    while(*tail) {
-      (*tail)->data->set.stream_depends_e = FALSE;
-      tail = &(*tail)->next;
-    }
-
-    DEBUGASSERT(!*tail);
-    *tail = dep;
+  CF_DATA_SAVE(save, cf, data);
+  switch(event) {
+  case CF_CTRL_DATA_SETUP: {
+    result = http2_data_setup(cf, data);
+    break;
   }
+  case CF_CTRL_DATA_PAUSE: {
+    result = http2_data_pause(cf, data, (arg1 != 0));
+    break;
+  }
+  case CF_CTRL_DATA_DONE_SEND: {
+    result = http2_data_done_send(cf, data);
+    break;
+  }
+  case CF_CTRL_DATA_DONE: {
+    http2_data_done(cf, data, arg1 != 0);
+    break;
+  }
+  default:
+    break;
+  }
+  CF_DATA_RESTORE(cf, save);
+  return result;
+}
 
-  child->set.stream_depends_on = parent;
-  child->set.stream_depends_e = exclusive;
+static bool cf_h2_data_pending(struct Curl_cfilter *cf,
+                               const struct Curl_easy *data)
+{
+  struct cf_h2_ctx *ctx = cf->ctx;
+  if(ctx && ctx->inbuflen > 0 && ctx->nread_inbuf > ctx->inbuflen)
+    return TRUE;
+  return cf->next? cf->next->cft->has_data_pending(cf->next, data) : FALSE;
+}
+
+static bool cf_h2_is_alive(struct Curl_cfilter *cf,
+                           struct Curl_easy *data,
+                           bool *input_pending)
+{
+  struct cf_h2_ctx *ctx = cf->ctx;
+  CURLcode result;
+  struct cf_call_data save;
+
+  CF_DATA_SAVE(save, cf, data);
+  result = (ctx && ctx->h2 && http2_connisalive(cf, data, input_pending));
+  DEBUGF(LOG_CF(data, cf, "conn alive -> %d, input_pending=%d",
+         result, *input_pending));
+  CF_DATA_RESTORE(cf, save);
+  return result;
+}
+
+static CURLcode cf_h2_keep_alive(struct Curl_cfilter *cf,
+                                 struct Curl_easy *data)
+{
+  CURLcode result;
+  struct cf_call_data save;
+
+  CF_DATA_SAVE(save, cf, data);
+  result = http2_send_ping(cf, data);
+  CF_DATA_RESTORE(cf, save);
+  return result;
+}
+
+static CURLcode cf_h2_query(struct Curl_cfilter *cf,
+                            struct Curl_easy *data,
+                            int query, int *pres1, void *pres2)
+{
+  struct cf_h2_ctx *ctx = cf->ctx;
+  struct cf_call_data save;
+  size_t effective_max;
+
+  switch(query) {
+  case CF_QUERY_MAX_CONCURRENT:
+    DEBUGASSERT(pres1);
+
+    CF_DATA_SAVE(save, cf, data);
+    if(nghttp2_session_check_request_allowed(ctx->h2) == 0) {
+      /* the limit is what we have in use right now */
+      effective_max = CONN_INUSE(cf->conn);
+    }
+    else {
+      effective_max = ctx->max_concurrent_streams;
+    }
+    *pres1 = (effective_max > INT_MAX)? INT_MAX : (int)effective_max;
+    CF_DATA_RESTORE(cf, save);
+    return CURLE_OK;
+  default:
+    break;
+  }
+  return cf->next?
+    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
+    CURLE_UNKNOWN_OPTION;
+}
+
+struct Curl_cftype Curl_cft_nghttp2 = {
+  "HTTP/2",
+  CF_TYPE_MULTIPLEX,
+  CURL_LOG_DEFAULT,
+  cf_h2_destroy,
+  cf_h2_connect,
+  cf_h2_close,
+  Curl_cf_def_get_host,
+  cf_h2_get_select_socks,
+  cf_h2_data_pending,
+  cf_h2_send,
+  cf_h2_recv,
+  cf_h2_cntrl,
+  cf_h2_is_alive,
+  cf_h2_keep_alive,
+  cf_h2_query,
+};
+
+static CURLcode http2_cfilter_add(struct Curl_cfilter **pcf,
+                                  struct Curl_easy *data,
+                                  struct connectdata *conn,
+                                  int sockindex)
+{
+  struct Curl_cfilter *cf = NULL;
+  struct cf_h2_ctx *ctx;
+  CURLcode result = CURLE_OUT_OF_MEMORY;
+
+  DEBUGASSERT(data->conn);
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx)
+    goto out;
+
+  result = Curl_cf_create(&cf, &Curl_cft_nghttp2, ctx);
+  if(result)
+    goto out;
+
+  Curl_conn_cf_add(data, conn, sockindex, cf);
+  result = CURLE_OK;
+
+out:
+  if(result)
+    cf_h2_ctx_free(ctx);
+  *pcf = result? NULL : cf;
+  return result;
+}
+
+static CURLcode http2_cfilter_insert_after(struct Curl_cfilter *cf,
+                                           struct Curl_easy *data)
+{
+  struct Curl_cfilter *cf_h2 = NULL;
+  struct cf_h2_ctx *ctx;
+  CURLcode result = CURLE_OUT_OF_MEMORY;
+
+  (void)data;
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx)
+    goto out;
+
+  result = Curl_cf_create(&cf_h2, &Curl_cft_nghttp2, ctx);
+  if(result)
+    goto out;
+
+  Curl_conn_cf_insert_after(cf, cf_h2);
+  result = CURLE_OK;
+
+out:
+  if(result)
+    cf_h2_ctx_free(ctx);
+  return result;
+}
+
+bool Curl_cf_is_http2(struct Curl_cfilter *cf, const struct Curl_easy *data)
+{
+  (void)data;
+  for(; cf; cf = cf->next) {
+    if(cf->cft == &Curl_cft_nghttp2)
+      return TRUE;
+    if(cf->cft->flags & CF_TYPE_IP_CONNECT)
+      return FALSE;
+  }
+  return FALSE;
+}
+
+bool Curl_conn_is_http2(const struct Curl_easy *data,
+                        const struct connectdata *conn,
+                        int sockindex)
+{
+  return conn? Curl_cf_is_http2(conn->cfilter[sockindex], data) : FALSE;
+}
+
+bool Curl_http2_may_switch(struct Curl_easy *data,
+                           struct connectdata *conn,
+                           int sockindex)
+{
+  (void)sockindex;
+  if(!Curl_conn_is_http2(data, conn, sockindex) &&
+     data->state.httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) {
+#ifndef CURL_DISABLE_PROXY
+    if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) {
+      /* We don't support HTTP/2 proxies yet. Also it's debatable
+         whether or not this setting should apply to HTTP/2 proxies. */
+      infof(data, "Ignoring HTTP/2 prior knowledge due to proxy");
+      return FALSE;
+    }
+#endif
+    return TRUE;
+  }
+  return FALSE;
+}
+
+CURLcode Curl_http2_switch(struct Curl_easy *data,
+                           struct connectdata *conn, int sockindex)
+{
+  struct Curl_cfilter *cf;
+  CURLcode result;
+
+  DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex));
+  DEBUGF(infof(data, DMSGI(data, sockindex, "switching to HTTP/2")));
+
+  result = http2_cfilter_add(&cf, data, conn, sockindex);
+  if(result)
+    return result;
+
+  result = cf_h2_ctx_init(cf, data, FALSE);
+  if(result)
+    return result;
+
+  conn->httpversion = 20; /* we know we're on HTTP/2 now */
+  conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
+  conn->bundle->multiuse = BUNDLE_MULTIPLEX;
+  multi_connchanged(data->multi);
+
+  if(cf->next) {
+    bool done;
+    return Curl_conn_cf_connect(cf, data, FALSE, &done);
+  }
   return CURLE_OK;
 }
 
-void Curl_http2_remove_child(struct Curl_easy *parent, struct Curl_easy *child)
+CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
-  struct Curl_http2_dep *last = 0;
-  struct Curl_http2_dep *data = parent->set.stream_dependents;
-  DEBUGASSERT(child->set.stream_depends_on == parent);
+  struct Curl_cfilter *cf_h2;
+  CURLcode result;
 
-  while(data && data->data != child) {
-    last = data;
-    data = data->next;
+  DEBUGASSERT(!Curl_cf_is_http2(cf, data));
+
+  result = http2_cfilter_insert_after(cf, data);
+  if(result)
+    return result;
+
+  cf_h2 = cf->next;
+  result = cf_h2_ctx_init(cf_h2, data, FALSE);
+  if(result)
+    return result;
+
+  cf->conn->httpversion = 20; /* we know we're on HTTP/2 now */
+  cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
+  cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX;
+  multi_connchanged(data->multi);
+
+  if(cf_h2->next) {
+    bool done;
+    return Curl_conn_cf_connect(cf_h2, data, FALSE, &done);
   }
-
-  DEBUGASSERT(data);
-
-  if(data) {
-    if(last) {
-      last->next = data->next;
-    }
-    else {
-      parent->set.stream_dependents = data->next;
-    }
-    free(data);
-  }
-
-  child->set.stream_depends_on = 0;
-  child->set.stream_depends_e = FALSE;
+  return CURLE_OK;
 }
 
-void Curl_http2_cleanup_dependencies(struct Curl_easy *data)
+CURLcode Curl_http2_upgrade(struct Curl_easy *data,
+                            struct connectdata *conn, int sockindex,
+                            const char *mem, size_t nread)
 {
-  while(data->set.stream_dependents) {
-    struct Curl_easy *tmp = data->set.stream_dependents->data;
-    Curl_http2_remove_child(data, tmp);
-    if(data->set.stream_depends_on)
-      Curl_http2_add_child(data->set.stream_depends_on, tmp, FALSE);
+  struct Curl_cfilter *cf;
+  struct cf_h2_ctx *ctx;
+  CURLcode result;
+
+  DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex));
+  DEBUGF(infof(data, DMSGI(data, sockindex, "upgrading to HTTP/2")));
+  DEBUGASSERT(data->req.upgr101 == UPGR101_RECEIVED);
+
+  result = http2_cfilter_add(&cf, data, conn, sockindex);
+  if(result)
+    return result;
+
+  DEBUGASSERT(cf->cft == &Curl_cft_nghttp2);
+  ctx = cf->ctx;
+
+  result = cf_h2_ctx_init(cf, data, TRUE);
+  if(result)
+    return result;
+
+  if(nread) {
+    /* we are going to copy mem to httpc->inbuf.  This is required since
+       mem is part of buffer pointed by stream->mem, and callbacks
+       called by nghttp2_session_mem_recv() will write stream specific
+       data into stream->mem, overwriting data already there. */
+    if(H2_BUFSIZE < nread) {
+      failf(data, "connection buffer size is too small to store data "
+            "following HTTP Upgrade response header: buflen=%d, datalen=%zu",
+            H2_BUFSIZE, nread);
+      return CURLE_HTTP2;
+    }
+
+    infof(data, "Copying HTTP/2 data in stream buffer to connection buffer"
+          " after upgrade: len=%zu", nread);
+    DEBUGASSERT(ctx->nread_inbuf == 0);
+    memcpy(ctx->inbuf, mem, nread);
+    ctx->inbuflen = nread;
   }
 
-  if(data->set.stream_depends_on)
-    Curl_http2_remove_child(data->set.stream_depends_on, data);
+  conn->httpversion = 20; /* we know we're on HTTP/2 now */
+  conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
+  conn->bundle->multiuse = BUNDLE_MULTIPLEX;
+  multi_connchanged(data->multi);
+
+  if(cf->next) {
+    bool done;
+    return Curl_conn_cf_connect(cf, data, FALSE, &done);
+  }
+  return CURLE_OK;
 }
 
 /* Only call this function for a transfer that already got an HTTP/2
@@ -2312,7 +2642,7 @@
 bool Curl_h2_http_1_1_error(struct Curl_easy *data)
 {
   struct HTTP *stream = data->req.p.http;
-  return (stream->error == NGHTTP2_HTTP_1_1_REQUIRED);
+  return (stream && stream->error == NGHTTP2_HTTP_1_1_REQUIRED);
 }
 
 #else /* !USE_NGHTTP2 */
diff --git a/Utilities/cmcurl/lib/http2.h b/Utilities/cmcurl/lib/http2.h
index 966bf75..f78fbf0 100644
--- a/Utilities/cmcurl/lib/http2.h
+++ b/Utilities/cmcurl/lib/http2.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -40,44 +40,41 @@
 
 const char *Curl_http2_strerror(uint32_t err);
 
-CURLcode Curl_http2_init(struct connectdata *conn);
-void Curl_http2_init_state(struct UrlState *state);
-void Curl_http2_init_userset(struct UserDefined *set);
 CURLcode Curl_http2_request_upgrade(struct dynbuf *req,
                                     struct Curl_easy *data);
-CURLcode Curl_http2_setup(struct Curl_easy *data, struct connectdata *conn);
-CURLcode Curl_http2_switched(struct Curl_easy *data,
-                             const char *ptr, size_t nread);
-/* called from http_setup_conn */
-void Curl_http2_setup_conn(struct connectdata *conn);
-void Curl_http2_setup_req(struct Curl_easy *data);
-void Curl_http2_done(struct Curl_easy *data, bool premature);
-CURLcode Curl_http2_done_sending(struct Curl_easy *data,
-                                 struct connectdata *conn);
-CURLcode Curl_http2_add_child(struct Curl_easy *parent,
-                              struct Curl_easy *child,
-                              bool exclusive);
-void Curl_http2_remove_child(struct Curl_easy *parent,
-                             struct Curl_easy *child);
-void Curl_http2_cleanup_dependencies(struct Curl_easy *data);
-CURLcode Curl_http2_stream_pause(struct Curl_easy *data, bool pause);
 
 /* returns true if the HTTP/2 stream error was HTTP_1_1_REQUIRED */
 bool Curl_h2_http_1_1_error(struct Curl_easy *data);
+
+bool Curl_conn_is_http2(const struct Curl_easy *data,
+                        const struct connectdata *conn,
+                        int sockindex);
+bool Curl_cf_is_http2(struct Curl_cfilter *cf, const struct Curl_easy *data);
+
+bool Curl_http2_may_switch(struct Curl_easy *data,
+                           struct connectdata *conn,
+                           int sockindex);
+
+CURLcode Curl_http2_switch(struct Curl_easy *data,
+                           struct connectdata *conn, int sockindex);
+
+CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data);
+
+CURLcode Curl_http2_upgrade(struct Curl_easy *data,
+                            struct connectdata *conn, int sockindex,
+                            const char *ptr, size_t nread);
+
+extern struct Curl_cftype Curl_cft_nghttp2;
+
 #else /* USE_NGHTTP2 */
+
+#define Curl_cf_is_http2(a,b) FALSE
+#define Curl_conn_is_http2(a,b,c) FALSE
+#define Curl_http2_may_switch(a,b,c) FALSE
+
 #define Curl_http2_request_upgrade(x,y) CURLE_UNSUPPORTED_PROTOCOL
-#define Curl_http2_setup(x,y) CURLE_UNSUPPORTED_PROTOCOL
-#define Curl_http2_switched(x,y,z) CURLE_UNSUPPORTED_PROTOCOL
-#define Curl_http2_setup_conn(x) Curl_nop_stmt
-#define Curl_http2_setup_req(x)
-#define Curl_http2_init_state(x)
-#define Curl_http2_init_userset(x)
-#define Curl_http2_done(x,y)
-#define Curl_http2_done_sending(x,y) (void)y
-#define Curl_http2_add_child(x, y, z)
-#define Curl_http2_remove_child(x, y)
-#define Curl_http2_cleanup_dependencies(x)
-#define Curl_http2_stream_pause(x, y)
+#define Curl_http2_switch(a,b,c)        CURLE_UNSUPPORTED_PROTOCOL
+#define Curl_http2_upgrade(a,b,c,d,e)   CURLE_UNSUPPORTED_PROTOCOL
 #define Curl_h2_http_1_1_error(x) 0
 #endif
 
diff --git a/Utilities/cmcurl/lib/http_aws_sigv4.c b/Utilities/cmcurl/lib/http_aws_sigv4.c
index 8c6d1c9..7d50cff 100644
--- a/Utilities/cmcurl/lib/http_aws_sigv4.c
+++ b/Utilities/cmcurl/lib/http_aws_sigv4.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -48,9 +48,9 @@
   do {                                      \
     ret = Curl_hmacit(Curl_HMAC_SHA256,     \
                       (unsigned char *)k,   \
-                      (unsigned int)kl,     \
+                      kl,                   \
                       (unsigned char *)d,   \
-                      (unsigned int)dl, o); \
+                      dl, o);               \
     if(ret) {                               \
       goto fail;                            \
     }                                       \
@@ -58,13 +58,15 @@
 
 #define TIMESTAMP_SIZE 17
 
-static void sha256_to_hex(char *dst, unsigned char *sha, size_t dst_l)
+/* hex-encoded with trailing null */
+#define SHA256_HEX_LENGTH (2 * SHA256_DIGEST_LENGTH + 1)
+
+static void sha256_to_hex(char *dst, unsigned char *sha)
 {
   int i;
 
-  DEBUGASSERT(dst_l >= 65);
-  for(i = 0; i < 32; ++i) {
-    msnprintf(dst + (i * 2), dst_l - (i * 2), "%02x", sha[i]);
+  for(i = 0; i < SHA256_DIGEST_LENGTH; ++i) {
+    msnprintf(dst + (i * 2), SHA256_HEX_LENGTH - (i * 2), "%02x", sha[i]);
   }
 }
 
@@ -135,6 +137,7 @@
                              char *timestamp,
                              char *provider1,
                              char **date_header,
+                             char *content_sha256_header,
                              struct dynbuf *canonical_headers,
                              struct dynbuf *signed_headers)
 {
@@ -189,6 +192,13 @@
   }
 
 
+  if (*content_sha256_header) {
+    tmp_head = curl_slist_append(head, content_sha256_header);
+    if(!tmp_head)
+      goto fail;
+    head = tmp_head;
+  }
+
   for(l = data->set.headers; l; l = l->next) {
     tmp_head = curl_slist_append(head, l->data);
     if(!tmp_head)
@@ -267,6 +277,9 @@
 }
 
 #define CONTENT_SHA256_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Content-Sha256"))
+/* add 2 for ": " between header name and value */
+#define CONTENT_SHA256_HDR_LEN (CONTENT_SHA256_KEY_LEN + 2 + \
+                                SHA256_HEX_LENGTH)
 
 /* try to parse a payload hash from the content-sha256 header */
 static char *parse_content_sha_hdr(struct Curl_easy *data,
@@ -300,6 +313,63 @@
   return value;
 }
 
+static CURLcode calc_payload_hash(struct Curl_easy *data,
+                                  unsigned char *sha_hash, char *sha_hex)
+{
+  const char *post_data = data->set.postfields;
+  size_t post_data_len = 0;
+  CURLcode result;
+
+  if(post_data) {
+    if(data->set.postfieldsize < 0)
+      post_data_len = strlen(post_data);
+    else
+      post_data_len = (size_t)data->set.postfieldsize;
+  }
+  result = Curl_sha256it(sha_hash, (const unsigned char *) post_data,
+                         post_data_len);
+  if(!result)
+    sha256_to_hex(sha_hex, sha_hash);
+  return result;
+}
+
+#define S3_UNSIGNED_PAYLOAD "UNSIGNED-PAYLOAD"
+
+static CURLcode calc_s3_payload_hash(struct Curl_easy *data,
+                                     Curl_HttpReq httpreq, char *provider1,
+                                     unsigned char *sha_hash,
+                                     char *sha_hex, char *header)
+{
+  bool empty_method = (httpreq == HTTPREQ_GET || httpreq == HTTPREQ_HEAD);
+  /* The request method or filesize indicate no request payload */
+  bool empty_payload = (empty_method || data->set.filesize == 0);
+  /* The POST payload is in memory */
+  bool post_payload = (httpreq == HTTPREQ_POST && data->set.postfields);
+  CURLcode ret = CURLE_OUT_OF_MEMORY;
+
+  if(empty_payload || post_payload) {
+    /* Calculate a real hash when we know the request payload */
+    ret = calc_payload_hash(data, sha_hash, sha_hex);
+    if(ret)
+      goto fail;
+  }
+  else {
+    /* Fall back to s3's UNSIGNED-PAYLOAD */
+    size_t len = sizeof(S3_UNSIGNED_PAYLOAD) - 1;
+    DEBUGASSERT(len < SHA256_HEX_LENGTH); /* 16 < 65 */
+    memcpy(sha_hex, S3_UNSIGNED_PAYLOAD, len);
+    sha_hex[len] = 0;
+  }
+
+  /* format the required content-sha256 header */
+  msnprintf(header, CONTENT_SHA256_HDR_LEN,
+            "x-%s-content-sha256: %s", provider1, sha_hex);
+
+  ret = CURLE_OK;
+fail:
+  return ret;
+}
+
 CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
 {
   CURLcode ret = CURLE_OUT_OF_MEMORY;
@@ -310,6 +380,7 @@
   char provider1[MAX_SIGV4_LEN + 1]="";
   char region[MAX_SIGV4_LEN + 1]="";
   char service[MAX_SIGV4_LEN + 1]="";
+  bool sign_as_s3 = false;
   const char *hostname = conn->host.name;
   time_t clock;
   struct tm tm;
@@ -318,20 +389,21 @@
   struct dynbuf canonical_headers;
   struct dynbuf signed_headers;
   char *date_header = NULL;
+  Curl_HttpReq httpreq;
+  const char *method = NULL;
   char *payload_hash = NULL;
   size_t payload_hash_len = 0;
-  const char *post_data = data->set.postfields;
-  size_t post_data_len = 0;
-  unsigned char sha_hash[32];
-  char sha_hex[65];
+  unsigned char sha_hash[SHA256_DIGEST_LENGTH];
+  char sha_hex[SHA256_HEX_LENGTH];
+  char content_sha256_hdr[CONTENT_SHA256_HDR_LEN + 2] = ""; /* add \r\n */
   char *canonical_request = NULL;
   char *request_type = NULL;
   char *credential_scope = NULL;
   char *str_to_sign = NULL;
   const char *user = data->state.aptr.user ? data->state.aptr.user : "";
   char *secret = NULL;
-  unsigned char sign0[32] = {0};
-  unsigned char sign1[32] = {0};
+  unsigned char sign0[SHA256_DIGEST_LENGTH] = {0};
+  unsigned char sign1[SHA256_DIGEST_LENGTH] = {0};
   char *auth_headers = NULL;
 
   DEBUGASSERT(!proxy);
@@ -408,6 +480,29 @@
     }
   }
 
+  Curl_http_method(data, conn, &method, &httpreq);
+
+  /* AWS S3 requires a x-amz-content-sha256 header, and supports special
+   * values like UNSIGNED-PAYLOAD */
+  sign_as_s3 = (strcasecompare(provider0, "aws") &&
+                strcasecompare(service, "s3"));
+
+  payload_hash = parse_content_sha_hdr(data, provider1, &payload_hash_len);
+
+  if(!payload_hash) {
+    if(sign_as_s3)
+      ret = calc_s3_payload_hash(data, httpreq, provider1, sha_hash,
+                                 sha_hex, content_sha256_hdr);
+    else
+      ret = calc_payload_hash(data, sha_hash, sha_hex);
+    if(ret)
+      goto fail;
+
+    payload_hash = sha_hex;
+    /* may be shorter than SHA256_HEX_LENGTH, like S3_UNSIGNED_PAYLOAD */
+    payload_hash_len = strlen(sha_hex);
+  }
+
 #ifdef DEBUGBUILD
   {
     char *force_timestamp = getenv("CURL_FORCETIME");
@@ -429,54 +524,37 @@
   }
 
   ret = make_headers(data, hostname, timestamp, provider1,
-                     &date_header, &canonical_headers, &signed_headers);
+                     &date_header, content_sha256_hdr,
+                     &canonical_headers, &signed_headers);
   if(ret)
     goto fail;
   ret = CURLE_OUT_OF_MEMORY;
 
+  if(*content_sha256_hdr) {
+    /* make_headers() needed this without the \r\n for canonicalization */
+    size_t hdrlen = strlen(content_sha256_hdr);
+    DEBUGASSERT(hdrlen + 3 < sizeof(content_sha256_hdr));
+    memcpy(content_sha256_hdr + hdrlen, "\r\n", 3);
+  }
+
   memcpy(date, timestamp, sizeof(date));
   date[sizeof(date) - 1] = 0;
 
-  payload_hash = parse_content_sha_hdr(data, provider1, &payload_hash_len);
-
-  if(!payload_hash) {
-    if(post_data) {
-      if(data->set.postfieldsize < 0)
-        post_data_len = strlen(post_data);
-      else
-        post_data_len = (size_t)data->set.postfieldsize;
-    }
-    if(Curl_sha256it(sha_hash, (const unsigned char *) post_data,
-                     post_data_len))
-      goto fail;
-
-    sha256_to_hex(sha_hex, sha_hash, sizeof(sha_hex));
-    payload_hash = sha_hex;
-    payload_hash_len = strlen(sha_hex);
-  }
-
-  {
-    Curl_HttpReq httpreq;
-    const char *method;
-
-    Curl_http_method(data, conn, &method, &httpreq);
-
-    canonical_request =
-      curl_maprintf("%s\n" /* HTTPRequestMethod */
-                    "%s\n" /* CanonicalURI */
-                    "%s\n" /* CanonicalQueryString */
-                    "%s\n" /* CanonicalHeaders */
-                    "%s\n" /* SignedHeaders */
-                    "%.*s",  /* HashedRequestPayload in hex */
-                    method,
-                    data->state.up.path,
-                    data->state.up.query ? data->state.up.query : "",
-                    Curl_dyn_ptr(&canonical_headers),
-                    Curl_dyn_ptr(&signed_headers),
-                    (int)payload_hash_len, payload_hash);
-    if(!canonical_request)
-      goto fail;
-  }
+  canonical_request =
+    curl_maprintf("%s\n" /* HTTPRequestMethod */
+                  "%s\n" /* CanonicalURI */
+                  "%s\n" /* CanonicalQueryString */
+                  "%s\n" /* CanonicalHeaders */
+                  "%s\n" /* SignedHeaders */
+                  "%.*s",  /* HashedRequestPayload in hex */
+                  method,
+                  data->state.up.path,
+                  data->state.up.query ? data->state.up.query : "",
+                  Curl_dyn_ptr(&canonical_headers),
+                  Curl_dyn_ptr(&signed_headers),
+                  (int)payload_hash_len, payload_hash);
+  if(!canonical_request)
+    goto fail;
 
   /* provider 0 lowercase */
   Curl_strntolower(provider0, provider0, strlen(provider0));
@@ -493,7 +571,7 @@
                    strlen(canonical_request)))
     goto fail;
 
-  sha256_to_hex(sha_hex, sha_hash, sizeof(sha_hex));
+  sha256_to_hex(sha_hex, sha_hash);
 
   /* provider 0 uppercase */
   Curl_strntoupper(provider0, provider0, strlen(provider0));
@@ -527,20 +605,22 @@
   HMAC_SHA256(sign0, sizeof(sign0), request_type, strlen(request_type), sign1);
   HMAC_SHA256(sign1, sizeof(sign1), str_to_sign, strlen(str_to_sign), sign0);
 
-  sha256_to_hex(sha_hex, sign0, sizeof(sha_hex));
+  sha256_to_hex(sha_hex, sign0);
 
   /* provider 0 uppercase */
   auth_headers = curl_maprintf("Authorization: %s4-HMAC-SHA256 "
                                "Credential=%s/%s, "
                                "SignedHeaders=%s, "
                                "Signature=%s\r\n"
-                               "%s\r\n",
+                               "%s\r\n"
+                               "%s", /* optional sha256 header includes \r\n */
                                provider0,
                                user,
                                credential_scope,
                                Curl_dyn_ptr(&signed_headers),
                                sha_hex,
-                               date_header);
+                               date_header,
+                               content_sha256_hdr);
   if(!auth_headers) {
     goto fail;
   }
diff --git a/Utilities/cmcurl/lib/http_aws_sigv4.h b/Utilities/cmcurl/lib/http_aws_sigv4.h
index 85755e9..57cc570 100644
--- a/Utilities/cmcurl/lib/http_aws_sigv4.h
+++ b/Utilities/cmcurl/lib/http_aws_sigv4.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/http_chunks.c b/Utilities/cmcurl/lib/http_chunks.c
index c344e6d..bda00d3 100644
--- a/Utilities/cmcurl/lib/http_chunks.c
+++ b/Utilities/cmcurl/lib/http_chunks.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/http_chunks.h b/Utilities/cmcurl/lib/http_chunks.h
index 2cf5507..ed50713 100644
--- a/Utilities/cmcurl/lib/http_chunks.h
+++ b/Utilities/cmcurl/lib/http_chunks.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/http_digest.c b/Utilities/cmcurl/lib/http_digest.c
index f015f78..8daad99 100644
--- a/Utilities/cmcurl/lib/http_digest.c
+++ b/Utilities/cmcurl/lib/http_digest.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/http_digest.h b/Utilities/cmcurl/lib/http_digest.h
index eea90b7..7d5cfc1 100644
--- a/Utilities/cmcurl/lib/http_digest.h
+++ b/Utilities/cmcurl/lib/http_digest.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/http_negotiate.c b/Utilities/cmcurl/lib/http_negotiate.c
index 5909f85..153e3d4 100644
--- a/Utilities/cmcurl/lib/http_negotiate.c
+++ b/Utilities/cmcurl/lib/http_negotiate.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/http_negotiate.h b/Utilities/cmcurl/lib/http_negotiate.h
index 6e2096c..76d8356 100644
--- a/Utilities/cmcurl/lib/http_negotiate.h
+++ b/Utilities/cmcurl/lib/http_negotiate.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/http_ntlm.c b/Utilities/cmcurl/lib/http_ntlm.c
index a19cc0d..b845ddf 100644
--- a/Utilities/cmcurl/lib/http_ntlm.c
+++ b/Utilities/cmcurl/lib/http_ntlm.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/http_ntlm.h b/Utilities/cmcurl/lib/http_ntlm.h
index cec63b8..f37572b 100644
--- a/Utilities/cmcurl/lib/http_ntlm.h
+++ b/Utilities/cmcurl/lib/http_ntlm.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/http_proxy.c b/Utilities/cmcurl/lib/http_proxy.c
index e30730a..9f214a3 100644
--- a/Utilities/cmcurl/lib/http_proxy.c
+++ b/Utilities/cmcurl/lib/http_proxy.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -26,7 +26,7 @@
 
 #include "http_proxy.h"
 
-#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
+#if !defined(CURL_DISABLE_PROXY)
 
 #include <curl/curl.h>
 #ifdef USE_HYPER
@@ -49,6 +49,9 @@
 #include "curl_memory.h"
 #include "memdebug.h"
 
+
+#if !defined(CURL_DISABLE_HTTP)
+
 typedef enum {
     TUNNEL_INIT,     /* init/default/no tunnel state */
     TUNNEL_CONNECT,  /* CONNECT request is being send */
@@ -63,8 +66,7 @@
   int sockindex;
   const char *hostname;
   int remote_port;
-  struct HTTP http_proxy;
-  struct HTTP *prot_save;
+  struct HTTP CONNECT;
   struct dynbuf rcvbuf;
   struct dynbuf req;
   size_t nsend;
@@ -149,17 +151,6 @@
   Curl_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS);
   Curl_dyn_init(&ts->req, DYN_HTTP_REQUEST);
 
-  /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the
-   * member conn->proto.http; we want [protocol] through HTTP and we have
-   * to change the member temporarily for connecting to the HTTP
-   * proxy. After Curl_proxyCONNECT we have to set back the member to the
-   * original pointer
-   *
-   * This function might be called several times in the multi interface case
-   * if the proxy's CONNECT response is not instant.
-   */
-  ts->prot_save = data->req.p.http;
-  data->req.p.http = &ts->http_proxy;
   *pts =  ts;
   connkeep(conn, "HTTP proxy CONNECT");
   return tunnel_reinit(ts, conn, data);
@@ -183,34 +174,39 @@
   /* entering this one */
   switch(new_state) {
   case TUNNEL_INIT:
+    DEBUGF(LOG_CF(data, cf, "new tunnel state 'init'"));
     tunnel_reinit(ts, cf->conn, data);
     break;
 
   case TUNNEL_CONNECT:
+    DEBUGF(LOG_CF(data, cf, "new tunnel state 'connect'"));
     ts->tunnel_state = TUNNEL_CONNECT;
     ts->keepon = KEEPON_CONNECT;
     Curl_dyn_reset(&ts->rcvbuf);
     break;
 
   case TUNNEL_RECEIVE:
+    DEBUGF(LOG_CF(data, cf, "new tunnel state 'receive'"));
     ts->tunnel_state = TUNNEL_RECEIVE;
     break;
 
   case TUNNEL_RESPONSE:
+    DEBUGF(LOG_CF(data, cf, "new tunnel state 'response'"));
     ts->tunnel_state = TUNNEL_RESPONSE;
     break;
 
   case TUNNEL_ESTABLISHED:
+    DEBUGF(LOG_CF(data, cf, "new tunnel state 'established'"));
     infof(data, "CONNECT phase completed");
     data->state.authproxy.done = TRUE;
     data->state.authproxy.multipass = FALSE;
     /* FALLTHROUGH */
   case TUNNEL_FAILED:
+    DEBUGF(LOG_CF(data, cf, "new tunnel state 'failed'"));
     ts->tunnel_state = new_state;
     Curl_dyn_reset(&ts->rcvbuf);
     Curl_dyn_reset(&ts->req);
     /* restore the protocol pointer */
-    data->req.p.http = ts->prot_save;
     data->info.httpcode = 0; /* clear it as it might've been used for the
                                 proxy */
     /* If a proxy-authorization header was used for the proxy, then we should
@@ -271,10 +267,11 @@
 }
 
 #ifndef USE_HYPER
-static CURLcode start_CONNECT(struct Curl_easy *data,
-                              struct connectdata *conn,
+static CURLcode start_CONNECT(struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
                               struct tunnel_state *ts)
 {
+  struct connectdata *conn = cf->conn;
   char *hostheader = NULL;
   char *host = NULL;
   const char *httpv;
@@ -338,7 +335,8 @@
     goto out;
 
   /* Send the connect request to the proxy */
-  result = Curl_buffer_send(&ts->req, data, &data->info.request_size, 0,
+  result = Curl_buffer_send(&ts->req, data, &ts->CONNECT,
+                            &data->info.request_size, 0,
                             ts->sockindex);
   ts->headerlines = 0;
 
@@ -356,7 +354,7 @@
                              bool *done)
 {
   struct SingleRequest *k = &data->req;
-  struct HTTP *http = data->req.p.http;
+  struct HTTP *http = &ts->CONNECT;
   CURLcode result = CURLE_OK;
 
   if(http->sending != HTTPSEND_REQUEST)
@@ -377,7 +375,7 @@
     result = Curl_write(data,
                         conn->writesockfd,  /* socket to send to */
                         k->upload_fromhere, /* buffer pointer */
-                        ts->nsend,           /* buffer size */
+                        ts->nsend,          /* buffer size */
                         &bytes_written);    /* actually sent */
     if(result)
       goto out;
@@ -398,13 +396,14 @@
   return result;
 }
 
-static CURLcode on_resp_header(struct Curl_easy *data,
+static CURLcode on_resp_header(struct Curl_cfilter *cf,
+                               struct Curl_easy *data,
                                struct tunnel_state *ts,
                                const char *header)
 {
   CURLcode result = CURLE_OK;
   struct SingleRequest *k = &data->req;
-  int subversion = 0;
+  (void)cf;
 
   if((checkprefix("WWW-Authenticate:", header) &&
       (401 == k->httpcode)) ||
@@ -416,8 +415,7 @@
     if(!auth)
       return CURLE_OUT_OF_MEMORY;
 
-    DEBUGF(infof(data, "CONNECT: fwd auth header '%s'",
-           header));
+    DEBUGF(LOG_CF(data, cf, "CONNECT: fwd auth header '%s'", header));
     result = Curl_http_input_auth(data, proxy, auth);
 
     free(auth);
@@ -462,23 +460,26 @@
                              STRCONST("Proxy-Connection:"),
                              STRCONST("close")))
     ts->close_connection = TRUE;
-  else if(2 == sscanf(header, "HTTP/1.%d %d",
-                      &subversion,
-                      &k->httpcode)) {
+  else if(!strncmp(header, "HTTP/1.", 7) &&
+          ((header[7] == '0') || (header[7] == '1')) &&
+          (header[8] == ' ') &&
+          ISDIGIT(header[9]) && ISDIGIT(header[10]) && ISDIGIT(header[11]) &&
+          !ISDIGIT(header[12])) {
     /* store the HTTP code from the proxy */
-    data->info.httpproxycode = k->httpcode;
+    data->info.httpproxycode =  k->httpcode = (header[9] - '0') * 100 +
+      (header[10] - '0') * 10 + (header[11] - '0');
   }
   return result;
 }
 
-static CURLcode recv_CONNECT_resp(struct Curl_easy *data,
-                                  struct connectdata *conn,
+static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
                                   struct tunnel_state *ts,
                                   bool *done)
 {
   CURLcode result = CURLE_OK;
   struct SingleRequest *k = &data->req;
-  curl_socket_t tunnelsocket = conn->sock[ts->sockindex];
+  curl_socket_t tunnelsocket = Curl_conn_cf_get_socket(cf, data);
   char *linep;
   size_t perline;
   int error;
@@ -634,7 +635,7 @@
           /* without content-length or chunked encoding, we
              can't keep the connection alive since the close is
              the end signal so we bail out at once instead */
-          DEBUGF(infof(data, "CONNECT: no content-length or chunked"));
+          DEBUGF(LOG_CF(data, cf, "CONNECT: no content-length or chunked"));
           ts->keepon = KEEPON_DONE;
         }
       }
@@ -647,7 +648,7 @@
       continue;
     }
 
-    result = on_resp_header(data, ts, linep);
+    result = on_resp_header(cf, data, ts, linep);
     if(result)
       return result;
 
@@ -667,12 +668,13 @@
 
 #else /* USE_HYPER */
 /* The Hyper version of CONNECT */
-static CURLcode start_CONNECT(struct Curl_easy *data,
-                              struct connectdata *conn,
+static CURLcode start_CONNECT(struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
                               struct tunnel_state *ts)
 {
+  struct connectdata *conn = cf->conn;
   struct hyptransfer *h = &data->hyp;
-  curl_socket_t tunnelsocket = conn->sock[ts->sockindex];
+  curl_socket_t tunnelsocket = Curl_conn_cf_get_socket(cf, data);
   hyper_io *io = NULL;
   hyper_request *req = NULL;
   hyper_headers *headers = NULL;
@@ -914,8 +916,8 @@
   return result;
 }
 
-static CURLcode recv_CONNECT_resp(struct Curl_easy *data,
-                                  struct connectdata *conn,
+static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
                                   struct tunnel_state *ts,
                                   bool *done)
 {
@@ -925,7 +927,7 @@
 
   (void)ts;
   *done = FALSE;
-  result = Curl_hyper_stream(data, conn, &didwhat, done,
+  result = Curl_hyper_stream(data, cf->conn, &didwhat, done,
                              CURL_CSELECT_IN | CURL_CSELECT_OUT);
   if(result || !*done)
     return result;
@@ -972,7 +974,8 @@
     switch(ts->tunnel_state) {
     case TUNNEL_INIT:
       /* Prepare the CONNECT request and make a first attempt to send. */
-      result = start_CONNECT(data, cf->conn, ts);
+      DEBUGF(LOG_CF(data, cf, "CONNECT start"));
+      result = start_CONNECT(cf, data, ts);
       if(result)
         goto out;
       tunnel_go_state(cf, ts, TUNNEL_CONNECT, data);
@@ -980,6 +983,7 @@
 
     case TUNNEL_CONNECT:
       /* see that the request is completely sent */
+      DEBUGF(LOG_CF(data, cf, "CONNECT send"));
       result = send_CONNECT(data, cf->conn, ts, &done);
       if(result || !done)
         goto out;
@@ -988,7 +992,8 @@
 
     case TUNNEL_RECEIVE:
       /* read what is there */
-      result = recv_CONNECT_resp(data, cf->conn, ts, &done);
+      DEBUGF(LOG_CF(data, cf, "CONNECT receive"));
+      result = recv_CONNECT_resp(cf, data, ts, &done);
       if(Curl_pgrsUpdate(data)) {
         result = CURLE_ABORTED_BY_CALLBACK;
         goto out;
@@ -1001,24 +1006,29 @@
       /* FALLTHROUGH */
 
     case TUNNEL_RESPONSE:
+      DEBUGF(LOG_CF(data, cf, "CONNECT response"));
       if(data->req.newurl) {
         /* not the "final" response, we need to do a follow up request.
          * If the other side indicated a connection close, or if someone
-         * else told us to close this connection, do so now. */
+         * else told us to close this connection, do so now.
+         */
         if(ts->close_connection || conn->bits.close) {
-          /* Close the filter chain and trigger connect, non-blocking
-           * again, so the process is ongoing. This will
-           * a) the close resets our tunnel state
-           * b) the connect makes sure that there will be a socket
-           *    to select on again.
-           * We return and expect to be called again. */
+          /* Close this filter and the sub-chain, re-connect the
+           * sub-chain and continue. Closing this filter will
+           * reset our tunnel state. To avoid recursion, we return
+           * and expect to be called again.
+           */
+          DEBUGF(LOG_CF(data, cf, "CONNECT need to close+open"));
           infof(data, "Connect me again please");
-          Curl_conn_close(data, cf->sockindex);
-          result = cf->next->cft->connect(cf->next, data, FALSE, &done);
+          Curl_conn_cf_close(cf, data);
+          connkeep(conn, "HTTP proxy CONNECT");
+          result = Curl_conn_cf_connect(cf->next, data, FALSE, &done);
           goto out;
         }
-        /* staying on this connection, reset state */
-        tunnel_go_state(cf, ts, TUNNEL_INIT, data);
+        else {
+          /* staying on this connection, reset state */
+          tunnel_go_state(cf, ts, TUNNEL_INIT, data);
+        }
       }
       break;
 
@@ -1063,10 +1073,12 @@
     return CURLE_OK;
   }
 
+  DEBUGF(LOG_CF(data, cf, "connect"));
   result = cf->next->cft->connect(cf->next, data, blocking, done);
   if(result || !*done)
     return result;
 
+  DEBUGF(LOG_CF(data, cf, "subchain is connected"));
   /* TODO: can we do blocking? */
   /* We want "seamless" operations through HTTP proxy tunnel */
 
@@ -1117,22 +1129,21 @@
                                           curl_socket_t *socks)
 {
   struct tunnel_state *ts = cf->ctx;
-  struct connectdata *conn = cf->conn;
   int fds;
 
-  DEBUGASSERT(conn);
   fds = cf->next->cft->get_select_socks(cf->next, data, socks);
   if(!fds && cf->next->connected && !cf->connected) {
     /* If we are not connected, but the filter "below" is
      * and not waiting on something, we are tunneling. */
-    socks[0] = conn->sock[cf->sockindex];
+    socks[0] = Curl_conn_cf_get_socket(cf, data);
     if(ts) {
       /* when we've sent a CONNECT to a proxy, we should rather either
          wait for the socket to become readable to be able to get the
          response headers or if we're still sending the request, wait
          for write. */
-      if(ts->http_proxy.sending == HTTPSEND_REQUEST)
+      if(ts->CONNECT.sending == HTTPSEND_REQUEST) {
         return GETSOCK_WRITESOCK(0);
+      }
       return GETSOCK_READSOCK(0);
     }
     return GETSOCK_WRITESOCK(0);
@@ -1140,24 +1151,18 @@
   return fds;
 }
 
-static void http_proxy_cf_detach_data(struct Curl_cfilter *cf,
-                                      struct Curl_easy *data)
-{
-  if(cf->ctx) {
-    tunnel_free(cf, data);
-  }
-}
-
 static void http_proxy_cf_destroy(struct Curl_cfilter *cf,
                                   struct Curl_easy *data)
 {
-  http_proxy_cf_detach_data(cf, data);
+  DEBUGF(LOG_CF(data, cf, "destroy"));
+  tunnel_free(cf, data);
 }
 
 static void http_proxy_cf_close(struct Curl_cfilter *cf,
                                 struct Curl_easy *data)
 {
   DEBUGASSERT(cf->next);
+  DEBUGF(LOG_CF(data, cf, "close"));
   cf->connected = FALSE;
   cf->next->cft->close(cf->next, data);
   if(cf->ctx) {
@@ -1166,11 +1171,11 @@
 }
 
 
-static const struct Curl_cftype cft_http_proxy = {
+struct Curl_cftype Curl_cft_http_proxy = {
   "HTTP-PROXY",
   CF_TYPE_IP_CONNECT,
+  0,
   http_proxy_cf_destroy,
-  Curl_cf_def_setup,
   http_proxy_cf_connect,
   http_proxy_cf_close,
   http_proxy_cf_get_host,
@@ -1178,8 +1183,10 @@
   Curl_cf_def_data_pending,
   Curl_cf_def_send,
   Curl_cf_def_recv,
-  Curl_cf_def_attach_data,
-  http_proxy_cf_detach_data,
+  Curl_cf_def_cntrl,
+  Curl_cf_def_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  Curl_cf_def_query,
 };
 
 CURLcode Curl_conn_http_proxy_add(struct Curl_easy *data,
@@ -1189,31 +1196,73 @@
   struct Curl_cfilter *cf;
   CURLcode result;
 
-  result = Curl_cf_create(&cf, &cft_http_proxy, NULL);
+  result = Curl_cf_create(&cf, &Curl_cft_http_proxy, NULL);
   if(!result)
     Curl_conn_cf_add(data, conn, sockindex, cf);
   return result;
 }
 
-
-static CURLcode send_haproxy_header(struct Curl_cfilter*cf,
-                                    struct Curl_easy *data)
+CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at,
+                                         struct Curl_easy *data)
 {
-  struct dynbuf req;
+  struct Curl_cfilter *cf;
+  CURLcode result;
+
+  (void)data;
+  result = Curl_cf_create(&cf, &Curl_cft_http_proxy, NULL);
+  if(!result)
+    Curl_conn_cf_insert_after(cf_at, cf);
+  return result;
+}
+
+#endif /* ! CURL_DISABLE_HTTP */
+
+
+typedef enum {
+    HAPROXY_INIT,     /* init/default/no tunnel state */
+    HAPROXY_SEND,     /* data_out being sent */
+    HAPROXY_DONE      /* all work done */
+} haproxy_state;
+
+struct cf_haproxy_ctx {
+  int state;
+  struct dynbuf data_out;
+};
+
+static void cf_haproxy_ctx_reset(struct cf_haproxy_ctx *ctx)
+{
+  DEBUGASSERT(ctx);
+  ctx->state = HAPROXY_INIT;
+  Curl_dyn_reset(&ctx->data_out);
+}
+
+static void cf_haproxy_ctx_free(struct cf_haproxy_ctx *ctx)
+{
+  if(ctx) {
+    Curl_dyn_free(&ctx->data_out);
+    free(ctx);
+  }
+}
+
+static CURLcode cf_haproxy_date_out_set(struct Curl_cfilter*cf,
+                                        struct Curl_easy *data)
+{
+  struct cf_haproxy_ctx *ctx = cf->ctx;
   CURLcode result;
   const char *tcp_version;
-  Curl_dyn_init(&req, DYN_HAXPROXY);
 
+  DEBUGASSERT(ctx);
+  DEBUGASSERT(ctx->state == HAPROXY_INIT);
 #ifdef USE_UNIX_SOCKETS
   if(cf->conn->unix_domain_socket)
     /* the buffer is large enough to hold this! */
-    result = Curl_dyn_addn(&req, STRCONST("PROXY UNKNOWN\r\n"));
+    result = Curl_dyn_addn(&ctx->data_out, STRCONST("PROXY UNKNOWN\r\n"));
   else {
 #endif /* USE_UNIX_SOCKETS */
   /* Emit the correct prefix for IPv6 */
   tcp_version = cf->conn->bits.ipv6 ? "TCP6" : "TCP4";
 
-  result = Curl_dyn_addf(&req, "PROXY %s %s %s %i %i\r\n",
+  result = Curl_dyn_addf(&ctx->data_out, "PROXY %s %s %s %i %i\r\n",
                          tcp_version,
                          data->info.conn_local_ip,
                          data->info.conn_primary_ip,
@@ -1223,19 +1272,18 @@
 #ifdef USE_UNIX_SOCKETS
   }
 #endif /* USE_UNIX_SOCKETS */
-
-  if(!result)
-    result = Curl_buffer_send(&req, data, &data->info.request_size,
-                              0, FIRSTSOCKET);
   return result;
 }
 
-static CURLcode haproxy_cf_connect(struct Curl_cfilter *cf,
+static CURLcode cf_haproxy_connect(struct Curl_cfilter *cf,
                                    struct Curl_easy *data,
                                    bool blocking, bool *done)
 {
+  struct cf_haproxy_ctx *ctx = cf->ctx;
   CURLcode result;
+  size_t len;
 
+  DEBUGASSERT(ctx);
   if(cf->connected) {
     *done = TRUE;
     return CURLE_OK;
@@ -1245,28 +1293,120 @@
   if(result || !*done)
     return result;
 
-  result = send_haproxy_header(cf, data);
-  *done = (!result);
+  switch(ctx->state) {
+  case HAPROXY_INIT:
+    result = cf_haproxy_date_out_set(cf, data);
+    if(result)
+      goto out;
+    ctx->state = HAPROXY_SEND;
+    /* FALLTHROUGH */
+  case HAPROXY_SEND:
+    len = Curl_dyn_len(&ctx->data_out);
+    if(len > 0) {
+      ssize_t written = Curl_conn_send(data, cf->sockindex,
+                                       Curl_dyn_ptr(&ctx->data_out),
+                                       len, &result);
+      if(written < 0)
+        goto out;
+      Curl_dyn_tail(&ctx->data_out, len - (size_t)written);
+      if(Curl_dyn_len(&ctx->data_out) > 0) {
+        result = CURLE_OK;
+        goto out;
+      }
+    }
+    ctx->state = HAPROXY_DONE;
+    /* FALLTHROUGH */
+  default:
+    Curl_dyn_free(&ctx->data_out);
+    break;
+  }
+
+out:
+  *done = (!result) && (ctx->state == HAPROXY_DONE);
   cf->connected = *done;
   return result;
 }
 
-static const struct Curl_cftype cft_haproxy = {
+static void cf_haproxy_destroy(struct Curl_cfilter *cf,
+                               struct Curl_easy *data)
+{
+  (void)data;
+  DEBUGF(LOG_CF(data, cf, "destroy"));
+  cf_haproxy_ctx_free(cf->ctx);
+}
+
+static void cf_haproxy_close(struct Curl_cfilter *cf,
+                             struct Curl_easy *data)
+{
+  DEBUGF(LOG_CF(data, cf, "close"));
+  cf->connected = FALSE;
+  cf_haproxy_ctx_reset(cf->ctx);
+  if(cf->next)
+    cf->next->cft->close(cf->next, data);
+}
+
+static int cf_haproxy_get_select_socks(struct Curl_cfilter *cf,
+                                       struct Curl_easy *data,
+                                       curl_socket_t *socks)
+{
+  int fds;
+
+  fds = cf->next->cft->get_select_socks(cf->next, data, socks);
+  if(!fds && cf->next->connected && !cf->connected) {
+    /* If we are not connected, but the filter "below" is
+     * and not waiting on something, we are sending. */
+    socks[0] = Curl_conn_cf_get_socket(cf, data);
+    return GETSOCK_WRITESOCK(0);
+  }
+  return fds;
+}
+
+
+struct Curl_cftype Curl_cft_haproxy = {
   "HAPROXY",
   0,
-  Curl_cf_def_destroy_this,
-  Curl_cf_def_setup,
-  haproxy_cf_connect,
-  Curl_cf_def_close,
+  0,
+  cf_haproxy_destroy,
+  cf_haproxy_connect,
+  cf_haproxy_close,
   Curl_cf_def_get_host,
-  Curl_cf_def_get_select_socks,
+  cf_haproxy_get_select_socks,
   Curl_cf_def_data_pending,
   Curl_cf_def_send,
   Curl_cf_def_recv,
-  Curl_cf_def_attach_data,
-  Curl_cf_def_detach_data,
+  Curl_cf_def_cntrl,
+  Curl_cf_def_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  Curl_cf_def_query,
 };
 
+static CURLcode cf_haproxy_create(struct Curl_cfilter **pcf,
+                                  struct Curl_easy *data)
+{
+  struct Curl_cfilter *cf = NULL;
+  struct cf_haproxy_ctx *ctx;
+  CURLcode result;
+
+  (void)data;
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+  ctx->state = HAPROXY_INIT;
+  Curl_dyn_init(&ctx->data_out, DYN_HAXPROXY);
+
+  result = Curl_cf_create(&cf, &Curl_cft_haproxy, ctx);
+  if(result)
+    goto out;
+  ctx = NULL;
+
+out:
+  cf_haproxy_ctx_free(ctx);
+  *pcf = result? NULL : cf;
+  return result;
+}
+
 CURLcode Curl_conn_haproxy_add(struct Curl_easy *data,
                                struct connectdata *conn,
                                int sockindex)
@@ -1274,10 +1414,28 @@
   struct Curl_cfilter *cf;
   CURLcode result;
 
-  result = Curl_cf_create(&cf, &cft_haproxy, NULL);
-  if(!result)
-    Curl_conn_cf_add(data, conn, sockindex, cf);
+  result = cf_haproxy_create(&cf, data);
+  if(result)
+    goto out;
+  Curl_conn_cf_add(data, conn, sockindex, cf);
+
+out:
   return result;
 }
 
-#endif /* !CURL_DISABLE_PROXY &6 ! CURL_DISABLE_HTTP */
+CURLcode Curl_cf_haproxy_insert_after(struct Curl_cfilter *cf_at,
+                                      struct Curl_easy *data)
+{
+  struct Curl_cfilter *cf;
+  CURLcode result;
+
+  result = cf_haproxy_create(&cf, data);
+  if(result)
+    goto out;
+  Curl_conn_cf_insert_after(cf_at, cf);
+
+out:
+  return result;
+}
+
+#endif /* !CURL_DISABLE_PROXY */
diff --git a/Utilities/cmcurl/lib/http_proxy.h b/Utilities/cmcurl/lib/http_proxy.h
index dfdc0e7..f573da2 100644
--- a/Utilities/cmcurl/lib/http_proxy.h
+++ b/Utilities/cmcurl/lib/http_proxy.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -27,8 +27,9 @@
 #include "curl_setup.h"
 #include "urldata.h"
 
-#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
+#if !defined(CURL_DISABLE_PROXY)
 
+#if !defined(CURL_DISABLE_HTTP)
 /* Default proxy timeout in milliseconds */
 #define PROXY_TIMEOUT (3600*1000)
 
@@ -36,10 +37,22 @@
                                   struct connectdata *conn,
                                   int sockindex);
 
+CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at,
+                                         struct Curl_easy *data);
+
+extern struct Curl_cftype Curl_cft_http_proxy;
+
+#endif /* !CURL_DISABLE_HTTP */
+
 CURLcode Curl_conn_haproxy_add(struct Curl_easy *data,
                                struct connectdata *conn,
                                int sockindex);
 
-#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */
+CURLcode Curl_cf_haproxy_insert_after(struct Curl_cfilter *cf_at,
+                                      struct Curl_easy *data);
+
+extern struct Curl_cftype Curl_cft_haproxy;
+
+#endif /* !CURL_DISABLE_PROXY */
 
 #endif /* HEADER_CURL_HTTP_PROXY_H */
diff --git a/Utilities/cmcurl/lib/idn.c b/Utilities/cmcurl/lib/idn.c
index 6255221..5f4b07e 100644
--- a/Utilities/cmcurl/lib/idn.c
+++ b/Utilities/cmcurl/lib/idn.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -116,7 +116,7 @@
  * Curl_idn_decode() returns an allocated IDN decoded string if it was
  * possible. NULL on error.
  */
-static char *Curl_idn_decode(const char *input)
+static char *idn_decode(const char *input)
 {
   char *decoded = NULL;
 #ifdef USE_LIBIDN2
@@ -144,24 +144,29 @@
   return decoded;
 }
 
+char *Curl_idn_decode(const char *input)
+{
+  char *d = idn_decode(input);
+#ifdef USE_LIBIDN2
+  if(d) {
+    char *c = strdup(d);
+    idn2_free(d);
+    d = c;
+  }
+#endif
+  return d;
+}
+
 /*
  * Frees data allocated by idnconvert_hostname()
  */
 void Curl_free_idnconverted_hostname(struct hostname *host)
 {
-#if defined(USE_LIBIDN2)
   if(host->encalloc) {
-    idn2_free(host->encalloc); /* must be freed with idn2_free() since this was
-                                  allocated by libidn */
+    /* must be freed with idn2_free() if allocated by libidn */
+    Curl_idn_free(host->encalloc);
     host->encalloc = NULL;
   }
-#elif defined(USE_WIN32_IDN)
-  free(host->encalloc); /* must be freed with free() since this was
-                           allocated by Curl_win32_idn_to_ascii */
-  host->encalloc = NULL;
-#else
-  (void)host;
-#endif
 }
 
 #endif /* USE_IDN */
@@ -177,8 +182,13 @@
 #ifdef USE_IDN
   /* Check name for non-ASCII and convert hostname if we can */
   if(!Curl_is_ASCII_name(host->name)) {
-    char *decoded = Curl_idn_decode(host->name);
+    char *decoded = idn_decode(host->name);
     if(decoded) {
+      if(!*decoded) {
+        /* zero length is a bad host name */
+        Curl_idn_free(decoded);
+        return CURLE_URL_MALFORMAT;
+      }
       /* successful */
       host->encalloc = decoded;
       /* change the name pointer to point to the encoded hostname */
@@ -190,4 +200,3 @@
 #endif
   return CURLE_OK;
 }
-
diff --git a/Utilities/cmcurl/lib/idn.h b/Utilities/cmcurl/lib/idn.h
index 2d04efc..6c0bbb7 100644
--- a/Utilities/cmcurl/lib/idn.h
+++ b/Utilities/cmcurl/lib/idn.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -32,7 +32,15 @@
 #if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN)
 #define USE_IDN
 void Curl_free_idnconverted_hostname(struct hostname *host);
+char *Curl_idn_decode(const char *input);
+#ifdef USE_LIBIDN2
+#define Curl_idn_free(x) idn2_free(x)
+#else
+#define Curl_idn_free(x) free(x)
+#endif
+
 #else
 #define Curl_free_idnconverted_hostname(x)
+#define Curl_idn_decode(x) NULL
 #endif
 #endif /* HEADER_CURL_IDN_H */
diff --git a/Utilities/cmcurl/lib/if2ip.c b/Utilities/cmcurl/lib/if2ip.c
index c291948..6bf0ce1 100644
--- a/Utilities/cmcurl/lib/if2ip.c
+++ b/Utilities/cmcurl/lib/if2ip.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/if2ip.h b/Utilities/cmcurl/lib/if2ip.h
index 5d15459..1f97350 100644
--- a/Utilities/cmcurl/lib/if2ip.h
+++ b/Utilities/cmcurl/lib/if2ip.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2020, 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/imap.c b/Utilities/cmcurl/lib/imap.c
index bd4c6f2..c2f675d 100644
--- a/Utilities/cmcurl/lib/imap.c
+++ b/Utilities/cmcurl/lib/imap.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -475,15 +475,17 @@
   /* Start the SSL connection */
   struct imap_conn *imapc = &conn->proto.imapc;
   CURLcode result;
+  bool ssldone = FALSE;
 
-  if(!Curl_conn_is_ssl(data, FIRSTSOCKET)) {
+  if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
     result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET);
     if(result)
       goto out;
   }
 
-  result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &imapc->ssldone);
+  result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone);
   if(!result) {
+    imapc->ssldone = ssldone;
     if(imapc->state != IMAP_UPGRADETLS)
       state(data, IMAP_UPGRADETLS);
 
@@ -952,7 +954,7 @@
       line += wordlen;
     }
   }
-  else if(data->set.use_ssl && !Curl_conn_is_ssl(data, FIRSTSOCKET)) {
+  else if(data->set.use_ssl && !Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
     /* PREAUTH is not compatible with STARTTLS. */
     if(imapcode == IMAP_RESP_OK && imapc->tls_supported && !imapc->preauth) {
       /* Switch to TLS connection now */
@@ -1386,8 +1388,10 @@
   struct imap_conn *imapc = &conn->proto.imapc;
 
   if((conn->handler->flags & PROTOPT_SSL) && !imapc->ssldone) {
-    result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &imapc->ssldone);
-    if(result || !imapc->ssldone)
+    bool ssldone = FALSE;
+    result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone);
+    imapc->ssldone = ssldone;
+    if(result || !ssldone)
       return result;
   }
 
@@ -1774,7 +1778,7 @@
   /* Calculate the tag based on the connection ID and command ID */
   msnprintf(imapc->resptag, sizeof(imapc->resptag), "%c%03d",
             'A' + curlx_sltosi(data->conn->connection_id % 26),
-            (++imapc->cmdid)%1000);
+            ++imapc->cmdid);
 
   /* start with a blank buffer */
   Curl_dyn_reset(&imapc->dyn);
diff --git a/Utilities/cmcurl/lib/imap.h b/Utilities/cmcurl/lib/imap.h
index 43cc1e9..784ee97 100644
--- a/Utilities/cmcurl/lib/imap.h
+++ b/Utilities/cmcurl/lib/imap.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2009 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -72,19 +72,19 @@
    struct */
 struct imap_conn {
   struct pingpong pp;
-  imapstate state;            /* Always use imap.c:state() to change state! */
-  bool ssldone;               /* Is connect() over SSL done? */
-  bool preauth;               /* Is this connection PREAUTH? */
   struct SASL sasl;           /* SASL-related parameters */
-  unsigned int preftype;      /* Preferred authentication type */
-  unsigned int cmdid;         /* Last used command ID */
-  char resptag[5];            /* Response tag to wait for */
-  bool tls_supported;         /* StartTLS capability supported by server */
-  bool login_disabled;        /* LOGIN command disabled by server */
-  bool ir_supported;          /* Initial response supported by server */
+  struct dynbuf dyn;          /* for the IMAP commands */
   char *mailbox;              /* The last selected mailbox */
   char *mailbox_uidvalidity;  /* UIDVALIDITY parsed from select response */
-  struct dynbuf dyn;          /* for the IMAP commands */
+  imapstate state;            /* Always use imap.c:state() to change state! */
+  char resptag[5];            /* Response tag to wait for */
+  unsigned char preftype;     /* Preferred authentication type */
+  unsigned char cmdid;        /* Last used command ID */
+  BIT(ssldone);               /* Is connect() over SSL done? */
+  BIT(preauth);               /* Is this connection PREAUTH? */
+  BIT(tls_supported);         /* StartTLS capability supported by server */
+  BIT(login_disabled);        /* LOGIN command disabled by server */
+  BIT(ir_supported);          /* Initial response supported by server */
 };
 
 extern const struct Curl_handler Curl_handler_imap;
@@ -96,6 +96,6 @@
 
 /* Authentication type values */
 #define IMAP_TYPE_NONE      0
-#define IMAP_TYPE_ANY       ~0U
+#define IMAP_TYPE_ANY       (IMAP_TYPE_CLEARTEXT|IMAP_TYPE_SASL)
 
 #endif /* HEADER_CURL_IMAP_H */
diff --git a/Utilities/cmcurl/lib/inet_ntop.c b/Utilities/cmcurl/lib/inet_ntop.c
index 024f8da..770ed3a 100644
--- a/Utilities/cmcurl/lib/inet_ntop.c
+++ b/Utilities/cmcurl/lib/inet_ntop.c
@@ -42,6 +42,15 @@
 #define INT16SZ          2
 
 /*
+ * If ENABLE_IPV6 is disabled, we still want to parse IPv6 addresses, so make
+ * sure we have _some_ value for AF_INET6 without polluting our fake value
+ * everywhere.
+ */
+#if !defined(ENABLE_IPV6) && !defined(AF_INET6)
+#define AF_INET6 (AF_INET + 1)
+#endif
+
+/*
  * Format an IPv4 address, more or less like inet_ntop().
  *
  * Returns `dst' (as a const)
@@ -72,7 +81,6 @@
   return dst;
 }
 
-#ifdef ENABLE_IPV6
 /*
  * Convert IPv6 binary address into presentation (printable) format.
  */
@@ -168,7 +176,6 @@
   strcpy(dst, tmp);
   return dst;
 }
-#endif  /* ENABLE_IPV6 */
 
 /*
  * Convert a network format address to presentation format.
@@ -187,10 +194,8 @@
   switch(af) {
   case AF_INET:
     return inet_ntop4((const unsigned char *)src, buf, size);
-#ifdef ENABLE_IPV6
   case AF_INET6:
     return inet_ntop6((const unsigned char *)src, buf, size);
-#endif
   default:
     errno = EAFNOSUPPORT;
     return NULL;
diff --git a/Utilities/cmcurl/lib/inet_ntop.h b/Utilities/cmcurl/lib/inet_ntop.h
index 18fbd8b..7c3ead4 100644
--- a/Utilities/cmcurl/lib/inet_ntop.h
+++ b/Utilities/cmcurl/lib/inet_ntop.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/inet_pton.c b/Utilities/cmcurl/lib/inet_pton.c
index 47fb778..7d3c698 100644
--- a/Utilities/cmcurl/lib/inet_pton.c
+++ b/Utilities/cmcurl/lib/inet_pton.c
@@ -1,6 +1,6 @@
 /* This is from the BIND 4.9.4 release, modified to compile by itself */
 
-/* Copyright (c) 2003 - 2022 by Internet Software Consortium.
+/* Copyright (c) Internet Software Consortium.
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -39,14 +39,21 @@
 #define INT16SZ          2
 
 /*
+ * If ENABLE_IPV6 is disabled, we still want to parse IPv6 addresses, so make
+ * sure we have _some_ value for AF_INET6 without polluting our fake value
+ * everywhere.
+ */
+#if !defined(ENABLE_IPV6) && !defined(AF_INET6)
+#define AF_INET6 (AF_INET + 1)
+#endif
+
+/*
  * WARNING: Don't even consider trying to compile this on a system where
  * sizeof(int) < 4.  sizeof(int) > 4 is fine; all the world's not a VAX.
  */
 
 static int      inet_pton4(const char *src, unsigned char *dst);
-#ifdef ENABLE_IPV6
 static int      inet_pton6(const char *src, unsigned char *dst);
-#endif
 
 /* int
  * inet_pton(af, src, dst)
@@ -70,10 +77,8 @@
   switch(af) {
   case AF_INET:
     return (inet_pton4(src, (unsigned char *)dst));
-#ifdef ENABLE_IPV6
   case AF_INET6:
     return (inet_pton6(src, (unsigned char *)dst));
-#endif
   default:
     errno = EAFNOSUPPORT;
     return (-1);
@@ -135,7 +140,6 @@
   return (1);
 }
 
-#ifdef ENABLE_IPV6
 /* int
  * inet_pton6(src, dst)
  *      convert presentation level address to network order binary form.
@@ -234,6 +238,5 @@
   memcpy(dst, tmp, IN6ADDRSZ);
   return (1);
 }
-#endif /* ENABLE_IPV6 */
 
 #endif /* HAVE_INET_PTON */
diff --git a/Utilities/cmcurl/lib/inet_pton.h b/Utilities/cmcurl/lib/inet_pton.h
index 92ae93e..82fde7e 100644
--- a/Utilities/cmcurl/lib/inet_pton.h
+++ b/Utilities/cmcurl/lib/inet_pton.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/krb5.c b/Utilities/cmcurl/lib/krb5.c
index 08a6825..a71779a 100644
--- a/Utilities/cmcurl/lib/krb5.c
+++ b/Utilities/cmcurl/lib/krb5.c
@@ -2,7 +2,7 @@
  *
  * Copyright (c) 1995, 1996, 1997, 1998, 1999 Kungliga Tekniska Högskolan
  * (Royal Institute of Technology, Stockholm, Sweden).
- * Copyright (c) 2004 - 2022 Daniel Stenberg
+ * Copyright (C) Daniel Stenberg
  * All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
@@ -46,6 +46,8 @@
 #endif
 
 #include "urldata.h"
+#include "cfilters.h"
+#include "cf-socket.h"
 #include "curl_base64.h"
 #include "ftp.h"
 #include "curl_gssapi.h"
@@ -207,8 +209,8 @@
   gss_ctx_id_t *context = app_data;
   struct gss_channel_bindings_struct chan;
   size_t base64_sz = 0;
-  struct sockaddr_in **remote_addr =
-    (struct sockaddr_in **)&conn->ip_addr->ai_addr;
+  struct sockaddr_in *remote_addr =
+    (struct sockaddr_in *)(void *)&conn->remote_addr->sa_addr;
   char *stringp;
 
   if(getsockname(conn->sock[FIRSTSOCKET],
@@ -220,7 +222,7 @@
   chan.initiator_address.value = &conn->local_addr.sin_addr.s_addr;
   chan.acceptor_addrtype = GSS_C_AF_INET;
   chan.acceptor_address.length = l - 4;
-  chan.acceptor_address.value = &(*remote_addr)->sin_addr.s_addr;
+  chan.acceptor_address.value = &remote_addr->sin_addr.s_addr;
   chan.application_data.length = 0;
   chan.application_data.value = NULL;
 
@@ -454,15 +456,15 @@
 /* Read |len| from the socket |fd| and store it in |to|. Return a CURLcode
    saying whether an error occurred or CURLE_OK if |len| was read. */
 static CURLcode
-socket_read(struct Curl_easy *data, curl_socket_t fd, void *to, size_t len)
+socket_read(struct Curl_easy *data, int sockindex, void *to, size_t len)
 {
   char *to_p = to;
   CURLcode result;
   ssize_t nread = 0;
 
   while(len > 0) {
-    result = Curl_read_plain(data, fd, to_p, len, &nread);
-    if(!result) {
+    nread = Curl_conn_recv(data, sockindex, to_p, len, &result);
+    if(nread > 0) {
       len -= nread;
       to_p += nread;
     }
@@ -480,7 +482,7 @@
    CURLcode saying whether an error occurred or CURLE_OK if |len| was
    written. */
 static CURLcode
-socket_write(struct Curl_easy *data, curl_socket_t fd, const void *to,
+socket_write(struct Curl_easy *data, int sockindex, const void *to,
              size_t len)
 {
   const char *to_p = to;
@@ -488,8 +490,8 @@
   ssize_t written;
 
   while(len > 0) {
-    result = Curl_write_plain(data, fd, to_p, len, &written);
-    if(!result) {
+    written = Curl_conn_send(data, sockindex, to_p, len, &result);
+    if(written > 0) {
       len -= written;
       to_p += written;
     }
@@ -502,7 +504,7 @@
   return CURLE_OK;
 }
 
-static CURLcode read_data(struct Curl_easy *data, curl_socket_t fd,
+static CURLcode read_data(struct Curl_easy *data, int sockindex,
                           struct krb5buffer *buf)
 {
   struct connectdata *conn = data->conn;
@@ -510,7 +512,7 @@
   CURLcode result;
   int nread;
 
-  result = socket_read(data, fd, &len, sizeof(len));
+  result = socket_read(data, sockindex, &len, sizeof(len));
   if(result)
     return result;
 
@@ -525,7 +527,7 @@
   if(!len || !buf->data)
     return CURLE_OUT_OF_MEMORY;
 
-  result = socket_read(data, fd, buf->data, len);
+  result = socket_read(data, sockindex, buf->data, len);
   if(result)
     return result;
   nread = conn->mech->decode(conn->app_data, buf->data, len,
@@ -554,13 +556,12 @@
   size_t bytes_read;
   size_t total_read = 0;
   struct connectdata *conn = data->conn;
-  curl_socket_t fd = conn->sock[sockindex];
 
   *err = CURLE_OK;
 
   /* Handle clear text response. */
   if(conn->sec_complete == 0 || conn->data_prot == PROT_CLEAR)
-    return Curl_recv_plain(data, sockindex, buffer, len, err);
+    return Curl_conn_recv(data, sockindex, buffer, len, err);
 
   if(conn->in_buffer.eof_flag) {
     conn->in_buffer.eof_flag = 0;
@@ -573,7 +574,7 @@
   buffer += bytes_read;
 
   while(len > 0) {
-    if(read_data(data, fd, &conn->in_buffer))
+    if(read_data(data, sockindex, &conn->in_buffer))
       return -1;
     if(conn->in_buffer.size == 0) {
       if(bytes_read > 0)
@@ -720,8 +721,7 @@
     return 0;
 
   if(buf[3] != '-')
-    /* safe to ignore return code */
-    (void)sscanf(buf, "%d", &ret_code);
+    ret_code = atoi(buf);
 
   if(buf[decoded_len - 1] == '\n')
     buf[decoded_len - 1] = '\0';
@@ -764,8 +764,9 @@
 
     pbsz = strstr(data->state.buffer, "PBSZ=");
     if(pbsz) {
-      /* ignore return code, use default value if it fails */
-      (void)sscanf(pbsz, "PBSZ=%u", &buffer_size);
+      /* stick to default value if the check fails */
+      if(!strncmp(pbsz, "PBSZ=", 5) && ISDIGIT(pbsz[5]))
+        buffer_size = atoi(&pbsz[5]);
       if(buffer_size < conn->buffer_size)
         conn->buffer_size = buffer_size;
     }
diff --git a/Utilities/cmcurl/lib/ldap.c b/Utilities/cmcurl/lib/ldap.c
index 92006d6..595e4b3 100644
--- a/Utilities/cmcurl/lib/ldap.c
+++ b/Utilities/cmcurl/lib/ldap.c
@@ -5,7 +5,7 @@
  *                | (__| |_| |  _ <| |___
  *                 \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -140,6 +140,14 @@
 #define ldap_err2string ldap_err2stringA
 #endif
 
+#if defined(USE_WIN32_LDAP) && defined(_MSC_VER) && (_MSC_VER <= 1600)
+/* Workaround for warning:
+   'type cast' : conversion from 'int' to 'void *' of greater size */
+#undef LDAP_OPT_ON
+#undef LDAP_OPT_OFF
+#define LDAP_OPT_ON   ((void *)(size_t)1)
+#define LDAP_OPT_OFF  ((void *)(size_t)0)
+#endif
 
 static CURLcode ldap_do(struct Curl_easy *data, bool *done);
 
diff --git a/Utilities/cmcurl/lib/libcurl.rc b/Utilities/cmcurl/lib/libcurl.rc
index 23134a7..daa2d62 100644
--- a/Utilities/cmcurl/lib/libcurl.rc
+++ b/Utilities/cmcurl/lib/libcurl.rc
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/llist.c b/Utilities/cmcurl/lib/llist.c
index fa2d366..5b6b033 100644
--- a/Utilities/cmcurl/lib/llist.c
+++ b/Utilities/cmcurl/lib/llist.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/llist.h b/Utilities/cmcurl/lib/llist.h
index 2fcb91c..320580e 100644
--- a/Utilities/cmcurl/lib/llist.h
+++ b/Utilities/cmcurl/lib/llist.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/md4.c b/Utilities/cmcurl/lib/md4.c
index c13b080..318e9da 100644
--- a/Utilities/cmcurl/lib/md4.c
+++ b/Utilities/cmcurl/lib/md4.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -86,11 +86,7 @@
 #include "memdebug.h"
 
 
-#if defined(USE_WOLFSSL) && !defined(WOLFSSL_NO_MD4)
-
-#elif defined(USE_OPENSSL) && !defined(OPENSSL_NO_MD4)
-
-#elif defined(USE_GNUTLS)
+#if defined(USE_GNUTLS)
 
 typedef struct md4_ctx MD4_CTX;
 
@@ -109,6 +105,10 @@
   md4_digest(ctx, MD4_DIGEST_SIZE, result);
 }
 
+#elif defined(USE_WOLFSSL) && !defined(WOLFSSL_NO_MD4)
+
+#elif defined(USE_OPENSSL) && !defined(OPENSSL_NO_MD4)
+
 #elif defined(AN_APPLE_OS)
 typedef CC_MD4_CTX MD4_CTX;
 
diff --git a/Utilities/cmcurl/lib/md5.c b/Utilities/cmcurl/lib/md5.c
index 9610e0c..f57ef39 100644
--- a/Utilities/cmcurl/lib/md5.c
+++ b/Utilities/cmcurl/lib/md5.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/memdebug.c b/Utilities/cmcurl/lib/memdebug.c
index 15fb491..d6952a0 100644
--- a/Utilities/cmcurl/lib/memdebug.c
+++ b/Utilities/cmcurl/lib/memdebug.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/memdebug.h b/Utilities/cmcurl/lib/memdebug.h
index 7fc90e8..c9eb5dc 100644
--- a/Utilities/cmcurl/lib/memdebug.h
+++ b/Utilities/cmcurl/lib/memdebug.h
@@ -8,7 +8,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/mime.c b/Utilities/cmcurl/lib/mime.c
index e3f2821..83846c5 100644
--- a/Utilities/cmcurl/lib/mime.c
+++ b/Utilities/cmcurl/lib/mime.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/mime.h b/Utilities/cmcurl/lib/mime.h
index b9ea0f1..04adf2d 100644
--- a/Utilities/cmcurl/lib/mime.h
+++ b/Utilities/cmcurl/lib/mime.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/mprintf.c b/Utilities/cmcurl/lib/mprintf.c
index 8a7c17a..5de935b 100644
--- a/Utilities/cmcurl/lib/mprintf.c
+++ b/Utilities/cmcurl/lib/mprintf.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1999 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/mqtt.c b/Utilities/cmcurl/lib/mqtt.c
index 8ba826f..47af369 100644
--- a/Utilities/cmcurl/lib/mqtt.c
+++ b/Utilities/cmcurl/lib/mqtt.c
@@ -5,8 +5,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2020 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2019, Björn Stenberg, <bjorn@haxx.se>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Björn Stenberg, <bjorn@haxx.se>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -122,8 +122,9 @@
   struct MQTT *mq = data->req.p.mqtt;
   ssize_t n;
   result = Curl_write(data, sockfd, buf, len, &n);
-  if(!result)
-    Curl_debug(data, CURLINFO_HEADER_OUT, buf, (size_t)n);
+  if(result)
+    return result;
+  Curl_debug(data, CURLINFO_HEADER_OUT, buf, (size_t)n);
   if(len != (size_t)n) {
     size_t nsend = len - n;
     char *sendleftovers = Curl_memdup(&buf[n], nsend);
diff --git a/Utilities/cmcurl/lib/mqtt.h b/Utilities/cmcurl/lib/mqtt.h
index c400d9b..6396136 100644
--- a/Utilities/cmcurl/lib/mqtt.h
+++ b/Utilities/cmcurl/lib/mqtt.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2019 - 2022, Björn Stenberg, <bjorn@haxx.se>
+ * Copyright (C) Björn Stenberg, <bjorn@haxx.se>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/multi.c b/Utilities/cmcurl/lib/multi.c
index b96ee7c..731b259 100644
--- a/Utilities/cmcurl/lib/multi.c
+++ b/Utilities/cmcurl/lib/multi.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -445,9 +445,6 @@
   sockhash_destroy(&multi->sockhash);
   Curl_hash_destroy(&multi->hostcache);
   Curl_conncache_destroy(&multi->conn_cache);
-  Curl_llist_destroy(&multi->msglist, NULL);
-  Curl_llist_destroy(&multi->pending, NULL);
-
   free(multi);
   return NULL;
 }
@@ -459,6 +456,42 @@
                            CURL_DNS_HASH_SIZE);
 }
 
+static void link_easy(struct Curl_multi *multi,
+                      struct Curl_easy *data)
+{
+  /* We add the new easy entry last in the list. */
+  data->next = NULL; /* end of the line */
+  if(multi->easyp) {
+    struct Curl_easy *last = multi->easylp;
+    last->next = data;
+    data->prev = last;
+    multi->easylp = data; /* the new last node */
+  }
+  else {
+    /* first node, make prev NULL! */
+    data->prev = NULL;
+    multi->easylp = multi->easyp = data; /* both first and last */
+  }
+}
+
+/* unlink the given easy handle from the linked list of easy handles */
+static void unlink_easy(struct Curl_multi *multi,
+                        struct Curl_easy *data)
+{
+  /* make the previous node point to our next */
+  if(data->prev)
+    data->prev->next = data->next;
+  else
+    multi->easyp = data->next; /* point to first node */
+
+  /* make our next point to our previous node */
+  if(data->next)
+    data->next->prev = data->prev;
+  else
+    multi->easylp = data->prev; /* point to last node */
+}
+
+
 CURLMcode curl_multi_add_handle(struct Curl_multi *multi,
                                 struct Curl_easy *data)
 {
@@ -554,19 +587,7 @@
     data->psl = &multi->psl;
 #endif
 
-  /* We add the new entry last in the list. */
-  data->next = NULL; /* end of the line */
-  if(multi->easyp) {
-    struct Curl_easy *last = multi->easylp;
-    last->next = data;
-    data->prev = last;
-    multi->easylp = data; /* the new last node */
-  }
-  else {
-    /* first node, make prev NULL! */
-    data->prev = NULL;
-    multi->easylp = multi->easyp = data; /* both first and last */
-  }
+  link_easy(multi, data);
 
   /* increase the node-counter */
   multi->num_easy++;
@@ -655,6 +676,9 @@
       result = CURLE_ABORTED_BY_CALLBACK;
   }
 
+  /* Inform connection filters that this transfer is done */
+  Curl_conn_ev_data_done(data, premature);
+
   process_pending_handles(data->multi); /* connection / multiplex */
 
   CONNCACHE_LOCK(data);
@@ -709,12 +733,12 @@
            conn->proxy_negotiate_state == GSS_AUTHRECV)
 #endif
      ) || conn->bits.close
-       || (premature && !(conn->handler->flags & PROTOPT_STREAM))) {
+       || (premature && !Curl_conn_is_multiplex(conn, FIRSTSOCKET))) {
     DEBUGF(infof(data, "multi_done, not re-using connection=%ld, forbid=%d"
-                 ", close=%d, premature=%d, stream=%d",
+                 ", close=%d, premature=%d, conn_multiplex=%d",
                  conn->connection_id,
                  data->set.reuse_forbid, conn->bits.close, premature,
-                 (conn->handler->flags & PROTOPT_STREAM)));
+                 Curl_conn_is_multiplex(conn, FIRSTSOCKET)));
     connclose(conn, "disconnecting");
     Curl_conncache_remove_conn(data, conn, FALSE);
     CONNCACHE_UNLOCK(data);
@@ -838,10 +862,6 @@
 
   Curl_wildcard_dtor(&data->wildcard);
 
-  /* destroy the timeout list that is held in the easy handle, do this *after*
-     multi_done() as that may actually call Curl_expire that uses this */
-  Curl_llist_destroy(&data->state.timeoutlist, NULL);
-
   /* change state without using multistate(), only to make singlesocket() do
      what we want */
   data->mstate = MSTATE_COMPLETED;
@@ -914,17 +934,7 @@
     }
   }
 
-  /* make the previous node point to our next */
-  if(data->prev)
-    data->prev->next = data->next;
-  else
-    multi->easyp = data->next; /* point to first node */
-
-  /* make our next point to our previous node */
-  if(data->next)
-    data->next->prev = data->prev;
-  else
-    multi->easylp = data->prev; /* point to last node */
+  unlink_easy(multi, data);
 
   /* NOTE NOTE NOTE
      We do not touch the easy handle here! */
@@ -954,7 +964,7 @@
 {
   struct connectdata *conn = data->conn;
   if(conn) {
-    Curl_conn_detach_data(conn, data);
+    Curl_conn_ev_data_detach(conn, data);
     Curl_llist_remove(&conn->easyq, &data->conn_queue, NULL);
   }
   data->conn = NULL;
@@ -973,9 +983,9 @@
   data->conn = conn;
   Curl_llist_insert_next(&conn->easyq, conn->easyq.tail, data,
                          &data->conn_queue);
-  Curl_conn_attach_data(conn, data);
-  if(conn->handler->attach)
+  if(conn->handler && conn->handler->attach)
     conn->handler->attach(data, conn);
+  Curl_conn_ev_data_attach(conn, data);
 }
 
 static int domore_getsock(struct Curl_easy *data,
@@ -1002,11 +1012,7 @@
 {
   if(conn->handler->proto_getsock)
     return conn->handler->proto_getsock(data, conn, socks);
-  /* Backup getsock logic. Since there is a live socket in use, we must wait
-     for it or it will be removed from watching when the multi_socket API is
-     used. */
-  socks[0] = conn->sock[FIRSTSOCKET];
-  return GETSOCK_READSOCK(0) | GETSOCK_WRITESOCK(0);
+  return Curl_conn_get_select_socks(data, FIRSTSOCKET, socks);
 }
 
 /* returns bitmapped flags for this handle and its sockets. The 'socks[]'
@@ -1111,6 +1117,22 @@
   return CURLM_OK;
 }
 
+#ifdef USE_WINSOCK
+/* Reset FD_WRITE for TCP sockets. Nothing is actually sent. UDP sockets can't
+ * be reset this way because an empty datagram would be sent. #9203
+ *
+ * "On Windows the internal state of FD_WRITE as returned from
+ * WSAEnumNetworkEvents is only reset after successful send()."
+ */
+static void reset_socket_fdwrite(curl_socket_t s)
+{
+  int t;
+  int l = (int)sizeof(t);
+  if(!getsockopt(s, SOL_SOCKET, SO_TYPE, (char *)&t, &l) && t == SOCK_STREAM)
+    send(s, NULL, 0, 0);
+}
+#endif
+
 #define NUM_POLLS_ON_STACK 10
 
 static CURLMcode multi_wait(struct Curl_multi *multi,
@@ -1232,7 +1254,7 @@
           s = sockbunch[i];
 #ifdef USE_WINSOCK
           mask |= FD_WRITE|FD_CONNECT|FD_CLOSE;
-          send(s, NULL, 0, 0); /* reset FD_WRITE */
+          reset_socket_fdwrite(s);
 #endif
           ufds[nfds].fd = s;
           ufds[nfds].events = POLLOUT;
@@ -1266,7 +1288,7 @@
       mask |= FD_OOB;
     if(extra_fds[i].events & CURL_WAIT_POLLOUT) {
       mask |= FD_WRITE|FD_CONNECT|FD_CLOSE;
-      send(extra_fds[i].fd, NULL, 0, 0); /* reset FD_WRITE */
+      reset_socket_fdwrite(extra_fds[i].fd);
     }
     if(WSAEventSelect(extra_fds[i].fd, multi->wsa_event, mask) != 0) {
       if(ufds_malloc)
@@ -1862,6 +1884,15 @@
     multistate(data, MSTATE_COMPLETED);
   }
 
+#ifdef DEBUGBUILD
+  if(!multi->warned) {
+    infof(data, "!!! WARNING !!!");
+    infof(data, "This is a debug build of libcurl, "
+          "do not use in production.");
+    multi->warned = true;
+  }
+#endif
+
   do {
     /* A "stream" here is a logical stream if the protocol can handle that
        (HTTP/2), or the full connection for older protocols */
@@ -2168,7 +2199,7 @@
 #ifndef CURL_DISABLE_FTP
             /* some steps needed for wildcard matching */
             if(data->state.wildcardmatch) {
-              struct WildcardData *wc = &data->wildcard;
+              struct WildcardData *wc = data->wildcard;
               if(wc->state == CURLWC_DONE || wc->state == CURLWC_SKIP) {
                 /* skip some states if it is important */
                 multi_done(data, CURLE_OK, FALSE);
@@ -2320,7 +2351,7 @@
 #ifndef CURL_DISABLE_FTP
         if(data->state.wildcardmatch &&
            ((data->conn->handler->flags & PROTOPT_WILDCARD) == 0)) {
-          data->wildcard.state = CURLWC_DONE;
+          data->wildcard->state = CURLWC_DONE;
         }
 #endif
         multistate(data, MSTATE_DONE);
@@ -2550,7 +2581,7 @@
 
 #ifndef CURL_DISABLE_FTP
       if(data->state.wildcardmatch) {
-        if(data->wildcard.state != CURLWC_DONE) {
+        if(data->wildcard->state != CURLWC_DONE) {
           /* if a wildcard is set and we are not ending -> lets start again
              with MSTATE_INIT */
           multistate(data, MSTATE_INIT);
@@ -2682,18 +2713,25 @@
     return CURLM_RECURSIVE_API_CALL;
 
   data = multi->easyp;
-  while(data) {
+  if(data) {
     CURLMcode result;
+    bool nosig = data->set.no_signal;
     SIGPIPE_VARIABLE(pipe_st);
-
     sigpipe_ignore(data, &pipe_st);
-    result = multi_runsingle(multi, &now, data);
+    /* Do the loop and only alter the signal ignore state if the next handle
+       has a different NO_SIGNAL state than the previous */
+    do {
+      if(data->set.no_signal != nosig) {
+        sigpipe_restore(&pipe_st);
+        sigpipe_ignore(data, &pipe_st);
+        nosig = data->set.no_signal;
+      }
+      result = multi_runsingle(multi, &now, data);
+      if(result)
+        returncode = result;
+      data = data->next; /* operate on next handle */
+    } while(data);
     sigpipe_restore(&pipe_st);
-
-    if(result)
-      returncode = result;
-
-    data = data->next; /* operate on next handle */
   }
 
   /*
@@ -2764,9 +2802,6 @@
 
     sockhash_destroy(&multi->sockhash);
     Curl_conncache_destroy(&multi->conn_cache);
-    Curl_llist_destroy(&multi->msglist, NULL);
-    Curl_llist_destroy(&multi->pending, NULL);
-
     Curl_hash_destroy(&multi->hostcache);
     Curl_psl_destroy(&multi->psl);
 
@@ -3248,7 +3283,7 @@
     multi->push_userp = va_arg(param, void *);
     break;
   case CURLMOPT_PIPELINING:
-    multi->multiplexing = va_arg(param, long) & CURLPIPE_MULTIPLEX;
+    multi->multiplexing = va_arg(param, long) & CURLPIPE_MULTIPLEX ? 1 : 0;
     break;
   case CURLMOPT_TIMERFUNCTION:
     multi->timer_cb = va_arg(param, curl_multi_timer_callback);
diff --git a/Utilities/cmcurl/lib/multihandle.h b/Utilities/cmcurl/lib/multihandle.h
index 5a83656..6cda65d 100644
--- a/Utilities/cmcurl/lib/multihandle.h
+++ b/Utilities/cmcurl/lib/multihandle.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -162,14 +162,17 @@
 #define IPV6_DEAD    1
 #define IPV6_WORKS   2
   unsigned char ipv6_up;       /* IPV6_* defined */
-  bool multiplexing;           /* multiplexing wanted */
-  bool recheckstate;           /* see Curl_multi_connchanged */
-  bool in_callback;            /* true while executing a callback */
+  BIT(multiplexing);           /* multiplexing wanted */
+  BIT(recheckstate);           /* see Curl_multi_connchanged */
+  BIT(in_callback);            /* true while executing a callback */
 #ifdef USE_OPENSSL
-  bool ssl_seeded;
+  BIT(ssl_seeded);
 #endif
-  bool dead; /* a callback returned error, everything needs to crash and
+  BIT(dead); /* a callback returned error, everything needs to crash and
                 burn */
+#ifdef DEBUGBUILD
+  BIT(warned);                 /* true after user warned of DEBUGBUILD */
+#endif
 };
 
 #endif /* HEADER_CURL_MULTIHANDLE_H */
diff --git a/Utilities/cmcurl/lib/multiif.h b/Utilities/cmcurl/lib/multiif.h
index 0cb9d4f..cae02cb 100644
--- a/Utilities/cmcurl/lib/multiif.h
+++ b/Utilities/cmcurl/lib/multiif.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/netrc.c b/Utilities/cmcurl/lib/netrc.c
index 4461b84..aa1b80a 100644
--- a/Utilities/cmcurl/lib/netrc.c
+++ b/Utilities/cmcurl/lib/netrc.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/netrc.h b/Utilities/cmcurl/lib/netrc.h
index 53d0056..9f2815f 100644
--- a/Utilities/cmcurl/lib/netrc.h
+++ b/Utilities/cmcurl/lib/netrc.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/nonblock.c b/Utilities/cmcurl/lib/nonblock.c
index 8447b6f..f4eb656 100644
--- a/Utilities/cmcurl/lib/nonblock.c
+++ b/Utilities/cmcurl/lib/nonblock.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/nonblock.h b/Utilities/cmcurl/lib/nonblock.h
index a42f443..4a1a615 100644
--- a/Utilities/cmcurl/lib/nonblock.h
+++ b/Utilities/cmcurl/lib/nonblock.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/noproxy.c b/Utilities/cmcurl/lib/noproxy.c
index 9b13fe8..f1c1ed2 100644
--- a/Utilities/cmcurl/lib/noproxy.c
+++ b/Utilities/cmcurl/lib/noproxy.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -119,8 +119,10 @@
 * Checks if the host is in the noproxy list. returns TRUE if it matches and
 * therefore the proxy should NOT be used.
 ****************************************************************/
-bool Curl_check_noproxy(const char *name, const char *no_proxy)
+bool Curl_check_noproxy(const char *name, const char *no_proxy,
+                        bool *spacesep)
 {
+  *spacesep = FALSE;
   /*
    * If we don't have a hostname at all, like for example with a FILE
    * transfer, we have nothing to interrogate the noproxy list with.
@@ -244,6 +246,15 @@
         if(match)
           return TRUE;
       } /* if(tokenlen) */
+      /* pass blanks after pattern */
+      while(ISBLANK(*p))
+        p++;
+      /* if not a comma! */
+      if(*p && (*p != ',')) {
+        *spacesep = TRUE;
+        continue;
+      }
+      /* pass any number of commas */
       while(*p == ',')
         p++;
     } /* while(*p) */
diff --git a/Utilities/cmcurl/lib/noproxy.h b/Utilities/cmcurl/lib/noproxy.h
index 8800a21..a3a6807 100644
--- a/Utilities/cmcurl/lib/noproxy.h
+++ b/Utilities/cmcurl/lib/noproxy.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -37,7 +37,8 @@
                                unsigned int bits);
 #endif
 
-bool Curl_check_noproxy(const char *name, const char *no_proxy);
+bool Curl_check_noproxy(const char *name, const char *no_proxy,
+                        bool *spacesep);
 
 #endif
 
diff --git a/Utilities/cmcurl/lib/openldap.c b/Utilities/cmcurl/lib/openldap.c
index ab81e57..b9feeda 100644
--- a/Utilities/cmcurl/lib/openldap.c
+++ b/Utilities/cmcurl/lib/openldap.c
@@ -5,8 +5,8 @@
  *                | (__| |_| |  _ <| |___
  *                 \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2011 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2010, Howard Chu, <hyc@openldap.org>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Howard Chu, <hyc@openldap.org>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/parsedate.c b/Utilities/cmcurl/lib/parsedate.c
index 5ed8819..1662dd3 100644
--- a/Utilities/cmcurl/lib/parsedate.c
+++ b/Utilities/cmcurl/lib/parsedate.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -212,56 +212,55 @@
 {
   int i;
   const char * const *what;
-  bool found = FALSE;
   if(len > 3)
     what = &weekday[0];
-  else
+  else if(len == 3)
     what = &Curl_wkday[0];
+  else
+    return -1; /* too short */
   for(i = 0; i<7; i++) {
-    if(strcasecompare(check, what[0])) {
-      found = TRUE;
-      break;
-    }
+    size_t ilen = strlen(what[0]);
+    if((ilen == len) &&
+       strncasecompare(check, what[0], len))
+      return i;
     what++;
   }
-  return found?i:-1;
+  return -1;
 }
 
-static int checkmonth(const char *check)
+static int checkmonth(const char *check, size_t len)
 {
   int i;
-  const char * const *what;
-  bool found = FALSE;
+  const char * const *what = &Curl_month[0];
+  if(len != 3)
+    return -1; /* not a month */
 
-  what = &Curl_month[0];
   for(i = 0; i<12; i++) {
-    if(strcasecompare(check, what[0])) {
-      found = TRUE;
-      break;
-    }
+    if(strncasecompare(check, what[0], 3))
+      return i;
     what++;
   }
-  return found?i:-1; /* return the offset or -1, no real offset is -1 */
+  return -1; /* return the offset or -1, no real offset is -1 */
 }
 
 /* return the time zone offset between GMT and the input one, in number
    of seconds or -1 if the timezone wasn't found/legal */
 
-static int checktz(const char *check)
+static int checktz(const char *check, size_t len)
 {
   unsigned int i;
-  const struct tzinfo *what;
-  bool found = FALSE;
+  const struct tzinfo *what = tz;
+  if(len > 4) /* longer than any valid timezone */
+    return -1;
 
-  what = tz;
   for(i = 0; i< sizeof(tz)/sizeof(tz[0]); i++) {
-    if(strcasecompare(check, what->name)) {
-      found = TRUE;
-      break;
-    }
+    size_t ilen = strlen(what->name);
+    if((ilen == len) &&
+       strncasecompare(check, what->name, len))
+      return what->offset*60;
     what++;
   }
-  return found?what->offset*60:-1;
+  return -1;
 }
 
 static void skip(const char **date)
@@ -294,6 +293,53 @@
            + hour) * 60 + min) * 60 + sec;
 }
 
+/* Returns the value of a single-digit or two-digit decimal number, return
+   then pointer to after the number. The 'date' pointer is known to point to a
+   digit. */
+static int oneortwodigit(const char *date, const char **endp)
+{
+  int num = date[0] - '0';
+  if(ISDIGIT(date[1])) {
+    *endp = &date[2];
+    return num*10 + (date[1] - '0');
+  }
+  *endp = &date[1];
+  return num;
+}
+
+
+/* HH:MM:SS or HH:MM and accept single-digits too */
+static bool match_time(const char *date,
+                       int *h, int *m, int *s, char **endp)
+{
+  const char *p;
+  int hh, mm, ss = 0;
+  hh = oneortwodigit(date, &p);
+  if((hh < 24) && (*p == ':') && ISDIGIT(p[1])) {
+    mm = oneortwodigit(&p[1], &p);
+    if(mm < 60) {
+      if((*p == ':') && ISDIGIT(p[1])) {
+        ss = oneortwodigit(&p[1], &p);
+        if(ss <= 60) {
+          /* valid HH:MM:SS */
+          goto match;
+        }
+      }
+      else {
+        /* valid HH:MM */
+        goto match;
+      }
+    }
+  }
+  return FALSE; /* not a time string */
+  match:
+  *h = hh;
+  *m = mm;
+  *s = ss;
+  *endp = (char *)p;
+  return TRUE;
+}
+
 /*
  * parsedate()
  *
@@ -305,6 +351,9 @@
  * PARSEDATE_SOONER - time underflow at the low end of time_t
  */
 
+/* Wednesday is the longest name this parser knows about */
+#define NAME_LEN 12
+
 static int parsedate(const char *date, time_t *output)
 {
   time_t t = 0;
@@ -327,32 +376,32 @@
 
     if(ISALPHA(*date)) {
       /* a name coming up */
-      char buf[32]="";
-      size_t len;
-      if(sscanf(date, "%31[ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-                          "abcdefghijklmnopqrstuvwxyz]", buf))
-        len = strlen(buf);
-      else
-        len = 0;
-
-      if(wdaynum == -1) {
-        wdaynum = checkday(buf, len);
-        if(wdaynum != -1)
-          found = TRUE;
-      }
-      if(!found && (monnum == -1)) {
-        monnum = checkmonth(buf);
-        if(monnum != -1)
-          found = TRUE;
+      size_t len = 0;
+      const char *p = date;
+      while(ISALPHA(*p) && (len < NAME_LEN)) {
+        p++;
+        len++;
       }
 
-      if(!found && (tzoff == -1)) {
-        /* this just must be a time zone string */
-        tzoff = checktz(buf);
-        if(tzoff != -1)
-          found = TRUE;
-      }
+      if(len != NAME_LEN) {
+        if(wdaynum == -1) {
+          wdaynum = checkday(date, len);
+          if(wdaynum != -1)
+            found = TRUE;
+        }
+        if(!found && (monnum == -1)) {
+          monnum = checkmonth(date, len);
+          if(monnum != -1)
+            found = TRUE;
+        }
 
+        if(!found && (tzoff == -1)) {
+          /* this just must be a time zone string */
+          tzoff = checktz(date, len);
+          if(tzoff != -1)
+            found = TRUE;
+        }
+      }
       if(!found)
         return PARSEDATE_FAIL; /* bad string */
 
@@ -362,18 +411,10 @@
       /* a digit */
       int val;
       char *end;
-      int len = 0;
       if((secnum == -1) &&
-         (3 == sscanf(date, "%02d:%02d:%02d%n",
-                      &hournum, &minnum, &secnum, &len))) {
-        /* time stamp! */
-        date += len;
-      }
-      else if((secnum == -1) &&
-              (2 == sscanf(date, "%02d:%02d%n", &hournum, &minnum, &len))) {
-        /* time stamp without seconds */
-        date += len;
-        secnum = 0;
+         match_time(date, &hournum, &minnum, &secnum, &end)) {
+        /* time stamp */
+        date = end;
       }
       else {
         long lval;
diff --git a/Utilities/cmcurl/lib/parsedate.h b/Utilities/cmcurl/lib/parsedate.h
index 4e43477..84c37f1 100644
--- a/Utilities/cmcurl/lib/parsedate.h
+++ b/Utilities/cmcurl/lib/parsedate.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/pingpong.c b/Utilities/cmcurl/lib/pingpong.c
index 9b95580..2f4aa1c 100644
--- a/Utilities/cmcurl/lib/pingpong.c
+++ b/Utilities/cmcurl/lib/pingpong.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/pingpong.h b/Utilities/cmcurl/lib/pingpong.h
index cefae07..80d3f77 100644
--- a/Utilities/cmcurl/lib/pingpong.h
+++ b/Utilities/cmcurl/lib/pingpong.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/pop3.c b/Utilities/cmcurl/lib/pop3.c
index ce17f2a..36707e5 100644
--- a/Utilities/cmcurl/lib/pop3.c
+++ b/Utilities/cmcurl/lib/pop3.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -370,16 +370,18 @@
   /* Start the SSL connection */
   struct pop3_conn *pop3c = &conn->proto.pop3c;
   CURLcode result;
+  bool ssldone = FALSE;
 
-  if(!Curl_conn_is_ssl(data, FIRSTSOCKET)) {
+  if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
     result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET);
     if(result)
       goto out;
   }
 
-  result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &pop3c->ssldone);
+  result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone);
 
   if(!result) {
+    pop3c->ssldone = ssldone;
     if(pop3c->state != POP3_UPGRADETLS)
       state(data, POP3_UPGRADETLS);
 
@@ -769,7 +771,7 @@
     if(pop3code != '+')
       pop3c->authtypes |= POP3_TYPE_CLEARTEXT;
 
-    if(!data->set.use_ssl || Curl_conn_is_ssl(data, FIRSTSOCKET))
+    if(!data->set.use_ssl || Curl_conn_is_ssl(conn, FIRSTSOCKET))
       result = pop3_perform_authentication(data, conn);
     else if(pop3code == '+' && pop3c->tls_supported)
       /* Switch to TLS connection now */
@@ -1056,7 +1058,9 @@
   struct pop3_conn *pop3c = &conn->proto.pop3c;
 
   if((conn->handler->flags & PROTOPT_SSL) && !pop3c->ssldone) {
-    result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &pop3c->ssldone);
+    bool ssldone = FALSE;
+    result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone);
+    pop3c->ssldone = ssldone;
     if(result || !pop3c->ssldone)
       return result;
   }
diff --git a/Utilities/cmcurl/lib/pop3.h b/Utilities/cmcurl/lib/pop3.h
index bb0645f..83f0f83 100644
--- a/Utilities/cmcurl/lib/pop3.h
+++ b/Utilities/cmcurl/lib/pop3.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2009 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -62,16 +62,16 @@
 struct pop3_conn {
   struct pingpong pp;
   pop3state state;        /* Always use pop3.c:state() to change state! */
-  bool ssldone;           /* Is connect() over SSL done? */
-  bool tls_supported;     /* StartTLS capability supported by server */
   size_t eob;             /* Number of bytes of the EOB (End Of Body) that
                              have been received so far */
   size_t strip;           /* Number of bytes from the start to ignore as
                              non-body */
   struct SASL sasl;       /* SASL-related storage */
-  unsigned int authtypes; /* Accepted authentication types */
-  unsigned int preftype;  /* Preferred authentication type */
   char *apoptimestamp;    /* APOP timestamp from the server greeting */
+  unsigned char authtypes; /* Accepted authentication types */
+  unsigned char preftype;  /* Preferred authentication type */
+  BIT(ssldone);           /* Is connect() over SSL done? */
+  BIT(tls_supported);     /* StartTLS capability supported by server */
 };
 
 extern const struct Curl_handler Curl_handler_pop3;
@@ -84,7 +84,7 @@
 
 /* Authentication type values */
 #define POP3_TYPE_NONE      0
-#define POP3_TYPE_ANY       ~0U
+#define POP3_TYPE_ANY       (POP3_TYPE_CLEARTEXT|POP3_TYPE_APOP|POP3_TYPE_SASL)
 
 /* This is the 5-bytes End-Of-Body marker for POP3 */
 #define POP3_EOB "\x0d\x0a\x2e\x0d\x0a"
diff --git a/Utilities/cmcurl/lib/progress.c b/Utilities/cmcurl/lib/progress.c
index 4a1e1da..6092b78 100644
--- a/Utilities/cmcurl/lib/progress.c
+++ b/Utilities/cmcurl/lib/progress.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -87,8 +87,6 @@
               CURL_FORMAT_CURL_OFF_T "M", bytes/ONE_MEGABYTE,
               (bytes%ONE_MEGABYTE) / (ONE_MEGABYTE/CURL_OFF_T_C(10)) );
 
-#if (SIZEOF_CURL_OFF_T > 4)
-
   else if(bytes < CURL_OFF_T_C(10000) * ONE_MEGABYTE)
     /* 'XXXXM' is good until we're at 10000MB or above */
     msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "M", bytes/ONE_MEGABYTE);
@@ -111,15 +109,8 @@
     /* up to 10000PB, display without decimal: XXXXP */
     msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "P", bytes/ONE_PETABYTE);
 
-    /* 16384 petabytes (16 exabytes) is the maximum a 64 bit unsigned number
-       can hold, but our data type is signed so 8192PB will be the maximum. */
-
-#else
-
-  else
-    msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "M", bytes/ONE_MEGABYTE);
-
-#endif
+  /* 16384 petabytes (16 exabytes) is the maximum a 64 bit unsigned number can
+     hold, but our data type is signed so 8192PB will be the maximum. */
 
   return max5;
 }
@@ -166,14 +157,11 @@
 
 /*
  *
- * Curl_pgrsTime(). Store the current time at the given label. This fetches a
- * fresh "now" and returns it.
- *
- * @unittest: 1399
+ * Curl_pgrsTimeWas(). Store the timestamp time at the given label.
  */
-struct curltime Curl_pgrsTime(struct Curl_easy *data, timerid timer)
+void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer,
+                      struct curltime timestamp)
 {
-  struct curltime now = Curl_now();
   timediff_t *delta = NULL;
 
   switch(timer) {
@@ -183,15 +171,15 @@
     break;
   case TIMER_STARTOP:
     /* This is set at the start of a transfer */
-    data->progress.t_startop = now;
+    data->progress.t_startop = timestamp;
     break;
   case TIMER_STARTSINGLE:
     /* This is set at the start of each single fetch */
-    data->progress.t_startsingle = now;
+    data->progress.t_startsingle = timestamp;
     data->progress.is_t_startransfer_set = false;
     break;
   case TIMER_STARTACCEPT:
-    data->progress.t_acceptdata = now;
+    data->progress.t_acceptdata = timestamp;
     break;
   case TIMER_NAMELOOKUP:
     delta = &data->progress.t_nslookup;
@@ -214,7 +202,7 @@
      * changing the t_starttransfer time.
      */
     if(data->progress.is_t_startransfer_set) {
-      return now;
+      return;
     }
     else {
       data->progress.is_t_startransfer_set = true;
@@ -224,15 +212,30 @@
     /* this is the normal end-of-transfer thing */
     break;
   case TIMER_REDIRECT:
-    data->progress.t_redirect = Curl_timediff_us(now, data->progress.start);
+    data->progress.t_redirect = Curl_timediff_us(timestamp,
+                                                 data->progress.start);
     break;
   }
   if(delta) {
-    timediff_t us = Curl_timediff_us(now, data->progress.t_startsingle);
+    timediff_t us = Curl_timediff_us(timestamp, data->progress.t_startsingle);
     if(us < 1)
       us = 1; /* make sure at least one microsecond passed */
     *delta += us;
   }
+}
+
+/*
+ *
+ * Curl_pgrsTime(). Store the current time at the given label. This fetches a
+ * fresh "now" and returns it.
+ *
+ * @unittest: 1399
+ */
+struct curltime Curl_pgrsTime(struct Curl_easy *data, timerid timer)
+{
+  struct curltime now = Curl_now();
+
+  Curl_pgrsTimeWas(data, timer, now);
   return now;
 }
 
diff --git a/Utilities/cmcurl/lib/progress.h b/Utilities/cmcurl/lib/progress.h
index a129315..0049cd0 100644
--- a/Utilities/cmcurl/lib/progress.h
+++ b/Utilities/cmcurl/lib/progress.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -57,6 +57,13 @@
                                   curl_off_t limit,
                                   struct curltime start,
                                   struct curltime now);
+/**
+ * Update progress timer with the elapsed time from its start to `timestamp`.
+ * This allows updating timers later and is used by happy eyeballing, where
+ * we only want to record the winner's times.
+ */
+void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer,
+                      struct curltime timestamp);
 
 #define PGRS_HIDE    (1<<4)
 #define PGRS_UL_SIZE_KNOWN (1<<5)
diff --git a/Utilities/cmcurl/lib/psl.c b/Utilities/cmcurl/lib/psl.c
index 60c98a4..626a203 100644
--- a/Utilities/cmcurl/lib/psl.c
+++ b/Utilities/cmcurl/lib/psl.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/psl.h b/Utilities/cmcurl/lib/psl.h
index 34f0a5c..23cfa92 100644
--- a/Utilities/cmcurl/lib/psl.h
+++ b/Utilities/cmcurl/lib/psl.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/quic.h b/Utilities/cmcurl/lib/quic.h
deleted file mode 100644
index b357747..0000000
--- a/Utilities/cmcurl/lib/quic.h
+++ /dev/null
@@ -1,68 +0,0 @@
-#ifndef HEADER_CURL_QUIC_H
-#define HEADER_CURL_QUIC_H
-/***************************************************************************
- *                                  _   _ ____  _
- *  Project                     ___| | | |  _ \| |
- *                             / __| | | | |_) | |
- *                            | (__| |_| |  _ <| |___
- *                             \___|\___/|_| \_\_____|
- *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- *
- * This software is licensed as described in the file COPYING, which
- * you should have received as part of this distribution. The terms
- * are also available at https://curl.se/docs/copyright.html.
- *
- * You may opt to use, copy, modify, merge, publish, distribute and/or sell
- * copies of the Software, and permit persons to whom the Software is
- * furnished to do so, under the terms of the COPYING file.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- * SPDX-License-Identifier: curl
- *
- ***************************************************************************/
-
-#include "curl_setup.h"
-
-#ifdef ENABLE_QUIC
-#ifdef USE_NGTCP2
-#include "vquic/ngtcp2.h"
-#endif
-#ifdef USE_QUICHE
-#include "vquic/quiche.h"
-#endif
-#ifdef USE_MSH3
-#include "vquic/msh3.h"
-#endif
-
-#include "urldata.h"
-
-/* functions provided by the specific backends */
-CURLcode Curl_quic_connect(struct Curl_easy *data,
-                           struct connectdata *conn,
-                           curl_socket_t sockfd,
-                           int sockindex,
-                           const struct sockaddr *addr,
-                           socklen_t addrlen);
-CURLcode Curl_quic_is_connected(struct Curl_easy *data,
-                                struct connectdata *conn,
-                                int sockindex,
-                                bool *connected);
-void Curl_quic_ver(char *p, size_t len);
-CURLcode Curl_quic_done_sending(struct Curl_easy *data);
-void Curl_quic_done(struct Curl_easy *data, bool premature);
-bool Curl_quic_data_pending(const struct Curl_easy *data);
-void Curl_quic_disconnect(struct Curl_easy *data,
-                          struct connectdata *conn, int tempindex);
-CURLcode Curl_quic_idle(struct Curl_easy *data);
-
-#else /* ENABLE_QUIC */
-#define Curl_quic_done_sending(x)
-#define Curl_quic_done(x,y)
-#define Curl_quic_data_pending(x)
-#define Curl_quic_disconnect(x,y,z)
-#endif /* !ENABLE_QUIC */
-
-#endif /* HEADER_CURL_QUIC_H */
diff --git a/Utilities/cmcurl/lib/rand.c b/Utilities/cmcurl/lib/rand.c
index a549624..9abb722 100644
--- a/Utilities/cmcurl/lib/rand.c
+++ b/Utilities/cmcurl/lib/rand.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -30,6 +30,10 @@
 #ifdef HAVE_ARPA_INET_H
 #include <arpa/inet.h>
 #endif
+#ifdef HAVE_ARC4RANDOM
+/* Some platforms might have the prototype missing (ubuntu + libressl) */
+uint32_t arc4random(void);
+#endif
 
 #include <curl/curl.h>
 #include "vtls/vtls.h"
@@ -143,6 +147,11 @@
   }
 #endif
 
+#ifdef HAVE_ARC4RANDOM
+  *rnd = (unsigned int)arc4random();
+  return CURLE_OK;
+#endif
+
 #if defined(RANDOM_FILE) && !defined(WIN32)
   if(!seeded) {
     /* if there's a random file to read a seed from, use it */
diff --git a/Utilities/cmcurl/lib/rand.h b/Utilities/cmcurl/lib/rand.h
index 30fc296..cbe0567 100644
--- a/Utilities/cmcurl/lib/rand.h
+++ b/Utilities/cmcurl/lib/rand.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/rename.c b/Utilities/cmcurl/lib/rename.c
index cfb3699..97a66e9 100644
--- a/Utilities/cmcurl/lib/rename.c
+++ b/Utilities/cmcurl/lib/rename.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2020 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/rename.h b/Utilities/cmcurl/lib/rename.h
index 9958e2c..0444082 100644
--- a/Utilities/cmcurl/lib/rename.h
+++ b/Utilities/cmcurl/lib/rename.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2020 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/rtsp.c b/Utilities/cmcurl/lib/rtsp.c
index 75e620d..aef3560 100644
--- a/Utilities/cmcurl/lib/rtsp.c
+++ b/Utilities/cmcurl/lib/rtsp.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -38,6 +38,7 @@
 #include "strcase.h"
 #include "select.h"
 #include "connect.h"
+#include "cfilters.h"
 #include "strdup.h"
 /* The last 3 #include files should be in this order */
 #include "curl_printf.h"
@@ -134,36 +135,6 @@
 
 
 /*
- * The server may send us RTP data at any point, and RTSPREQ_RECEIVE does not
- * want to block the application forever while receiving a stream. Therefore,
- * we cannot assume that an RTSP socket is dead just because it is readable.
- *
- * Instead, if it is readable, run Curl_connalive() to peek at the socket
- * and distinguish between closed and data.
- */
-static bool rtsp_connisdead(struct Curl_easy *data, struct connectdata *check)
-{
-  int sval;
-  bool ret_val = TRUE;
-
-  sval = SOCKET_READABLE(check->sock[FIRSTSOCKET], 0);
-  if(sval == 0) {
-    /* timeout */
-    ret_val = FALSE;
-  }
-  else if(sval & CURL_CSELECT_ERR) {
-    /* socket is in an error state */
-    ret_val = TRUE;
-  }
-  else if(sval & CURL_CSELECT_IN) {
-    /* readable with no error. could still be closed */
-    ret_val = !Curl_connalive(data, check);
-  }
-
-  return ret_val;
-}
-
-/*
  * Function to check on various aspects of a connection.
  */
 static unsigned int rtsp_conncheck(struct Curl_easy *data,
@@ -174,7 +145,8 @@
   (void)data;
 
   if(checks_to_perform & CONNCHECK_ISDEAD) {
-    if(rtsp_connisdead(data, conn))
+    bool input_pending;
+    if(!Curl_conn_is_alive(data, conn, &input_pending))
       ret_val |= CONNRESULT_DEAD;
   }
 
@@ -592,7 +564,7 @@
   }
 
   /* issue the request */
-  result = Curl_buffer_send(&req_buffer, data,
+  result = Curl_buffer_send(&req_buffer, data, data->req.p.http,
                             &data->info.request_size, 0, FIRSTSOCKET);
   if(result) {
     failf(data, "Failed sending RTSP request");
@@ -784,12 +756,14 @@
 
 CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header)
 {
-  long CSeq = 0;
-
   if(checkprefix("CSeq:", header)) {
-    /* Store the received CSeq. Match is verified in rtsp_done */
-    int nc = sscanf(&header[4], ": %ld", &CSeq);
-    if(nc == 1) {
+    long CSeq = 0;
+    char *endp;
+    char *p = &header[5];
+    while(ISBLANK(*p))
+      p++;
+    CSeq = strtol(p, &endp, 10);
+    if(p != endp) {
       struct RTSP *rtsp = data->req.p.rtsp;
       rtsp->CSeq_recv = CSeq; /* mark the request */
       data->state.rtsp_CSeq_recv = CSeq; /* update the handle */
diff --git a/Utilities/cmcurl/lib/rtsp.h b/Utilities/cmcurl/lib/rtsp.h
index fa6606a..6e55616 100644
--- a/Utilities/cmcurl/lib/rtsp.h
+++ b/Utilities/cmcurl/lib/rtsp.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/select.c b/Utilities/cmcurl/lib/select.c
index 2ac0746..61cce61 100644
--- a/Utilities/cmcurl/lib/select.c
+++ b/Utilities/cmcurl/lib/select.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -230,14 +230,14 @@
   if(readfd0 != CURL_SOCKET_BAD) {
     if(pfd[num].revents & (POLLRDNORM|POLLIN|POLLERR|POLLHUP))
       r |= CURL_CSELECT_IN;
-    if(pfd[num].revents & (POLLRDBAND|POLLPRI|POLLNVAL))
+    if(pfd[num].revents & (POLLPRI|POLLNVAL))
       r |= CURL_CSELECT_ERR;
     num++;
   }
   if(readfd1 != CURL_SOCKET_BAD) {
     if(pfd[num].revents & (POLLRDNORM|POLLIN|POLLERR|POLLHUP))
       r |= CURL_CSELECT_IN2;
-    if(pfd[num].revents & (POLLRDBAND|POLLPRI|POLLNVAL))
+    if(pfd[num].revents & (POLLPRI|POLLNVAL))
       r |= CURL_CSELECT_ERR;
     num++;
   }
diff --git a/Utilities/cmcurl/lib/select.h b/Utilities/cmcurl/lib/select.h
index f2cf8bb..5b1ca23 100644
--- a/Utilities/cmcurl/lib/select.h
+++ b/Utilities/cmcurl/lib/select.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/sendf.c b/Utilities/cmcurl/lib/sendf.c
index 6326240..2b08271 100644
--- a/Utilities/cmcurl/lib/sendf.c
+++ b/Utilities/cmcurl/lib/sendf.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -138,149 +138,6 @@
 }
 #endif /* CURL_DO_LINEEND_CONV && !CURL_DISABLE_FTP */
 
-#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
-bool Curl_recv_has_postponed_data(struct connectdata *conn, int sockindex)
-{
-  struct postponed_data * const psnd = &(conn->postponed[sockindex]);
-  return psnd->buffer && psnd->allocated_size &&
-         psnd->recv_size > psnd->recv_processed;
-}
-
-static CURLcode pre_receive_plain(struct Curl_easy *data,
-                                  struct connectdata *conn, int num)
-{
-  const curl_socket_t sockfd = conn->sock[num];
-  struct postponed_data * const psnd = &(conn->postponed[num]);
-  size_t bytestorecv = psnd->allocated_size - psnd->recv_size;
-  ssize_t recvedbytes;
-
-  /* WinSock will destroy unread received data if send() is
-     failed.
-     To avoid lossage of received data, recv() must be
-     performed before every send() if any incoming data is
-     available. However, skip this, if buffer is already full. */
-  if((conn->handler->protocol&PROTO_FAMILY_HTTP) != 0 &&
-     conn->recv[num] == Curl_conn_recv &&
-     (!psnd->buffer || bytestorecv)) {
-    const int readymask = Curl_socket_check(sockfd, CURL_SOCKET_BAD,
-                                            CURL_SOCKET_BAD, 0);
-    if(readymask != -1 && (readymask & CURL_CSELECT_IN) != 0) {
-      /* Have some incoming data */
-      if(!psnd->buffer) {
-        /* Use buffer double default size for intermediate buffer */
-        psnd->allocated_size = 2 * data->set.buffer_size;
-        psnd->buffer = malloc(psnd->allocated_size);
-        if(!psnd->buffer)
-          return CURLE_OUT_OF_MEMORY;
-        psnd->recv_size = 0;
-        psnd->recv_processed = 0;
-#ifdef DEBUGBUILD
-        psnd->bindsock = sockfd; /* Used only for DEBUGASSERT */
-#endif /* DEBUGBUILD */
-        bytestorecv = psnd->allocated_size;
-      }
-
-      DEBUGASSERT(psnd->bindsock == sockfd);
-      recvedbytes = sread(sockfd, psnd->buffer + psnd->recv_size,
-                          bytestorecv);
-      if(recvedbytes > 0)
-        psnd->recv_size += recvedbytes;
-    }
-  }
-  return CURLE_OK;
-}
-
-static ssize_t get_pre_recved(struct connectdata *conn, int num, char *buf,
-                              size_t len)
-{
-  struct postponed_data * const psnd = &(conn->postponed[num]);
-  size_t copysize;
-  if(!psnd->buffer)
-    return 0;
-
-  DEBUGASSERT(psnd->allocated_size > 0);
-  DEBUGASSERT(psnd->recv_size <= psnd->allocated_size);
-  DEBUGASSERT(psnd->recv_processed <= psnd->recv_size);
-  /* Check and process data that already received and storied in internal
-     intermediate buffer */
-  if(psnd->recv_size > psnd->recv_processed) {
-    DEBUGASSERT(psnd->bindsock == conn->sock[num]);
-    copysize = CURLMIN(len, psnd->recv_size - psnd->recv_processed);
-    memcpy(buf, psnd->buffer + psnd->recv_processed, copysize);
-    psnd->recv_processed += copysize;
-  }
-  else
-    copysize = 0; /* buffer was allocated, but nothing was received */
-
-  /* Free intermediate buffer if it has no unprocessed data */
-  if(psnd->recv_processed == psnd->recv_size) {
-    free(psnd->buffer);
-    psnd->buffer = NULL;
-    psnd->allocated_size = 0;
-    psnd->recv_size = 0;
-    psnd->recv_processed = 0;
-#ifdef DEBUGBUILD
-    psnd->bindsock = CURL_SOCKET_BAD;
-#endif /* DEBUGBUILD */
-  }
-  return (ssize_t)copysize;
-}
-#else  /* ! USE_RECV_BEFORE_SEND_WORKAROUND */
-/* Use "do-nothing" macros instead of functions when workaround not used */
-bool Curl_recv_has_postponed_data(struct connectdata *conn, int sockindex)
-{
-  (void)conn;
-  (void)sockindex;
-  return false;
-}
-#define pre_receive_plain(d,c,n) CURLE_OK
-#define get_pre_recved(c,n,b,l) 0
-#endif /* ! USE_RECV_BEFORE_SEND_WORKAROUND */
-
-/* Curl_infof() is for info message along the way */
-#define MAXINFO 2048
-
-void Curl_infof(struct Curl_easy *data, const char *fmt, ...)
-{
-  DEBUGASSERT(!strchr(fmt, '\n'));
-  if(data && data->set.verbose) {
-    va_list ap;
-    int len;
-    char buffer[MAXINFO + 2];
-    va_start(ap, fmt);
-    len = mvsnprintf(buffer, MAXINFO, fmt, ap);
-    va_end(ap);
-    buffer[len++] = '\n';
-    buffer[len] = '\0';
-    Curl_debug(data, CURLINFO_TEXT, buffer, len);
-  }
-}
-
-/* Curl_failf() is for messages stating why we failed.
- * The message SHALL NOT include any LF or CR.
- */
-
-void Curl_failf(struct Curl_easy *data, const char *fmt, ...)
-{
-  DEBUGASSERT(!strchr(fmt, '\n'));
-  if(data->set.verbose || data->set.errorbuffer) {
-    va_list ap;
-    int len;
-    char error[CURL_ERROR_SIZE + 2];
-    va_start(ap, fmt);
-    len = mvsnprintf(error, CURL_ERROR_SIZE, fmt, ap);
-
-    if(data->set.errorbuffer && !data->state.errorbuf) {
-      strcpy(data->set.errorbuffer, error);
-      data->state.errorbuf = TRUE; /* wrote error string */
-    }
-    error[len++] = '\n';
-    error[len] = '\0';
-    Curl_debug(data, CURLINFO_TEXT, error, len);
-    va_end(ap);
-  }
-}
-
 /*
  * Curl_write() is an internal write function that sends data to the
  * server. Works with plain sockets, SCP, SSL or kerberos.
@@ -301,7 +158,7 @@
   DEBUGASSERT(data);
   DEBUGASSERT(data->conn);
   conn = data->conn;
-  num = (sockfd == conn->sock[SECONDARYSOCKET]);
+  num = (sockfd != CURL_SOCKET_BAD && sockfd == conn->sock[SECONDARYSOCKET]);
 
 #ifdef CURLDEBUG
   {
@@ -338,153 +195,6 @@
   }
 }
 
-/* Curl_send_plain sends raw data without a size restriction on 'len'. */
-ssize_t Curl_send_plain(struct Curl_easy *data, int num,
-                        const void *mem, size_t len, CURLcode *code)
-{
-  struct connectdata *conn;
-  curl_socket_t sockfd;
-  ssize_t bytes_written;
-
-  DEBUGASSERT(data);
-  DEBUGASSERT(data->conn);
-  conn = data->conn;
-  sockfd = conn->sock[num];
-  /* WinSock will destroy unread received data if send() is
-     failed.
-     To avoid lossage of received data, recv() must be
-     performed before every send() if any incoming data is
-     available. */
-  if(pre_receive_plain(data, conn, num)) {
-    *code = CURLE_OUT_OF_MEMORY;
-    return -1;
-  }
-
-#if defined(MSG_FASTOPEN) && !defined(TCP_FASTOPEN_CONNECT) /* Linux */
-  if(conn->bits.tcp_fastopen) {
-    bytes_written = sendto(sockfd, mem, len, MSG_FASTOPEN,
-                           conn->ip_addr->ai_addr, conn->ip_addr->ai_addrlen);
-    conn->bits.tcp_fastopen = FALSE;
-  }
-  else
-#endif
-    bytes_written = swrite(sockfd, mem, len);
-
-  *code = CURLE_OK;
-  if(-1 == bytes_written) {
-    int err = SOCKERRNO;
-
-    if(
-#ifdef WSAEWOULDBLOCK
-      /* This is how Windows does it */
-      (WSAEWOULDBLOCK == err)
-#else
-      /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned
-         due to its inability to send off data without blocking. We therefore
-         treat both error codes the same here */
-      (EWOULDBLOCK == err) || (EAGAIN == err) || (EINTR == err) ||
-      (EINPROGRESS == err)
-#endif
-      ) {
-      /* this is just a case of EWOULDBLOCK */
-      *code = CURLE_AGAIN;
-    }
-    else {
-      char buffer[STRERROR_LEN];
-      failf(data, "Send failure: %s",
-            Curl_strerror(err, buffer, sizeof(buffer)));
-      data->state.os_errno = err;
-      *code = CURLE_SEND_ERROR;
-    }
-  }
-  return bytes_written;
-}
-
-/*
- * Curl_write_plain() is an internal write function that sends data to the
- * server using plain sockets only. Otherwise meant to have the exact same
- * proto as Curl_write().
- *
- * This function wraps Curl_send_plain(). The only difference besides the
- * prototype is '*written' (bytes written) is set to 0 on error.
- * 'sockfd' must be one of the connection's two main sockets and the value of
- * 'len' must not be changed.
- */
-CURLcode Curl_write_plain(struct Curl_easy *data,
-                          curl_socket_t sockfd,
-                          const void *mem,
-                          size_t len,
-                          ssize_t *written)
-{
-  CURLcode result;
-  struct connectdata *conn = data->conn;
-  int num;
-  DEBUGASSERT(conn);
-  DEBUGASSERT(sockfd == conn->sock[FIRSTSOCKET] ||
-              sockfd == conn->sock[SECONDARYSOCKET]);
-  if(sockfd != conn->sock[FIRSTSOCKET] &&
-     sockfd != conn->sock[SECONDARYSOCKET])
-    return CURLE_BAD_FUNCTION_ARGUMENT;
-
-  num = (sockfd == conn->sock[SECONDARYSOCKET]);
-
-  *written = Curl_send_plain(data, num, mem, len, &result);
-  if(*written == -1)
-    *written = 0;
-
-  return result;
-}
-
-/* Curl_recv_plain receives raw data without a size restriction on 'len'. */
-ssize_t Curl_recv_plain(struct Curl_easy *data, int num, char *buf,
-                        size_t len, CURLcode *code)
-{
-  struct connectdata *conn;
-  curl_socket_t sockfd;
-  ssize_t nread;
-  DEBUGASSERT(data);
-  DEBUGASSERT(data->conn);
-  conn = data->conn;
-  sockfd = conn->sock[num];
-  /* Check and return data that already received and storied in internal
-     intermediate buffer */
-  nread = get_pre_recved(conn, num, buf, len);
-  if(nread > 0) {
-    *code = CURLE_OK;
-    return nread;
-  }
-
-  nread = sread(sockfd, buf, len);
-
-  *code = CURLE_OK;
-  if(-1 == nread) {
-    int err = SOCKERRNO;
-
-    if(
-#ifdef WSAEWOULDBLOCK
-      /* This is how Windows does it */
-      (WSAEWOULDBLOCK == err)
-#else
-      /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned
-         due to its inability to send off data without blocking. We therefore
-         treat both error codes the same here */
-      (EWOULDBLOCK == err) || (EAGAIN == err) || (EINTR == err)
-#endif
-      ) {
-      /* this is just a case of EWOULDBLOCK */
-      *code = CURLE_AGAIN;
-    }
-    else {
-      char buffer[STRERROR_LEN];
-      failf(data, "Recv failure: %s",
-            Curl_strerror(err, buffer, sizeof(buffer)));
-      data->state.os_errno = err;
-      *code = CURLE_RECV_ERROR;
-    }
-  }
-  return nread;
-}
-
 static CURLcode pausewrite(struct Curl_easy *data,
                            int type, /* what type of data */
                            const char *ptr,
@@ -498,8 +208,7 @@
   unsigned int i;
   bool newtype = TRUE;
 
-  /* If this transfers over HTTP/2, pause the stream! */
-  Curl_http2_stream_pause(data, TRUE);
+  Curl_conn_ev_data_pause(data, TRUE);
 
   if(s->tempcount) {
     for(i = 0; i< s->tempcount; i++) {
@@ -678,41 +387,6 @@
 }
 
 /*
- * Curl_read_plain() is an internal read function that reads data from the
- * server using plain sockets only. Otherwise meant to have the exact same
- * proto as Curl_read().
- *
- * This function wraps Curl_recv_plain(). The only difference besides the
- * prototype is '*n' (bytes read) is set to 0 on error.
- * 'sockfd' must be one of the connection's two main sockets and the value of
- * 'sizerequested' must not be changed.
- */
-CURLcode Curl_read_plain(struct Curl_easy *data,   /* transfer */
-                         curl_socket_t sockfd,     /* read from this socket */
-                         char *buf,                /* store read data here */
-                         size_t sizerequested,     /* max amount to read */
-                         ssize_t *n)               /* amount bytes read */
-{
-  CURLcode result;
-  struct connectdata *conn = data->conn;
-  int num;
-  DEBUGASSERT(conn);
-  DEBUGASSERT(sockfd == conn->sock[FIRSTSOCKET] ||
-              sockfd == conn->sock[SECONDARYSOCKET]);
-  if(sockfd != conn->sock[FIRSTSOCKET] &&
-     sockfd != conn->sock[SECONDARYSOCKET])
-    return CURLE_BAD_FUNCTION_ARGUMENT;
-
-  num = (sockfd == conn->sock[SECONDARYSOCKET]);
-
-  *n = Curl_recv_plain(data, num, buf, sizerequested, &result);
-  if(*n == -1)
-    *n = 0;
-
-  return result;
-}
-
-/*
  * Internal read-from-socket function. This is meant to deal with plain
  * sockets, SSL sockets and kerberos sockets.
  *
@@ -752,30 +426,3 @@
   return result;
 }
 
-/* return 0 on success */
-void Curl_debug(struct Curl_easy *data, curl_infotype type,
-                char *ptr, size_t size)
-{
-  if(data->set.verbose) {
-    static const char s_infotype[CURLINFO_END][3] = {
-      "* ", "< ", "> ", "{ ", "} ", "{ ", "} " };
-    if(data->set.fdebug) {
-      bool inCallback = Curl_is_in_callback(data);
-      Curl_set_in_callback(data, true);
-      (void)(*data->set.fdebug)(data, type, ptr, size, data->set.debugdata);
-      Curl_set_in_callback(data, inCallback);
-    }
-    else {
-      switch(type) {
-      case CURLINFO_TEXT:
-      case CURLINFO_HEADER_OUT:
-      case CURLINFO_HEADER_IN:
-        fwrite(s_infotype[type], 2, 1, data->set.err);
-        fwrite(ptr, size, 1, data->set.err);
-        break;
-      default: /* nada */
-        break;
-      }
-    }
-  }
-}
diff --git a/Utilities/cmcurl/lib/sendf.h b/Utilities/cmcurl/lib/sendf.h
index 8af5c46..d0c9275 100644
--- a/Utilities/cmcurl/lib/sendf.h
+++ b/Utilities/cmcurl/lib/sendf.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -26,26 +26,8 @@
 
 #include "curl_setup.h"
 
-void Curl_infof(struct Curl_easy *, const char *fmt, ...);
-void Curl_failf(struct Curl_easy *, const char *fmt, ...);
+#include "curl_log.h"
 
-#if defined(CURL_DISABLE_VERBOSE_STRINGS)
-
-#if defined(HAVE_VARIADIC_MACROS_C99)
-#define infof(...)  Curl_nop_stmt
-#elif defined(HAVE_VARIADIC_MACROS_GCC)
-#define infof(x...)  Curl_nop_stmt
-#else
-#error "missing VARIADIC macro define, fix and rebuild!"
-#endif
-
-#else /* CURL_DISABLE_VERBOSE_STRINGS */
-
-#define infof Curl_infof
-
-#endif /* CURL_DISABLE_VERBOSE_STRINGS */
-
-#define failf Curl_failf
 
 #define CLIENTWRITE_BODY    (1<<0)
 #define CLIENTWRITE_HEADER  (1<<1)
@@ -58,20 +40,6 @@
 CURLcode Curl_client_write(struct Curl_easy *data, int type, char *ptr,
                            size_t len) WARN_UNUSED_RESULT;
 
-bool Curl_recv_has_postponed_data(struct connectdata *conn, int sockindex);
-
-/* internal read-function, does plain socket only */
-CURLcode Curl_read_plain(struct Curl_easy *data,
-                         curl_socket_t sockfd,
-                         char *buf,
-                         size_t sizerequested,
-                         ssize_t *n);
-
-ssize_t Curl_recv_plain(struct Curl_easy *data, int num, char *buf,
-                        size_t len, CURLcode *code);
-ssize_t Curl_send_plain(struct Curl_easy *data, int num,
-                        const void *mem, size_t len, CURLcode *code);
-
 /* internal read-function, does plain socket, SSL and krb4 */
 CURLcode Curl_read(struct Curl_easy *data, curl_socket_t sockfd,
                    char *buf, size_t buffersize,
@@ -83,15 +51,4 @@
                     const void *mem, size_t len,
                     ssize_t *written);
 
-/* internal write-function, does plain sockets ONLY */
-CURLcode Curl_write_plain(struct Curl_easy *data,
-                          curl_socket_t sockfd,
-                          const void *mem, size_t len,
-                          ssize_t *written);
-
-/* the function used to output verbose information */
-void Curl_debug(struct Curl_easy *data, curl_infotype type,
-                char *ptr, size_t size);
-
-
 #endif /* HEADER_CURL_SENDF_H */
diff --git a/Utilities/cmcurl/lib/setopt.c b/Utilities/cmcurl/lib/setopt.c
index b77e95b..6bb8879 100644
--- a/Utilities/cmcurl/lib/setopt.c
+++ b/Utilities/cmcurl/lib/setopt.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -174,7 +174,7 @@
 
       *val |= h->protocol;
     }
-  } while(str++);
+  } while(str && str++);
 
   if(!*val)
     /* no protocol listed */
@@ -463,8 +463,8 @@
          version_max >= CURL_SSLVERSION_MAX_LAST)
         return CURLE_BAD_FUNCTION_ARGUMENT;
 
-      primary->version = version;
-      primary->version_max = version_max;
+      primary->version = (unsigned char)version;
+      primary->version_max = (unsigned int)version_max;
     }
 #else
     result = CURLE_NOT_BUILT_IN;
@@ -732,13 +732,6 @@
     data->set.sep_headers = (bool)((arg & CURLHEADER_SEPARATE)? TRUE: FALSE);
     break;
 
-  case CURLOPT_HTTP200ALIASES:
-    /*
-     * Set a list of aliases for HTTP 200 in response header
-     */
-    data->set.http200aliases = va_arg(param, struct curl_slist *);
-    break;
-
 #if !defined(CURL_DISABLE_COOKIES)
   case CURLOPT_COOKIE:
     /*
@@ -760,18 +753,18 @@
         return CURLE_BAD_FUNCTION_ARGUMENT;
       /* append the cookie file name to the list of file names, and deal with
          them later */
-      cl = curl_slist_append(data->state.cookielist, argptr);
+      cl = curl_slist_append(data->set.cookielist, argptr);
       if(!cl) {
-        curl_slist_free_all(data->state.cookielist);
-        data->state.cookielist = NULL;
+        curl_slist_free_all(data->set.cookielist);
+        data->set.cookielist = NULL;
         return CURLE_OUT_OF_MEMORY;
       }
-      data->state.cookielist = cl; /* store the list for later use */
+      data->set.cookielist = cl; /* store the list for later use */
     }
     else {
       /* clear the list of cookie files */
-      curl_slist_free_all(data->state.cookielist);
-      data->state.cookielist = NULL;
+      curl_slist_free_all(data->set.cookielist);
+      data->set.cookielist = NULL;
 
       if(!data->share || !data->share->cookies) {
         /* throw away all existing cookies if this isn't a shared cookie
@@ -902,22 +895,38 @@
      * the listed enums in curl/curl.h.
      */
     arg = va_arg(param, long);
-    if(arg < CURL_HTTP_VERSION_NONE)
-      return CURLE_BAD_FUNCTION_ARGUMENT;
-#ifdef ENABLE_QUIC
-    if(arg == CURL_HTTP_VERSION_3)
-      ;
-    else
-#endif
-#ifndef USE_HTTP2
-    if(arg >= CURL_HTTP_VERSION_2)
-      return CURLE_UNSUPPORTED_PROTOCOL;
-#else
-    if(arg >= CURL_HTTP_VERSION_LAST)
-      return CURLE_UNSUPPORTED_PROTOCOL;
-    if(arg == CURL_HTTP_VERSION_NONE)
+    switch(arg) {
+    case CURL_HTTP_VERSION_NONE:
+#ifdef USE_HTTP2
+      /* TODO: this seems an undesirable quirk to force a behaviour on
+       * lower implementations that they should recognize independently? */
       arg = CURL_HTTP_VERSION_2TLS;
 #endif
+      /* accepted */
+      break;
+    case CURL_HTTP_VERSION_1_0:
+    case CURL_HTTP_VERSION_1_1:
+      /* accepted */
+      break;
+#ifdef USE_HTTP2
+    case CURL_HTTP_VERSION_2_0:
+    case CURL_HTTP_VERSION_2TLS:
+    case CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE:
+      /* accepted */
+      break;
+#endif
+#ifdef ENABLE_QUIC
+    case CURL_HTTP_VERSION_3:
+    case CURL_HTTP_VERSION_3ONLY:
+      /* accepted */
+      break;
+#endif
+    default:
+      /* not accepted */
+      if(arg < CURL_HTTP_VERSION_NONE)
+        return CURLE_BAD_FUNCTION_ARGUMENT;
+      return CURLE_UNSUPPORTED_PROTOCOL;
+    }
     data->set.httpwant = (unsigned char)arg;
     break;
 
@@ -944,6 +953,13 @@
     data->set.http09_allowed = arg ? TRUE : FALSE;
 #endif
     break;
+
+  case CURLOPT_HTTP200ALIASES:
+    /*
+     * Set a list of aliases for HTTP 200 in response header
+     */
+    data->set.http200aliases = va_arg(param, struct curl_slist *);
+    break;
 #endif   /* CURL_DISABLE_HTTP */
 
 #if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_SMTP) ||       \
@@ -1309,6 +1325,7 @@
     data->set.krb = (data->set.str[STRING_KRB_LEVEL]) ? TRUE : FALSE;
     break;
 #endif
+#if !defined(CURL_DISABLE_FTP) || defined(USE_SSH)
   case CURLOPT_FTP_CREATE_MISSING_DIRS:
     /*
      * An FTP/SFTP option that modifies an upload to create missing
@@ -1322,6 +1339,26 @@
     else
       data->set.ftp_create_missing_dirs = (unsigned char)arg;
     break;
+
+  case CURLOPT_POSTQUOTE:
+    /*
+     * List of RAW FTP commands to use after a transfer
+     */
+    data->set.postquote = va_arg(param, struct curl_slist *);
+    break;
+  case CURLOPT_PREQUOTE:
+    /*
+     * List of RAW FTP commands to use prior to RETR (Wesley Laxton)
+     */
+    data->set.prequote = va_arg(param, struct curl_slist *);
+    break;
+  case CURLOPT_QUOTE:
+    /*
+     * List of RAW FTP commands to use before a transfer
+     */
+    data->set.quote = va_arg(param, struct curl_slist *);
+    break;
+#endif
   case CURLOPT_READDATA:
     /*
      * FILE pointer to read the file to be uploaded from. Or possibly
@@ -1431,7 +1468,7 @@
 
   case CURLOPT_TIMEOUT_MS:
     uarg = va_arg(param, unsigned long);
-    if(uarg >= UINT_MAX)
+    if(uarg > UINT_MAX)
       uarg = UINT_MAX;
     data->set.timeout = (unsigned int)uarg;
     break;
@@ -1449,7 +1486,7 @@
 
   case CURLOPT_CONNECTTIMEOUT_MS:
     uarg = va_arg(param, unsigned long);
-    if(uarg >= UINT_MAX)
+    if(uarg > UINT_MAX)
       uarg = UINT_MAX;
     data->set.connecttimeout = (unsigned int)uarg;
     break;
@@ -1460,7 +1497,7 @@
      * The maximum time for curl to wait for FTP server connect
      */
     uarg = va_arg(param, unsigned long);
-    if(uarg >= UINT_MAX)
+    if(uarg > UINT_MAX)
       uarg = UINT_MAX;
     data->set.accepttimeout = (unsigned int)uarg;
     break;
@@ -1506,24 +1543,6 @@
                             va_arg(param, char *));
     break;
 
-  case CURLOPT_POSTQUOTE:
-    /*
-     * List of RAW FTP commands to use after a transfer
-     */
-    data->set.postquote = va_arg(param, struct curl_slist *);
-    break;
-  case CURLOPT_PREQUOTE:
-    /*
-     * List of RAW FTP commands to use prior to RETR (Wesley Laxton)
-     */
-    data->set.prequote = va_arg(param, struct curl_slist *);
-    break;
-  case CURLOPT_QUOTE:
-    /*
-     * List of RAW FTP commands to use before a transfer
-     */
-    data->set.quote = va_arg(param, struct curl_slist *);
-    break;
   case CURLOPT_RESOLVE:
     /*
      * List of HOST:PORT:[addresses] strings to populate the DNS cache with
@@ -1871,16 +1890,15 @@
     arg = va_arg(param, long);
     if((arg < 0) || (arg > 65535))
       return CURLE_BAD_FUNCTION_ARGUMENT;
-    data->set.localportrange = curlx_sltosi(arg);
+    data->set.localportrange = curlx_sltous(arg);
     break;
   case CURLOPT_GSSAPI_DELEGATION:
     /*
      * GSS-API credential delegation bitmask
      */
-    arg = va_arg(param, long);
-    if(arg < CURLGSSAPI_DELEGATION_NONE)
-      return CURLE_BAD_FUNCTION_ARGUMENT;
-    data->set.gssapi_delegation = arg;
+    uarg = va_arg(param, unsigned long);
+    data->set.gssapi_delegation = (unsigned char)uarg&
+      (CURLGSSAPI_DELEGATION_POLICY_FLAG|CURLGSSAPI_DELEGATION_FLAG);
     break;
   case CURLOPT_SSL_VERIFYPEER:
     /*
@@ -2260,9 +2278,14 @@
         data->cookies = NULL;
 #endif
 
+#ifndef CURL_DISABLE_HSTS
+      if(data->share->hsts == data->hsts)
+        data->hsts = NULL;
+#endif
+#ifdef USE_SSL
       if(data->share->sslsession == data->state.session)
         data->state.session = NULL;
-
+#endif
 #ifdef USE_LIBPSL
       if(data->psl == &data->share->psl)
         data->psl = data->multi? &data->multi->psl: NULL;
@@ -2296,10 +2319,19 @@
         data->cookies = data->share->cookies;
       }
 #endif   /* CURL_DISABLE_HTTP */
+#ifndef CURL_DISABLE_HSTS
+      if(data->share->hsts) {
+        /* first free the private one if any */
+        Curl_hsts_cleanup(&data->hsts);
+        data->hsts = data->share->hsts;
+      }
+#endif   /* CURL_DISABLE_HTTP */
+#ifdef USE_SSL
       if(data->share->sslsession) {
         data->set.general_ssl.max_ssl_sessions = data->share->max_ssl_sessions;
         data->state.session = data->share->sslsession;
       }
+#endif
 #ifdef USE_LIBPSL
       if(data->share->specifier & (1 << CURL_LOCK_DATA_PSL))
         data->psl = &data->share->psl;
@@ -2337,7 +2369,7 @@
     arg = va_arg(param, long);
     if((arg < CURLUSESSL_NONE) || (arg >= CURLUSESSL_LAST))
       return CURLE_BAD_FUNCTION_ARGUMENT;
-    data->set.use_ssl = (curl_usessl)arg;
+    data->set.use_ssl = (unsigned char)arg;
     break;
 
   case CURLOPT_SSL_OPTIONS:
@@ -2515,6 +2547,14 @@
                             va_arg(param, char *));
     break;
 
+  case CURLOPT_SSH_KNOWNHOSTS:
+    /*
+     * Store the file name to read known hosts from.
+     */
+    result = Curl_setstropt(&data->set.str[STRING_SSH_KNOWNHOSTS],
+                            va_arg(param, char *));
+    break;
+#ifdef USE_LIBSSH2
   case CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256:
     /*
      * Option to allow for the SHA256 of the host public key to be checked
@@ -2524,14 +2564,6 @@
                             va_arg(param, char *));
     break;
 
-  case CURLOPT_SSH_KNOWNHOSTS:
-    /*
-     * Store the file name to read known hosts from.
-     */
-    result = Curl_setstropt(&data->set.str[STRING_SSH_KNOWNHOSTS],
-                            va_arg(param, char *));
-    break;
-#ifdef USE_LIBSSH2
   case CURLOPT_SSH_HOSTKEYFUNCTION:
     /* the callback to check the hostkey without the knownhost file */
     data->set.ssh_hostkeyfunc = va_arg(param, curl_sshhostkeycallback);
@@ -2544,6 +2576,7 @@
     data->set.ssh_hostkeyfunc_userp = va_arg(param, void *);
     break;
 #endif
+
   case CURLOPT_SSH_KEYFUNCTION:
     /* setting to NULL is fine since the ssh.c functions themselves will
        then revert to use the internal default */
@@ -2590,7 +2623,8 @@
       return CURLE_BAD_FUNCTION_ARGUMENT;
     data->set.new_file_perms = (unsigned int)arg;
     break;
-
+#endif
+#ifdef USE_SSH
   case CURLOPT_NEW_DIRECTORY_PERMS:
     /*
      * Uses these permissions instead of 0755
@@ -2815,7 +2849,7 @@
     data->set.fnmatch = va_arg(param, curl_fnmatch_callback);
     break;
   case CURLOPT_CHUNK_DATA:
-    data->wildcard.customptr = va_arg(param, void *);
+    data->set.wildcardptr = va_arg(param, void *);
     break;
   case CURLOPT_FNMATCH_DATA:
     data->set.fnmatch_data = va_arg(param, void *);
@@ -2825,52 +2859,33 @@
   case CURLOPT_TLSAUTH_USERNAME:
     result = Curl_setstropt(&data->set.str[STRING_TLSAUTH_USERNAME],
                             va_arg(param, char *));
-    if(data->set.str[STRING_TLSAUTH_USERNAME] &&
-       !data->set.ssl.primary.authtype)
-      data->set.ssl.primary.authtype = CURL_TLSAUTH_SRP; /* default to SRP */
     break;
 #ifndef CURL_DISABLE_PROXY
   case CURLOPT_PROXY_TLSAUTH_USERNAME:
     result = Curl_setstropt(&data->set.str[STRING_TLSAUTH_USERNAME_PROXY],
                             va_arg(param, char *));
-    if(data->set.str[STRING_TLSAUTH_USERNAME_PROXY] &&
-       !data->set.proxy_ssl.primary.authtype)
-      data->set.proxy_ssl.primary.authtype = CURL_TLSAUTH_SRP; /* default to
-                                                                  SRP */
     break;
 #endif
   case CURLOPT_TLSAUTH_PASSWORD:
     result = Curl_setstropt(&data->set.str[STRING_TLSAUTH_PASSWORD],
                             va_arg(param, char *));
-    if(data->set.str[STRING_TLSAUTH_USERNAME] &&
-       !data->set.ssl.primary.authtype)
-      data->set.ssl.primary.authtype = CURL_TLSAUTH_SRP; /* default */
     break;
 #ifndef CURL_DISABLE_PROXY
   case CURLOPT_PROXY_TLSAUTH_PASSWORD:
     result = Curl_setstropt(&data->set.str[STRING_TLSAUTH_PASSWORD_PROXY],
                             va_arg(param, char *));
-    if(data->set.str[STRING_TLSAUTH_USERNAME_PROXY] &&
-       !data->set.proxy_ssl.primary.authtype)
-      data->set.proxy_ssl.primary.authtype = CURL_TLSAUTH_SRP; /* default */
     break;
 #endif
   case CURLOPT_TLSAUTH_TYPE:
     argptr = va_arg(param, char *);
-    if(!argptr ||
-       strncasecompare(argptr, "SRP", strlen("SRP")))
-      data->set.ssl.primary.authtype = CURL_TLSAUTH_SRP;
-    else
-      data->set.ssl.primary.authtype = CURL_TLSAUTH_NONE;
+    if(argptr && !strncasecompare(argptr, "SRP", strlen("SRP")))
+      return CURLE_BAD_FUNCTION_ARGUMENT;
     break;
 #ifndef CURL_DISABLE_PROXY
   case CURLOPT_PROXY_TLSAUTH_TYPE:
     argptr = va_arg(param, char *);
-    if(!argptr ||
-       strncasecompare(argptr, "SRP", strlen("SRP")))
-      data->set.proxy_ssl.primary.authtype = CURL_TLSAUTH_SRP;
-    else
-      data->set.proxy_ssl.primary.authtype = CURL_TLSAUTH_NONE;
+    if(argptr || !strncasecompare(argptr, "SRP", strlen("SRP")))
+      return CURLE_BAD_FUNCTION_ARGUMENT;
     break;
 #endif
 #endif
@@ -2956,29 +2971,23 @@
     data->set.pipewait = (0 != va_arg(param, long)) ? TRUE : FALSE;
     break;
   case CURLOPT_STREAM_WEIGHT:
-#ifndef USE_NGHTTP2
-    return CURLE_NOT_BUILT_IN;
-#else
+#if defined(USE_HTTP2) || defined(USE_HTTP3)
     arg = va_arg(param, long);
     if((arg >= 1) && (arg <= 256))
-      data->set.stream_weight = (int)arg;
+      data->set.priority.weight = (int)arg;
     break;
+#else
+    return CURLE_NOT_BUILT_IN;
 #endif
   case CURLOPT_STREAM_DEPENDS:
   case CURLOPT_STREAM_DEPENDS_E:
   {
-#ifndef USE_NGHTTP2
-    return CURLE_NOT_BUILT_IN;
-#else
     struct Curl_easy *dep = va_arg(param, struct Curl_easy *);
     if(!dep || GOOD_EASY_HANDLE(dep)) {
-      if(data->set.stream_depends_on) {
-        Curl_http2_remove_child(data->set.stream_depends_on, data);
-      }
-      Curl_http2_add_child(dep, data, (option == CURLOPT_STREAM_DEPENDS_E));
+      return Curl_data_priority_add_child(dep, data,
+                                          option == CURLOPT_STREAM_DEPENDS_E);
     }
     break;
-#endif
   }
   case CURLOPT_CONNECT_TO:
     data->set.connect_to = va_arg(param, struct curl_slist *);
@@ -2988,7 +2997,7 @@
     break;
   case CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS:
     uarg = va_arg(param, unsigned long);
-    if(uarg >= UINT_MAX)
+    if(uarg > UINT_MAX)
       uarg = UINT_MAX;
     data->set.happy_eyeballs_timeout = (unsigned int)uarg;
     break;
@@ -3049,19 +3058,39 @@
   case CURLOPT_HSTSWRITEDATA:
     data->set.hsts_write_userp = va_arg(param, void *);
     break;
-  case CURLOPT_HSTS:
+  case CURLOPT_HSTS: {
+    struct curl_slist *h;
     if(!data->hsts) {
       data->hsts = Curl_hsts_init();
       if(!data->hsts)
         return CURLE_OUT_OF_MEMORY;
     }
     argptr = va_arg(param, char *);
-    result = Curl_setstropt(&data->set.str[STRING_HSTS], argptr);
-    if(result)
-      return result;
-    if(argptr)
-      (void)Curl_hsts_loadfile(data, data->hsts, argptr);
+    if(argptr) {
+      result = Curl_setstropt(&data->set.str[STRING_HSTS], argptr);
+      if(result)
+        return result;
+      /* this needs to build a list of file names to read from, so that it can
+         read them later, as we might get a shared HSTS handle to load them
+         into */
+      h = curl_slist_append(data->set.hstslist, argptr);
+      if(!h) {
+        curl_slist_free_all(data->set.hstslist);
+        data->set.hstslist = NULL;
+        return CURLE_OUT_OF_MEMORY;
+      }
+      data->set.hstslist = h; /* store the list for later use */
+    }
+    else {
+      /* clear the list of HSTS files */
+      curl_slist_free_all(data->set.hstslist);
+      data->set.hstslist = NULL;
+      if(!data->share || !data->share->hsts)
+        /* throw away the HSTS cache unless shared */
+        Curl_hsts_cleanup(&data->hsts);
+    }
     break;
+  }
   case CURLOPT_HSTS_CTRL:
     arg = va_arg(param, long);
     if(arg & CURLHSTS_ENABLE) {
diff --git a/Utilities/cmcurl/lib/setopt.h b/Utilities/cmcurl/lib/setopt.h
index ffc77a7..3c14a05 100644
--- a/Utilities/cmcurl/lib/setopt.h
+++ b/Utilities/cmcurl/lib/setopt.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/setup-os400.h b/Utilities/cmcurl/lib/setup-os400.h
index 7854397..7595834 100644
--- a/Utilities/cmcurl/lib/setup-os400.h
+++ b/Utilities/cmcurl/lib/setup-os400.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -205,7 +205,7 @@
 extern int Curl_os400_connect(int sd, struct sockaddr *destaddr, int addrlen);
 extern int Curl_os400_bind(int sd, struct sockaddr *localaddr, int addrlen);
 extern int Curl_os400_sendto(int sd, char *buffer, int buflen, int flags,
-                             struct sockaddr *dstaddr, int addrlen);
+                             const struct sockaddr *dstaddr, int addrlen);
 extern int Curl_os400_recvfrom(int sd, char *buffer, int buflen, int flags,
                                struct sockaddr *fromaddr, int *addrlen);
 extern int Curl_os400_getpeername(int sd, struct sockaddr *addr, int *addrlen);
diff --git a/Utilities/cmcurl/lib/setup-vms.h b/Utilities/cmcurl/lib/setup-vms.h
index b570683..46657b2 100644
--- a/Utilities/cmcurl/lib/setup-vms.h
+++ b/Utilities/cmcurl/lib/setup-vms.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/setup-win32.h b/Utilities/cmcurl/lib/setup-win32.h
index bc5f8ef..1394838 100644
--- a/Utilities/cmcurl/lib/setup-win32.h
+++ b/Utilities/cmcurl/lib/setup-win32.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/sha256.c b/Utilities/cmcurl/lib/sha256.c
index c96a9fc..fdfd631 100644
--- a/Utilities/cmcurl/lib/sha256.c
+++ b/Utilities/cmcurl/lib/sha256.c
@@ -5,8 +5,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2017, Florin Petriuc, <petriuc.florin@gmail.com>
- * Copyright (C) 2018 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Florin Petriuc, <petriuc.florin@gmail.com>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/share.c b/Utilities/cmcurl/lib/share.c
index 1a083e7..c0a8d80 100644
--- a/Utilities/cmcurl/lib/share.c
+++ b/Utilities/cmcurl/lib/share.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -29,9 +29,11 @@
 #include "share.h"
 #include "psl.h"
 #include "vtls/vtls.h"
-#include "curl_memory.h"
+#include "hsts.h"
 
-/* The last #include file should be: */
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
 #include "memdebug.h"
 
 struct Curl_share *
@@ -89,6 +91,18 @@
 #endif
       break;
 
+    case CURL_LOCK_DATA_HSTS:
+#ifndef CURL_DISABLE_HSTS
+      if(!share->hsts) {
+        share->hsts = Curl_hsts_init();
+        if(!share->hsts)
+          res = CURLSHE_NOMEM;
+      }
+#else   /* CURL_DISABLE_HSTS */
+      res = CURLSHE_NOT_BUILT_IN;
+#endif
+      break;
+
     case CURL_LOCK_DATA_SSL_SESSION:
 #ifdef USE_SSL
       if(!share->sslsession) {
@@ -141,6 +155,16 @@
 #endif
       break;
 
+    case CURL_LOCK_DATA_HSTS:
+#ifndef CURL_DISABLE_HSTS
+      if(share->hsts) {
+        Curl_hsts_cleanup(&share->hsts);
+      }
+#else   /* CURL_DISABLE_HSTS */
+      res = CURLSHE_NOT_BUILT_IN;
+#endif
+      break;
+
     case CURL_LOCK_DATA_SSL_SESSION:
 #ifdef USE_SSL
       Curl_safefree(share->sslsession);
@@ -207,6 +231,10 @@
   Curl_cookie_cleanup(share->cookies);
 #endif
 
+#ifndef CURL_DISABLE_HSTS
+  Curl_hsts_cleanup(&share->hsts);
+#endif
+
 #ifdef USE_SSL
   if(share->sslsession) {
     size_t i;
diff --git a/Utilities/cmcurl/lib/share.h b/Utilities/cmcurl/lib/share.h
index 32be416..7f55aac 100644
--- a/Utilities/cmcurl/lib/share.h
+++ b/Utilities/cmcurl/lib/share.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -59,10 +59,14 @@
 #ifdef USE_LIBPSL
   struct PslCache psl;
 #endif
-
+#ifndef CURL_DISABLE_HSTS
+  struct hsts *hsts;
+#endif
+#ifdef USE_SSL
   struct Curl_ssl_session *sslsession;
   size_t max_ssl_sessions;
   long sessionage;
+#endif
 };
 
 CURLSHcode Curl_share_lock(struct Curl_easy *, curl_lock_data,
diff --git a/Utilities/cmcurl/lib/sigpipe.h b/Utilities/cmcurl/lib/sigpipe.h
index d12b317..48761ad 100644
--- a/Utilities/cmcurl/lib/sigpipe.h
+++ b/Utilities/cmcurl/lib/sigpipe.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -50,7 +50,6 @@
   if(!data->set.no_signal) {
     struct sigaction action;
     /* first, extract the existing situation */
-    memset(&ig->old_pipe_act, 0, sizeof(struct sigaction));
     sigaction(SIGPIPE, NULL, &ig->old_pipe_act);
     action = ig->old_pipe_act;
     /* ignore this signal */
diff --git a/Utilities/cmcurl/lib/slist.c b/Utilities/cmcurl/lib/slist.c
index 6c80722..366b247 100644
--- a/Utilities/cmcurl/lib/slist.c
+++ b/Utilities/cmcurl/lib/slist.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/slist.h b/Utilities/cmcurl/lib/slist.h
index 4e5834c..9561fd0 100644
--- a/Utilities/cmcurl/lib/slist.h
+++ b/Utilities/cmcurl/lib/slist.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/smb.c b/Utilities/cmcurl/lib/smb.c
index 48d5a2f..0762004 100644
--- a/Utilities/cmcurl/lib/smb.c
+++ b/Utilities/cmcurl/lib/smb.c
@@ -5,8 +5,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2016 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2014, Bill Nagel <wnagel@tycoint.com>, Exacq Technologies
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Bill Nagel <wnagel@tycoint.com>, Exacq Technologies
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -25,8 +25,7 @@
 
 #include "curl_setup.h"
 
-#if !defined(CURL_DISABLE_SMB) && defined(USE_CURL_NTLM_CORE) &&  \
-  (SIZEOF_CURL_OFF_T > 4)
+#if !defined(CURL_DISABLE_SMB) && defined(USE_CURL_NTLM_CORE)
 
 #define BUILDING_CURL_SMB_C
 
@@ -763,6 +762,11 @@
   void *msg = NULL;
   const struct smb_nt_create_response *smb_m;
 
+  if(data->set.upload && (data->state.infilesize < 0)) {
+    failf(data, "SMB upload needs to know the size up front");
+    return CURLE_SEND_ERROR;
+  }
+
   /* Start the request */
   if(req->state == SMB_REQUESTING) {
     result = smb_send_tree_connect(data);
@@ -993,6 +997,7 @@
   /* The share must be present */
   if(!slash) {
     Curl_safefree(smbc->share);
+    failf(data, "missing share in URL path for SMB");
     return CURLE_URL_MALFORMAT;
   }
 
diff --git a/Utilities/cmcurl/lib/smb.h b/Utilities/cmcurl/lib/smb.h
index 919f3ac..c35f3e9 100644
--- a/Utilities/cmcurl/lib/smb.h
+++ b/Utilities/cmcurl/lib/smb.h
@@ -7,8 +7,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2018, Bill Nagel <wnagel@tycoint.com>, Exacq Technologies
- * Copyright (C) 2018 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Bill Nagel <wnagel@tycoint.com>, Exacq Technologies
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/smtp.c b/Utilities/cmcurl/lib/smtp.c
index 6d0783f4..7a03030 100644
--- a/Utilities/cmcurl/lib/smtp.c
+++ b/Utilities/cmcurl/lib/smtp.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -398,15 +398,17 @@
   struct connectdata *conn = data->conn;
   struct smtp_conn *smtpc = &conn->proto.smtpc;
   CURLcode result;
+  bool ssldone = FALSE;
 
-  if(!Curl_conn_is_ssl(data, FIRSTSOCKET)) {
+  if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
     result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET);
     if(result)
       goto out;
   }
 
-  result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &smtpc->ssldone);
+  result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone);
   if(!result) {
+    smtpc->ssldone = ssldone;
     if(smtpc->state != SMTP_UPGRADETLS)
       state(data, SMTP_UPGRADETLS);
 
@@ -891,7 +893,7 @@
 
   if(smtpcode/100 != 2 && smtpcode != 1) {
     if(data->set.use_ssl <= CURLUSESSL_TRY
-       || Curl_conn_is_ssl(data, FIRSTSOCKET))
+       || Curl_conn_is_ssl(conn, FIRSTSOCKET))
       result = smtp_perform_helo(data, conn);
     else {
       failf(data, "Remote access denied: %d", smtpcode);
@@ -956,7 +958,7 @@
     }
 
     if(smtpcode != 1) {
-      if(data->set.use_ssl && !Curl_conn_is_ssl(data, FIRSTSOCKET)) {
+      if(data->set.use_ssl && !Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
         /* We don't have a SSL/TLS connection yet, but SSL is requested */
         if(smtpc->tls_supported)
           /* Switch to TLS connection now */
@@ -1288,7 +1290,9 @@
   struct smtp_conn *smtpc = &conn->proto.smtpc;
 
   if((conn->handler->flags & PROTOPT_SSL) && !smtpc->ssldone) {
-    result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &smtpc->ssldone);
+    bool ssldone = FALSE;
+    result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone);
+    smtpc->ssldone = ssldone;
     if(result || !smtpc->ssldone)
       return result;
   }
diff --git a/Utilities/cmcurl/lib/smtp.h b/Utilities/cmcurl/lib/smtp.h
index 24c5589..7a04c21 100644
--- a/Utilities/cmcurl/lib/smtp.h
+++ b/Utilities/cmcurl/lib/smtp.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2009 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -57,28 +57,28 @@
   curl_pp_transfer transfer;
   char *custom;            /* Custom Request */
   struct curl_slist *rcpt; /* Recipient list */
-  bool rcpt_had_ok;        /* Whether any of RCPT TO commands (depends on
-                              total number of recipients) succeeded so far */
-  bool trailing_crlf;      /* Specifies if the trailing CRLF is present */
   int rcpt_last_error;     /* The last error received for RCPT TO command */
   size_t eob;              /* Number of bytes of the EOB (End Of Body) that
                               have been received so far */
+  BIT(rcpt_had_ok);        /* Whether any of RCPT TO commands (depends on
+                              total number of recipients) succeeded so far */
+  BIT(trailing_crlf);      /* Specifies if the trailing CRLF is present */
 };
 
 /* smtp_conn is used for struct connection-oriented data in the connectdata
    struct */
 struct smtp_conn {
   struct pingpong pp;
-  smtpstate state;         /* Always use smtp.c:state() to change state! */
-  bool ssldone;            /* Is connect() over SSL done? */
-  char *domain;            /* Client address/name to send in the EHLO */
   struct SASL sasl;        /* SASL-related storage */
-  bool tls_supported;      /* StartTLS capability supported by server */
-  bool size_supported;     /* If server supports SIZE extension according to
+  smtpstate state;         /* Always use smtp.c:state() to change state! */
+  char *domain;            /* Client address/name to send in the EHLO */
+  BIT(ssldone);            /* Is connect() over SSL done? */
+  BIT(tls_supported);      /* StartTLS capability supported by server */
+  BIT(size_supported);     /* If server supports SIZE extension according to
                               RFC 1870 */
-  bool utf8_supported;     /* If server supports SMTPUTF8 extension according
+  BIT(utf8_supported);     /* If server supports SMTPUTF8 extension according
                               to RFC 6531 */
-  bool auth_supported;     /* AUTH capability supported by server */
+  BIT(auth_supported);     /* AUTH capability supported by server */
 };
 
 extern const struct Curl_handler Curl_handler_smtp;
diff --git a/Utilities/cmcurl/lib/sockaddr.h b/Utilities/cmcurl/lib/sockaddr.h
index 77ec833..5a6bb20 100644
--- a/Utilities/cmcurl/lib/sockaddr.h
+++ b/Utilities/cmcurl/lib/sockaddr.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/socketpair.c b/Utilities/cmcurl/lib/socketpair.c
index 0f8798f..b94c984 100644
--- a/Utilities/cmcurl/lib/socketpair.c
+++ b/Utilities/cmcurl/lib/socketpair.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2019 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -65,7 +65,7 @@
   union {
     struct sockaddr_in inaddr;
     struct sockaddr addr;
-  } a, a2;
+  } a;
   curl_socket_t listener;
   curl_socklen_t addrlen = sizeof(a.inaddr);
   int reuse = 1;
@@ -85,9 +85,22 @@
 
   socks[0] = socks[1] = CURL_SOCKET_BAD;
 
+#if defined(WIN32) || defined(__CYGWIN__)
+  /* don't set SO_REUSEADDR on Windows */
+  (void)reuse;
+#ifdef SO_EXCLUSIVEADDRUSE
+  {
+    int exclusive = 1;
+    if(setsockopt(listener, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
+                  (char *)&exclusive, (curl_socklen_t)sizeof(exclusive)) == -1)
+      goto error;
+  }
+#endif
+#else
   if(setsockopt(listener, SOL_SOCKET, SO_REUSEADDR,
                 (char *)&reuse, (curl_socklen_t)sizeof(reuse)) == -1)
     goto error;
+#endif
   if(bind(listener, &a.addr, sizeof(a.inaddr)) == -1)
     goto error;
   if(getsockname(listener, &a.addr, &addrlen) == -1 ||
@@ -107,24 +120,59 @@
   pfd[0].fd = listener;
   pfd[0].events = POLLIN;
   pfd[0].revents = 0;
-  (void)Curl_poll(pfd, 1, 10*1000); /* 10 seconds */
+  (void)Curl_poll(pfd, 1, 1000); /* one second */
   socks[1] = accept(listener, NULL, NULL);
   if(socks[1] == CURL_SOCKET_BAD)
     goto error;
+  else {
+    struct curltime check;
+    struct curltime start = Curl_now();
+    char *p = (char *)&check;
+    size_t s = sizeof(check);
 
-  /* verify that nothing else connected */
-  addrlen = sizeof(a.inaddr);
-  if(getsockname(socks[0], &a.addr, &addrlen) == -1 ||
-     addrlen < (int)sizeof(a.inaddr))
-    goto error;
-  addrlen = sizeof(a2.inaddr);
-  if(getpeername(socks[1], &a2.addr, &addrlen) == -1 ||
-     addrlen < (int)sizeof(a2.inaddr))
-    goto error;
-  if(a.inaddr.sin_family != a2.inaddr.sin_family ||
-     a.inaddr.sin_addr.s_addr != a2.inaddr.sin_addr.s_addr ||
-     a.inaddr.sin_port != a2.inaddr.sin_port)
-    goto error;
+    /* write data to the socket */
+    swrite(socks[0], &start, sizeof(start));
+    /* verify that we read the correct data */
+    do {
+      ssize_t nread;
+
+      pfd[0].fd = socks[1];
+      pfd[0].events = POLLIN;
+      pfd[0].revents = 0;
+      (void)Curl_poll(pfd, 1, 1000); /* one second */
+
+      nread = sread(socks[1], p, s);
+      if(nread == -1) {
+        int sockerr = SOCKERRNO;
+        /* Don't block forever */
+        if(Curl_timediff(Curl_now(), start) > (60 * 1000))
+          goto error;
+        if(
+#ifdef WSAEWOULDBLOCK
+          /* This is how Windows does it */
+          (WSAEWOULDBLOCK == sockerr)
+#else
+          /* errno may be EWOULDBLOCK or on some systems EAGAIN when it
+             returned due to its inability to send off data without
+             blocking. We therefore treat both error codes the same here */
+          (EWOULDBLOCK == sockerr) || (EAGAIN == sockerr) ||
+          (EINTR == sockerr) || (EINPROGRESS == sockerr)
+#endif
+          ) {
+          continue;
+        }
+        goto error;
+      }
+      s -= nread;
+      if(s) {
+        p += nread;
+        continue;
+      }
+      if(memcmp(&start, &check, sizeof(check)))
+        goto error;
+      break;
+    } while(1);
+  }
 
   sclose(listener);
   return 0;
diff --git a/Utilities/cmcurl/lib/socketpair.h b/Utilities/cmcurl/lib/socketpair.h
index de70df6..306ab5d 100644
--- a/Utilities/cmcurl/lib/socketpair.h
+++ b/Utilities/cmcurl/lib/socketpair.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2019 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/socks.c b/Utilities/cmcurl/lib/socks.c
index d491e08..95c2b00 100644
--- a/Utilities/cmcurl/lib/socks.c
+++ b/Utilities/cmcurl/lib/socks.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -89,8 +89,8 @@
  *
  * This is STUPID BLOCKING behavior. Only used by the SOCKS GSSAPI functions.
  */
-int Curl_blockread_all(struct Curl_easy *data,   /* transfer */
-                       curl_socket_t sockfd,     /* read from this socket */
+int Curl_blockread_all(struct Curl_cfilter *cf,
+                       struct Curl_easy *data,   /* transfer */
                        char *buf,                /* store read data here */
                        ssize_t buffersize,       /* max amount to read */
                        ssize_t *n)               /* amount bytes read */
@@ -98,6 +98,8 @@
   ssize_t nread = 0;
   ssize_t allread = 0;
   int result;
+  CURLcode err = CURLE_OK;
+
   *n = 0;
   for(;;) {
     timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE);
@@ -108,15 +110,19 @@
     }
     if(!timeout_ms)
       timeout_ms = TIMEDIFF_T_MAX;
-    if(SOCKET_READABLE(sockfd, timeout_ms) <= 0) {
+    if(SOCKET_READABLE(cf->conn->sock[cf->sockindex], timeout_ms) <= 0) {
       result = ~CURLE_OK;
       break;
     }
-    result = Curl_read_plain(data, sockfd, buf, buffersize, &nread);
-    if(CURLE_AGAIN == result)
-      continue;
-    if(result)
-      break;
+    nread = Curl_conn_cf_recv(cf->next, data, buf, buffersize, &err);
+    if(nread <= 0) {
+      result = err;
+      if(CURLE_AGAIN == err)
+        continue;
+      if(err) {
+        break;
+      }
+    }
 
     if(buffersize == nread) {
       allread += nread;
@@ -192,6 +198,68 @@
 #endif
 }
 
+static CURLproxycode socks_state_send(struct Curl_cfilter *cf,
+                                      struct socks_state *sx,
+                                      struct Curl_easy *data,
+                                      CURLproxycode failcode,
+                                      const char *description)
+{
+  ssize_t nwritten;
+  CURLcode result;
+
+  nwritten = Curl_conn_cf_send(cf->next, data, (char *)sx->outp,
+                               sx->outstanding, &result);
+  if(nwritten <= 0) {
+    if(CURLE_AGAIN == result) {
+      return CURLPX_OK;
+    }
+    else if(CURLE_OK == result) {
+      /* connection closed */
+      failf(data, "connection to proxy closed");
+      return CURLPX_CLOSED;
+    }
+    failf(data, "Failed to send %s: %s", description,
+          curl_easy_strerror(result));
+    return failcode;
+  }
+  DEBUGASSERT(sx->outstanding >= nwritten);
+  /* not done, remain in state */
+  sx->outstanding -= nwritten;
+  sx->outp += nwritten;
+  return CURLPX_OK;
+}
+
+static CURLproxycode socks_state_recv(struct Curl_cfilter *cf,
+                                      struct socks_state *sx,
+                                      struct Curl_easy *data,
+                                      CURLproxycode failcode,
+                                      const char *description)
+{
+  ssize_t nread;
+  CURLcode result;
+
+  nread = Curl_conn_cf_recv(cf->next, data, (char *)sx->outp,
+                            sx->outstanding, &result);
+  if(nread <= 0) {
+    if(CURLE_AGAIN == result) {
+      return CURLPX_OK;
+    }
+    else if(CURLE_OK == result) {
+      /* connection closed */
+      failf(data, "connection to proxy closed");
+      return CURLPX_CLOSED;
+    }
+    failf(data, "SOCKS4: Failed receiving %s: %s", description,
+          curl_easy_strerror(result));
+    return failcode;
+  }
+  /* remain in reading state */
+  DEBUGASSERT(sx->outstanding >= nread);
+  sx->outstanding -= nread;
+  sx->outp += nread;
+  return CURLPX_OK;
+}
+
 /*
 * This function logs in to a SOCKS4 proxy and sends the specifics to the final
 * destination server.
@@ -212,10 +280,8 @@
     (conn->socks_proxy.proxytype == CURLPROXY_SOCKS4A) ? TRUE : FALSE;
   unsigned char *socksreq = (unsigned char *)data->state.buffer;
   CURLcode result;
-  curl_socket_t sockfd = conn->sock[cf->sockindex];
+  CURLproxycode presult;
   struct Curl_dns_entry *dns = NULL;
-  ssize_t actualread;
-  ssize_t written;
 
   /* make sure that the buffer is at least 600 bytes */
   DEBUGASSERT(READBUFFER_MIN >= 600);
@@ -250,7 +316,7 @@
     /* DNS resolve only for SOCKS4, not SOCKS4a */
     if(!protocol4a) {
       enum resolve_t rc =
-        Curl_resolv(data, sx->hostname, sx->remote_port, FALSE, &dns);
+        Curl_resolv(data, sx->hostname, sx->remote_port, TRUE, &dns);
 
       if(rc == CURLRESOLV_ERROR)
         return CURLPX_RESOLVE_HOST;
@@ -375,19 +441,14 @@
     /* FALLTHROUGH */
   case CONNECT_REQ_SENDING:
     /* Send request */
-    result = Curl_write_plain(data, sockfd, (char *)sx->outp,
-                              sx->outstanding, &written);
-    if(result && (CURLE_AGAIN != result)) {
-      failf(data, "Failed to send SOCKS4 connect request.");
-      return CURLPX_SEND_CONNECT;
-    }
-    if(written != sx->outstanding) {
-      /* not done, remain in state */
-      sx->outstanding -= written;
-      sx->outp += written;
+    presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT,
+                               "SOCKS4 connect request");
+    if(CURLPX_OK != presult)
+      return presult;
+    else if(sx->outstanding) {
+      /* remain in sending state */
       return CURLPX_OK;
     }
-
     /* done sending! */
     sx->outstanding = 8; /* receive data size */
     sx->outp = socksreq;
@@ -396,22 +457,12 @@
     /* FALLTHROUGH */
   case CONNECT_SOCKS_READ:
     /* Receive response */
-    result = Curl_read_plain(data, sockfd, (char *)sx->outp,
-                             sx->outstanding, &actualread);
-    if(result && (CURLE_AGAIN != result)) {
-      failf(data, "SOCKS4: Failed receiving connect request ack: %s",
-            curl_easy_strerror(result));
-      return CURLPX_RECV_CONNECT;
-    }
-    else if(!result && !actualread) {
-      /* connection closed */
-      failf(data, "connection to proxy closed");
-      return CURLPX_CLOSED;
-    }
-    else if(actualread != sx->outstanding) {
+    presult = socks_state_recv(cf, sx, data, CURLPX_RECV_CONNECT,
+                               "connect request ack");
+    if(CURLPX_OK != presult)
+      return presult;
+    else if(sx->outstanding) {
       /* remain in reading state */
-      sx->outstanding -= actualread;
-      sx->outp += actualread;
       return CURLPX_OK;
     }
     sxstate(sx, data, CONNECT_DONE);
@@ -518,10 +569,8 @@
   unsigned char *socksreq = (unsigned char *)data->state.buffer;
   char dest[256] = "unknown";  /* printable hostname:port */
   int idx;
-  ssize_t actualread;
-  ssize_t written;
   CURLcode result;
-  curl_socket_t sockfd = conn->sock[cf->sockindex];
+  CURLproxycode presult;
   bool socks5_resolve_local =
     (conn->socks_proxy.proxytype == CURLPROXY_SOCKS5) ? TRUE : FALSE;
   const size_t hostname_len = strlen(sx->hostname);
@@ -567,30 +616,25 @@
     /* write the number of authentication methods */
     socksreq[1] = (unsigned char) (idx - 2);
 
-    result = Curl_write_plain(data, sockfd, socksreq, idx, &written);
-    if(result && (CURLE_AGAIN != result)) {
-      failf(data, "Unable to send initial SOCKS5 request.");
-      return CURLPX_SEND_CONNECT;
-    }
-    if(written != idx) {
-      sxstate(sx, data, CONNECT_SOCKS_SEND);
-      sx->outstanding = idx - written;
-      sx->outp = &socksreq[written];
+    sx->outp = socksreq;
+    sx->outstanding = idx;
+    presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT,
+                               "initial SOCKS5 request");
+    if(CURLPX_OK != presult)
+      return presult;
+    else if(sx->outstanding) {
+      /* remain in sending state */
       return CURLPX_OK;
     }
     sxstate(sx, data, CONNECT_SOCKS_READ);
     goto CONNECT_SOCKS_READ_INIT;
   case CONNECT_SOCKS_SEND:
-    result = Curl_write_plain(data, sockfd, (char *)sx->outp,
-                              sx->outstanding, &written);
-    if(result && (CURLE_AGAIN != result)) {
-      failf(data, "Unable to send initial SOCKS5 request.");
-      return CURLPX_SEND_CONNECT;
-    }
-    if(written != sx->outstanding) {
-      /* not done, remain in state */
-      sx->outstanding -= written;
-      sx->outp += written;
+    presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT,
+                               "initial SOCKS5 request");
+    if(CURLPX_OK != presult)
+      return presult;
+    else if(sx->outstanding) {
+      /* remain in sending state */
       return CURLPX_OK;
     }
     /* FALLTHROUGH */
@@ -600,21 +644,12 @@
     sx->outp = socksreq; /* store it here */
     /* FALLTHROUGH */
   case CONNECT_SOCKS_READ:
-    result = Curl_read_plain(data, sockfd, (char *)sx->outp,
-                             sx->outstanding, &actualread);
-    if(result && (CURLE_AGAIN != result)) {
-      failf(data, "Unable to receive initial SOCKS5 response.");
-      return CURLPX_RECV_CONNECT;
-    }
-    else if(!result && !actualread) {
-      /* connection closed */
-      failf(data, "Connection to proxy closed");
-      return CURLPX_CLOSED;
-    }
-    else if(actualread != sx->outstanding) {
+    presult = socks_state_recv(cf, sx, data, CURLPX_RECV_CONNECT,
+                               "initial SOCKS5 response");
+    if(CURLPX_OK != presult)
+      return presult;
+    else if(sx->outstanding) {
       /* remain in reading state */
-      sx->outstanding -= actualread;
-      sx->outp += actualread;
       return CURLPX_OK;
     }
     else if(socksreq[0] != 5) {
@@ -634,7 +669,7 @@
 #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
     else if(allow_gssapi && (socksreq[1] == 1)) {
       sxstate(sx, data, CONNECT_GSSAPI_INIT);
-      result = Curl_SOCKS5_gssapi_negotiate(cf->sockindex, data);
+      result = Curl_SOCKS5_gssapi_negotiate(cf, data);
       if(result) {
         failf(data, "Unable to negotiate SOCKS5 GSS-API context.");
         return CURLPX_GSSAPI;
@@ -713,16 +748,12 @@
   }
     /* FALLTHROUGH */
   case CONNECT_AUTH_SEND:
-    result = Curl_write_plain(data, sockfd, sx->outp,
-                              sx->outstanding, &written);
-    if(result && (CURLE_AGAIN != result)) {
-      failf(data, "Failed to send SOCKS5 sub-negotiation request.");
-      return CURLPX_SEND_AUTH;
-    }
-    if(sx->outstanding != written) {
-      /* remain in state */
-      sx->outstanding -= written;
-      sx->outp += written;
+    presult = socks_state_send(cf, sx, data, CURLPX_SEND_AUTH,
+                               "SOCKS5 sub-negotiation request");
+    if(CURLPX_OK != presult)
+      return presult;
+    else if(sx->outstanding) {
+      /* remain in sending state */
       return CURLPX_OK;
     }
     sx->outp = socksreq;
@@ -730,21 +761,12 @@
     sxstate(sx, data, CONNECT_AUTH_READ);
     /* FALLTHROUGH */
   case CONNECT_AUTH_READ:
-    result = Curl_read_plain(data, sockfd, (char *)sx->outp,
-                             sx->outstanding, &actualread);
-    if(result && (CURLE_AGAIN != result)) {
-      failf(data, "Unable to receive SOCKS5 sub-negotiation response.");
-      return CURLPX_RECV_AUTH;
-    }
-    else if(!result && !actualread) {
-      /* connection closed */
-      failf(data, "connection to proxy closed");
-      return CURLPX_CLOSED;
-    }
-    else if(actualread != sx->outstanding) {
-      /* remain in state */
-      sx->outstanding -= actualread;
-      sx->outp += actualread;
+    presult = socks_state_recv(cf, sx, data, CURLPX_RECV_AUTH,
+                               "SOCKS5 sub-negotiation response");
+    if(CURLPX_OK != presult)
+      return presult;
+    else if(sx->outstanding) {
+      /* remain in reading state */
       return CURLPX_OK;
     }
     /* ignore the first (VER) byte */
@@ -761,7 +783,7 @@
   case CONNECT_REQ_INIT:
     if(socks5_resolve_local) {
       enum resolve_t rc = Curl_resolv(data, sx->hostname, sx->remote_port,
-                                      FALSE, &dns);
+                                      TRUE, &dns);
 
       if(rc == CURLRESOLV_ERROR)
         return CURLPX_RESOLVE_HOST;
@@ -909,16 +931,12 @@
     sxstate(sx, data, CONNECT_REQ_SENDING);
     /* FALLTHROUGH */
   case CONNECT_REQ_SENDING:
-    result = Curl_write_plain(data, sockfd, (char *)sx->outp,
-                              sx->outstanding, &written);
-    if(result && (CURLE_AGAIN != result)) {
-      failf(data, "Failed to send SOCKS5 connect request.");
-      return CURLPX_SEND_REQUEST;
-    }
-    if(sx->outstanding != written) {
-      /* remain in state */
-      sx->outstanding -= written;
-      sx->outp += written;
+    presult = socks_state_send(cf, sx, data, CURLPX_SEND_REQUEST,
+                               "SOCKS5 connect request");
+    if(CURLPX_OK != presult)
+      return presult;
+    else if(sx->outstanding) {
+      /* remain in send state */
       return CURLPX_OK;
     }
 #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
@@ -932,25 +950,15 @@
     sxstate(sx, data, CONNECT_REQ_READ);
     /* FALLTHROUGH */
   case CONNECT_REQ_READ:
-    result = Curl_read_plain(data, sockfd, (char *)sx->outp,
-                             sx->outstanding, &actualread);
-    if(result && (CURLE_AGAIN != result)) {
-      failf(data, "Failed to receive SOCKS5 connect request ack.");
-      return CURLPX_RECV_REQACK;
-    }
-    else if(!result && !actualread) {
-      /* connection closed */
-      failf(data, "connection to proxy closed");
-      return CURLPX_CLOSED;
-    }
-    else if(actualread != sx->outstanding) {
-      /* remain in state */
-      sx->outstanding -= actualread;
-      sx->outp += actualread;
+    presult = socks_state_recv(cf, sx, data, CURLPX_RECV_REQACK,
+                               "SOCKS5 connect request ack");
+    if(CURLPX_OK != presult)
+      return presult;
+    else if(sx->outstanding) {
+      /* remain in reading state */
       return CURLPX_OK;
     }
-
-    if(socksreq[0] != 5) { /* version */
+    else if(socksreq[0] != 5) { /* version */
       failf(data,
             "SOCKS5 reply has wrong version, version should be 5.");
       return CURLPX_BAD_VERSION;
@@ -1031,21 +1039,12 @@
 #endif
     /* FALLTHROUGH */
   case CONNECT_REQ_READ_MORE:
-    result = Curl_read_plain(data, sockfd, (char *)sx->outp,
-                             sx->outstanding, &actualread);
-    if(result && (CURLE_AGAIN != result)) {
-      failf(data, "Failed to receive SOCKS5 connect request ack.");
-      return CURLPX_RECV_ADDRESS;
-    }
-    else if(!result && !actualread) {
-      /* connection closed */
-      failf(data, "connection to proxy closed");
-      return CURLPX_CLOSED;
-    }
-    else if(actualread != sx->outstanding) {
-      /* remain in state */
-      sx->outstanding -= actualread;
-      sx->outp += actualread;
+    presult = socks_state_recv(cf, sx, data, CURLPX_RECV_ADDRESS,
+                               "SOCKS5 connect request address");
+    if(CURLPX_OK != presult)
+      return presult;
+    else if(sx->outstanding) {
+      /* remain in reading state */
       return CURLPX_OK;
     }
     sxstate(sx, data, CONNECT_DONE);
@@ -1151,7 +1150,6 @@
   result = connect_SOCKS(cf, sx, data);
   if(!result && sx->state == CONNECT_DONE) {
     cf->connected = TRUE;
-    Curl_updateconninfo(data, conn, conn->sock[cf->sockindex]);
     Curl_verboseconnect(data, conn);
     socks_proxy_cf_free(cf);
   }
@@ -1171,7 +1169,7 @@
   if(!fds && cf->next->connected && !cf->connected && sx) {
     /* If we are not connected, the filter below is and has nothing
      * to wait on, we determine what to wait for. */
-    socks[0] = cf->conn->sock[cf->sockindex];
+    socks[0] = Curl_conn_cf_get_socket(cf, data);
     switch(sx->state) {
     case CONNECT_RESOLVING:
     case CONNECT_SOCKS_READ:
@@ -1205,13 +1203,6 @@
   socks_proxy_cf_free(cf);
 }
 
-static void socks_proxy_cf_detach_data(struct Curl_cfilter *cf,
-                                       struct Curl_easy *data)
-{
-  (void)data;
-  socks_proxy_cf_free(cf);
-}
-
 static void socks_cf_get_host(struct Curl_cfilter *cf,
                               struct Curl_easy *data,
                               const char **phost,
@@ -1229,11 +1220,11 @@
   }
 }
 
-static const struct Curl_cftype cft_socks_proxy = {
+struct Curl_cftype Curl_cft_socks_proxy = {
   "SOCKS-PROXYY",
   CF_TYPE_IP_CONNECT,
+  0,
   socks_proxy_cf_destroy,
-  Curl_cf_def_setup,
   socks_proxy_cf_connect,
   socks_proxy_cf_close,
   socks_cf_get_host,
@@ -1241,8 +1232,10 @@
   Curl_cf_def_data_pending,
   Curl_cf_def_send,
   Curl_cf_def_recv,
-  Curl_cf_def_attach_data,
-  socks_proxy_cf_detach_data,
+  Curl_cf_def_cntrl,
+  Curl_cf_def_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  Curl_cf_def_query,
 };
 
 CURLcode Curl_conn_socks_proxy_add(struct Curl_easy *data,
@@ -1252,10 +1245,23 @@
   struct Curl_cfilter *cf;
   CURLcode result;
 
-  result = Curl_cf_create(&cf, &cft_socks_proxy, NULL);
+  result = Curl_cf_create(&cf, &Curl_cft_socks_proxy, NULL);
   if(!result)
     Curl_conn_cf_add(data, conn, sockindex, cf);
   return result;
 }
 
+CURLcode Curl_cf_socks_proxy_insert_after(struct Curl_cfilter *cf_at,
+                                          struct Curl_easy *data)
+{
+  struct Curl_cfilter *cf;
+  CURLcode result;
+
+  (void)data;
+  result = Curl_cf_create(&cf, &Curl_cft_socks_proxy, NULL);
+  if(!result)
+    Curl_conn_cf_insert_after(cf_at, cf);
+  return result;
+}
+
 #endif /* CURL_DISABLE_PROXY */
diff --git a/Utilities/cmcurl/lib/socks.h b/Utilities/cmcurl/lib/socks.h
index 2e2fa18..ba5b54a 100644
--- a/Utilities/cmcurl/lib/socks.h
+++ b/Utilities/cmcurl/lib/socks.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -37,8 +37,8 @@
  *
  * This is STUPID BLOCKING behavior
  */
-int Curl_blockread_all(struct Curl_easy *data,
-                       curl_socket_t sockfd,
+int Curl_blockread_all(struct Curl_cfilter *cf,
+                       struct Curl_easy *data,
                        char *buf,
                        ssize_t buffersize,
                        ssize_t *n);
@@ -47,7 +47,7 @@
 /*
  * This function handles the SOCKS5 GSS-API negotiation and initialization
  */
-CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
+CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf,
                                       struct Curl_easy *data);
 #endif
 
@@ -55,6 +55,11 @@
                                    struct connectdata *conn,
                                    int sockindex);
 
+CURLcode Curl_cf_socks_proxy_insert_after(struct Curl_cfilter *cf_at,
+                                          struct Curl_easy *data);
+
+extern struct Curl_cftype Curl_cft_socks_proxy;
+
 #endif /* CURL_DISABLE_PROXY */
 
 #endif  /* HEADER_CURL_SOCKS_H */
diff --git a/Utilities/cmcurl/lib/socks_gssapi.c b/Utilities/cmcurl/lib/socks_gssapi.c
index f14099f..2ede8c7 100644
--- a/Utilities/cmcurl/lib/socks_gssapi.c
+++ b/Utilities/cmcurl/lib/socks_gssapi.c
@@ -5,8 +5,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2012, Markus Moeller, <markus_moeller@compuserve.com>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Markus Moeller, <markus_moeller@compuserve.com>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -30,6 +30,7 @@
 #include "curl_gssapi.h"
 #include "urldata.h"
 #include "sendf.h"
+#include "cfilters.h"
 #include "connect.h"
 #include "timeval.h"
 #include "socks.h"
@@ -101,14 +102,14 @@
   return 0;
 }
 
-CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
+CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf,
                                       struct Curl_easy *data)
 {
-  struct connectdata *conn = data->conn;
-  curl_socket_t sock = conn->sock[sockindex];
+  struct connectdata *conn = cf->conn;
+  curl_socket_t sock = conn->sock[cf->sockindex];
   CURLcode code;
   ssize_t actualread;
-  ssize_t written;
+  ssize_t nwritten;
   int result;
   OM_uint32 gss_major_status, gss_minor_status, gss_status;
   OM_uint32 gss_ret_flags;
@@ -203,8 +204,8 @@
       us_length = htons((short)gss_send_token.length);
       memcpy(socksreq + 2, &us_length, sizeof(short));
 
-      code = Curl_write_plain(data, sock, (char *)socksreq, 4, &written);
-      if(code || (4 != written)) {
+      nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, &code);
+      if(code || (4 != nwritten)) {
         failf(data, "Failed to send GSS-API authentication request.");
         gss_release_name(&gss_status, &server);
         gss_release_buffer(&gss_status, &gss_recv_token);
@@ -213,10 +214,10 @@
         return CURLE_COULDNT_CONNECT;
       }
 
-      code = Curl_write_plain(data, sock, (char *)gss_send_token.value,
-                              gss_send_token.length, &written);
-
-      if(code || ((ssize_t)gss_send_token.length != written)) {
+      nwritten = Curl_conn_cf_send(cf->next, data,
+                                   (char *)gss_send_token.value,
+                                   gss_send_token.length, &code);
+      if(code || ((ssize_t)gss_send_token.length != nwritten)) {
         failf(data, "Failed to send GSS-API authentication token.");
         gss_release_name(&gss_status, &server);
         gss_release_buffer(&gss_status, &gss_recv_token);
@@ -242,7 +243,7 @@
      * +----+------+-----+----------------+
      */
 
-    result = Curl_blockread_all(data, sock, (char *)socksreq, 4, &actualread);
+    result = Curl_blockread_all(cf, data, (char *)socksreq, 4, &actualread);
     if(result || (actualread != 4)) {
       failf(data, "Failed to receive GSS-API authentication response.");
       gss_release_name(&gss_status, &server);
@@ -281,7 +282,7 @@
       return CURLE_OUT_OF_MEMORY;
     }
 
-    result = Curl_blockread_all(data, sock, (char *)gss_recv_token.value,
+    result = Curl_blockread_all(cf, data, (char *)gss_recv_token.value,
                                 gss_recv_token.length, &actualread);
 
     if(result || (actualread != us_length)) {
@@ -410,8 +411,8 @@
     memcpy(socksreq + 2, &us_length, sizeof(short));
   }
 
-  code = Curl_write_plain(data, sock, (char *)socksreq, 4, &written);
-  if(code  || (4 != written)) {
+  nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, &code);
+  if(code  || (4 != nwritten)) {
     failf(data, "Failed to send GSS-API encryption request.");
     gss_release_buffer(&gss_status, &gss_w_token);
     gss_delete_sec_context(&gss_status, &gss_context, NULL);
@@ -420,17 +421,18 @@
 
   if(data->set.socks5_gssapi_nec) {
     memcpy(socksreq, &gss_enc, 1);
-    code = Curl_write_plain(data, sock, socksreq, 1, &written);
-    if(code || ( 1 != written)) {
+    nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 1, &code);
+    if(code || ( 1 != nwritten)) {
       failf(data, "Failed to send GSS-API encryption type.");
       gss_delete_sec_context(&gss_status, &gss_context, NULL);
       return CURLE_COULDNT_CONNECT;
     }
   }
   else {
-    code = Curl_write_plain(data, sock, (char *)gss_w_token.value,
-                            gss_w_token.length, &written);
-    if(code || ((ssize_t)gss_w_token.length != written)) {
+    nwritten = Curl_conn_cf_send(cf->next, data,
+                                 (char *)gss_w_token.value,
+                                 gss_w_token.length, &code);
+    if(code || ((ssize_t)gss_w_token.length != nwritten)) {
       failf(data, "Failed to send GSS-API encryption type.");
       gss_release_buffer(&gss_status, &gss_w_token);
       gss_delete_sec_context(&gss_status, &gss_context, NULL);
@@ -439,7 +441,7 @@
     gss_release_buffer(&gss_status, &gss_w_token);
   }
 
-  result = Curl_blockread_all(data, sock, (char *)socksreq, 4, &actualread);
+  result = Curl_blockread_all(cf, data, (char *)socksreq, 4, &actualread);
   if(result || (actualread != 4)) {
     failf(data, "Failed to receive GSS-API encryption response.");
     gss_delete_sec_context(&gss_status, &gss_context, NULL);
@@ -470,7 +472,7 @@
     gss_delete_sec_context(&gss_status, &gss_context, NULL);
     return CURLE_OUT_OF_MEMORY;
   }
-  result = Curl_blockread_all(data, sock, (char *)gss_recv_token.value,
+  result = Curl_blockread_all(cf, data, (char *)gss_recv_token.value,
                               gss_recv_token.length, &actualread);
 
   if(result || (actualread != us_length)) {
diff --git a/Utilities/cmcurl/lib/socks_sspi.c b/Utilities/cmcurl/lib/socks_sspi.c
index 210a0df..d1200ea 100644
--- a/Utilities/cmcurl/lib/socks_sspi.c
+++ b/Utilities/cmcurl/lib/socks_sspi.c
@@ -5,8 +5,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2012, 2011, Markus Moeller, <markus_moeller@compuserve.com>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Markus Moeller, <markus_moeller@compuserve.com>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -29,6 +29,7 @@
 
 #include "urldata.h"
 #include "sendf.h"
+#include "cfilters.h"
 #include "connect.h"
 #include "strerror.h"
 #include "timeval.h"
@@ -62,11 +63,11 @@
 }
 
 /* This is the SSPI-using version of this function */
-CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
+CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf,
                                       struct Curl_easy *data)
 {
-  struct connectdata *conn = data->conn;
-  curl_socket_t sock = conn->sock[sockindex];
+  struct connectdata *conn = cf->conn;
+  curl_socket_t sock = conn->sock[cf->sockindex];
   CURLcode code;
   ssize_t actualread;
   ssize_t written;
@@ -206,7 +207,7 @@
       us_length = htons((short)sspi_send_token.cbBuffer);
       memcpy(socksreq + 2, &us_length, sizeof(short));
 
-      code = Curl_write_plain(data, sock, (char *)socksreq, 4, &written);
+      written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, &code);
       if(code || (4 != written)) {
         failf(data, "Failed to send SSPI authentication request.");
         free(service_name);
@@ -219,8 +220,9 @@
         return CURLE_COULDNT_CONNECT;
       }
 
-      code = Curl_write_plain(data, sock, (char *)sspi_send_token.pvBuffer,
-                              sspi_send_token.cbBuffer, &written);
+      written = Curl_conn_cf_send(cf->next, data,
+                                  (char *)sspi_send_token.pvBuffer,
+                                  sspi_send_token.cbBuffer, &code);
       if(code || (sspi_send_token.cbBuffer != (size_t)written)) {
         failf(data, "Failed to send SSPI authentication token.");
         free(service_name);
@@ -260,7 +262,7 @@
      * +----+------+-----+----------------+
      */
 
-    result = Curl_blockread_all(data, sock, (char *)socksreq, 4, &actualread);
+    result = Curl_blockread_all(cf, data, (char *)socksreq, 4, &actualread);
     if(result || (actualread != 4)) {
       failf(data, "Failed to receive SSPI authentication response.");
       free(service_name);
@@ -300,7 +302,7 @@
       s_pSecFn->DeleteSecurityContext(&sspi_context);
       return CURLE_OUT_OF_MEMORY;
     }
-    result = Curl_blockread_all(data, sock, (char *)sspi_recv_token.pvBuffer,
+    result = Curl_blockread_all(cf, data, (char *)sspi_recv_token.pvBuffer,
                                 sspi_recv_token.cbBuffer, &actualread);
 
     if(result || (actualread != us_length)) {
@@ -468,7 +470,7 @@
     memcpy(socksreq + 2, &us_length, sizeof(short));
   }
 
-  code = Curl_write_plain(data, sock, (char *)socksreq, 4, &written);
+  written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, &code);
   if(code || (4 != written)) {
     failf(data, "Failed to send SSPI encryption request.");
     if(sspi_send_token.pvBuffer)
@@ -479,7 +481,7 @@
 
   if(data->set.socks5_gssapi_nec) {
     memcpy(socksreq, &gss_enc, 1);
-    code = Curl_write_plain(data, sock, (char *)socksreq, 1, &written);
+    written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 1, &code);
     if(code || (1 != written)) {
       failf(data, "Failed to send SSPI encryption type.");
       s_pSecFn->DeleteSecurityContext(&sspi_context);
@@ -487,8 +489,9 @@
     }
   }
   else {
-    code = Curl_write_plain(data, sock, (char *)sspi_send_token.pvBuffer,
-                            sspi_send_token.cbBuffer, &written);
+    written = Curl_conn_cf_send(cf->next, data,
+                                (char *)sspi_send_token.pvBuffer,
+                                sspi_send_token.cbBuffer, &code);
     if(code || (sspi_send_token.cbBuffer != (size_t)written)) {
       failf(data, "Failed to send SSPI encryption type.");
       if(sspi_send_token.pvBuffer)
@@ -500,7 +503,7 @@
       s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer);
   }
 
-  result = Curl_blockread_all(data, sock, (char *)socksreq, 4, &actualread);
+  result = Curl_blockread_all(cf, data, (char *)socksreq, 4, &actualread);
   if(result || (actualread != 4)) {
     failf(data, "Failed to receive SSPI encryption response.");
     s_pSecFn->DeleteSecurityContext(&sspi_context);
@@ -532,7 +535,7 @@
     return CURLE_OUT_OF_MEMORY;
   }
 
-  result = Curl_blockread_all(data, sock, (char *)sspi_w_token[0].pvBuffer,
+  result = Curl_blockread_all(cf, data, (char *)sspi_w_token[0].pvBuffer,
                               sspi_w_token[0].cbBuffer, &actualread);
 
   if(result || (actualread != us_length)) {
diff --git a/Utilities/cmcurl/lib/speedcheck.c b/Utilities/cmcurl/lib/speedcheck.c
index 3ddc43d..580efbd 100644
--- a/Utilities/cmcurl/lib/speedcheck.c
+++ b/Utilities/cmcurl/lib/speedcheck.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/speedcheck.h b/Utilities/cmcurl/lib/speedcheck.h
index cb44eb0..bff2f32 100644
--- a/Utilities/cmcurl/lib/speedcheck.h
+++ b/Utilities/cmcurl/lib/speedcheck.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/splay.c b/Utilities/cmcurl/lib/splay.c
index 33b44aa..48e079b 100644
--- a/Utilities/cmcurl/lib/splay.c
+++ b/Utilities/cmcurl/lib/splay.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1997 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/splay.h b/Utilities/cmcurl/lib/splay.h
index 015e2ca..dd1d07a 100644
--- a/Utilities/cmcurl/lib/splay.h
+++ b/Utilities/cmcurl/lib/splay.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1997 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/strcase.c b/Utilities/cmcurl/lib/strcase.c
index 7fb9c80..7c0b4ef 100644
--- a/Utilities/cmcurl/lib/strcase.c
+++ b/Utilities/cmcurl/lib/strcase.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/strcase.h b/Utilities/cmcurl/lib/strcase.h
index 192e0da..8c50bbc 100644
--- a/Utilities/cmcurl/lib/strcase.h
+++ b/Utilities/cmcurl/lib/strcase.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/strdup.c b/Utilities/cmcurl/lib/strdup.c
index ac22b6d..07a6139 100644
--- a/Utilities/cmcurl/lib/strdup.c
+++ b/Utilities/cmcurl/lib/strdup.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -37,7 +37,7 @@
 #include "memdebug.h"
 
 #ifndef HAVE_STRDUP
-char *curlx_strdup(const char *str)
+char *Curl_strdup(const char *str)
 {
   size_t len;
   char *newstr;
diff --git a/Utilities/cmcurl/lib/strdup.h b/Utilities/cmcurl/lib/strdup.h
index fb46808..c3430b5 100644
--- a/Utilities/cmcurl/lib/strdup.h
+++ b/Utilities/cmcurl/lib/strdup.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -26,7 +26,7 @@
 #include "curl_setup.h"
 
 #ifndef HAVE_STRDUP
-extern char *curlx_strdup(const char *str);
+char *Curl_strdup(const char *str);
 #endif
 #ifdef WIN32
 wchar_t* Curl_wcsdup(const wchar_t* src);
diff --git a/Utilities/cmcurl/lib/strerror.c b/Utilities/cmcurl/lib/strerror.c
index b9a51e2..3ec10e3 100644
--- a/Utilities/cmcurl/lib/strerror.c
+++ b/Utilities/cmcurl/lib/strerror.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2004 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -550,6 +550,9 @@
   case CURLUE_BAD_USER:
     return "Bad user";
 
+  case CURLUE_LACKS_IDN:
+    return "libcurl lacks IDN support";
+
   case CURLUE_LAST:
     break;
   }
diff --git a/Utilities/cmcurl/lib/strerror.h b/Utilities/cmcurl/lib/strerror.h
index 658f16c..399712f 100644
--- a/Utilities/cmcurl/lib/strerror.h
+++ b/Utilities/cmcurl/lib/strerror.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/strtok.c b/Utilities/cmcurl/lib/strtok.c
index 6120bcc..d8e1e81 100644
--- a/Utilities/cmcurl/lib/strtok.c
+++ b/Utilities/cmcurl/lib/strtok.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/strtok.h b/Utilities/cmcurl/lib/strtok.h
index 641a3da..321cba2 100644
--- a/Utilities/cmcurl/lib/strtok.h
+++ b/Utilities/cmcurl/lib/strtok.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/strtoofft.c b/Utilities/cmcurl/lib/strtoofft.c
index fb8d921..077b257 100644
--- a/Utilities/cmcurl/lib/strtoofft.c
+++ b/Utilities/cmcurl/lib/strtoofft.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/strtoofft.h b/Utilities/cmcurl/lib/strtoofft.h
index 311dae4..34d293b 100644
--- a/Utilities/cmcurl/lib/strtoofft.h
+++ b/Utilities/cmcurl/lib/strtoofft.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/system_win32.c b/Utilities/cmcurl/lib/system_win32.c
index bede9c7..0cdaf3b 100644
--- a/Utilities/cmcurl/lib/system_win32.c
+++ b/Utilities/cmcurl/lib/system_win32.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2016 - 2022, Steve Holme, <steve_holme@hotmail.com>.
+ * Copyright (C) Steve Holme, <steve_holme@hotmail.com>.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/system_win32.h b/Utilities/cmcurl/lib/system_win32.h
index 167804e..24899cb 100644
--- a/Utilities/cmcurl/lib/system_win32.h
+++ b/Utilities/cmcurl/lib/system_win32.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2016 - 2022, Steve Holme, <steve_holme@hotmail.com>.
+ * Copyright (C) Steve Holme, <steve_holme@hotmail.com>.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/telnet.c b/Utilities/cmcurl/lib/telnet.c
index 22bc81e..e4ffd85 100644
--- a/Utilities/cmcurl/lib/telnet.c
+++ b/Utilities/cmcurl/lib/telnet.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -770,22 +770,32 @@
   }
 }
 
+static bool str_is_nonascii(const char *str)
+{
+  size_t len = strlen(str);
+  while(len--) {
+    if(*str & 0x80)
+      return TRUE;
+    str++;
+  }
+  return FALSE;
+}
+
 static CURLcode check_telnet_options(struct Curl_easy *data)
 {
   struct curl_slist *head;
   struct curl_slist *beg;
-  char option_keyword[128] = "";
-  char option_arg[256] = "";
   struct TELNET *tn = data->req.p.telnet;
-  struct connectdata *conn = data->conn;
   CURLcode result = CURLE_OK;
-  int binary_option;
 
   /* Add the user name as an environment variable if it
      was given on the command line */
   if(data->state.aptr.user) {
-    msnprintf(option_arg, sizeof(option_arg), "USER,%s", conn->user);
-    beg = curl_slist_append(tn->telnet_vars, option_arg);
+    char buffer[256];
+    if(str_is_nonascii(data->conn->user))
+      return CURLE_BAD_FUNCTION_ARGUMENT;
+    msnprintf(buffer, sizeof(buffer), "USER,%s", data->conn->user);
+    beg = curl_slist_append(tn->telnet_vars, buffer);
     if(!beg) {
       curl_slist_free_all(tn->telnet_vars);
       tn->telnet_vars = NULL;
@@ -795,68 +805,100 @@
     tn->us_preferred[CURL_TELOPT_NEW_ENVIRON] = CURL_YES;
   }
 
-  for(head = data->set.telnet_options; head; head = head->next) {
-    if(sscanf(head->data, "%127[^= ]%*[ =]%255s",
-              option_keyword, option_arg) == 2) {
-
-      /* Terminal type */
-      if(strcasecompare(option_keyword, "TTYPE")) {
-        strncpy(tn->subopt_ttype, option_arg, 31);
-        tn->subopt_ttype[31] = 0; /* String termination */
-        tn->us_preferred[CURL_TELOPT_TTYPE] = CURL_YES;
+  for(head = data->set.telnet_options; head && !result; head = head->next) {
+    size_t olen;
+    char *option = head->data;
+    char *arg;
+    char *sep = strchr(option, '=');
+    if(sep) {
+      olen = sep - option;
+      arg = ++sep;
+      if(str_is_nonascii(arg))
         continue;
-      }
-
-      /* Display variable */
-      if(strcasecompare(option_keyword, "XDISPLOC")) {
-        strncpy(tn->subopt_xdisploc, option_arg, 127);
-        tn->subopt_xdisploc[127] = 0; /* String termination */
-        tn->us_preferred[CURL_TELOPT_XDISPLOC] = CURL_YES;
-        continue;
-      }
-
-      /* Environment variable */
-      if(strcasecompare(option_keyword, "NEW_ENV")) {
-        beg = curl_slist_append(tn->telnet_vars, option_arg);
-        if(!beg) {
-          result = CURLE_OUT_OF_MEMORY;
-          break;
+      switch(olen) {
+      case 5:
+        /* Terminal type */
+        if(strncasecompare(option, "TTYPE", 5)) {
+          strncpy(tn->subopt_ttype, arg, 31);
+          tn->subopt_ttype[31] = 0; /* String termination */
+          tn->us_preferred[CURL_TELOPT_TTYPE] = CURL_YES;
         }
-        tn->telnet_vars = beg;
-        tn->us_preferred[CURL_TELOPT_NEW_ENVIRON] = CURL_YES;
-        continue;
-      }
+        else
+          result = CURLE_UNKNOWN_OPTION;
+        break;
 
-      /* Window Size */
-      if(strcasecompare(option_keyword, "WS")) {
-        if(sscanf(option_arg, "%hu%*[xX]%hu",
-                  &tn->subopt_wsx, &tn->subopt_wsy) == 2)
-          tn->us_preferred[CURL_TELOPT_NAWS] = CURL_YES;
-        else {
-          failf(data, "Syntax error in telnet option: %s", head->data);
-          result = CURLE_SETOPT_OPTION_SYNTAX;
-          break;
+      case 8:
+        /* Display variable */
+        if(strncasecompare(option, "XDISPLOC", 8)) {
+          strncpy(tn->subopt_xdisploc, arg, 127);
+          tn->subopt_xdisploc[127] = 0; /* String termination */
+          tn->us_preferred[CURL_TELOPT_XDISPLOC] = CURL_YES;
         }
-        continue;
-      }
+        else
+          result = CURLE_UNKNOWN_OPTION;
+        break;
 
-      /* To take care or not of the 8th bit in data exchange */
-      if(strcasecompare(option_keyword, "BINARY")) {
-        binary_option = atoi(option_arg);
-        if(binary_option != 1) {
-          tn->us_preferred[CURL_TELOPT_BINARY] = CURL_NO;
-          tn->him_preferred[CURL_TELOPT_BINARY] = CURL_NO;
+      case 7:
+        /* Environment variable */
+        if(strncasecompare(option, "NEW_ENV", 7)) {
+          beg = curl_slist_append(tn->telnet_vars, arg);
+          if(!beg) {
+            result = CURLE_OUT_OF_MEMORY;
+            break;
+          }
+          tn->telnet_vars = beg;
+          tn->us_preferred[CURL_TELOPT_NEW_ENVIRON] = CURL_YES;
         }
-        continue;
-      }
+        else
+          result = CURLE_UNKNOWN_OPTION;
+        break;
 
-      failf(data, "Unknown telnet option %s", head->data);
-      result = CURLE_UNKNOWN_OPTION;
-      break;
+      case 2:
+        /* Window Size */
+        if(strncasecompare(option, "WS", 2)) {
+          char *p;
+          unsigned long x = strtoul(arg, &p, 10);
+          unsigned long y = 0;
+          if(x && (x <= 0xffff) && Curl_raw_tolower(*p) == 'x') {
+            p++;
+            y = strtoul(p, NULL, 10);
+            if(y && (y <= 0xffff)) {
+              tn->subopt_wsx = (unsigned short)x;
+              tn->subopt_wsy = (unsigned short)y;
+              tn->us_preferred[CURL_TELOPT_NAWS] = CURL_YES;
+            }
+          }
+          if(!y) {
+            failf(data, "Syntax error in telnet option: %s", head->data);
+            result = CURLE_SETOPT_OPTION_SYNTAX;
+          }
+        }
+        else
+          result = CURLE_UNKNOWN_OPTION;
+        break;
+
+      case 6:
+        /* To take care or not of the 8th bit in data exchange */
+        if(strncasecompare(option, "BINARY", 6)) {
+          int binary_option = atoi(arg);
+          if(binary_option != 1) {
+            tn->us_preferred[CURL_TELOPT_BINARY] = CURL_NO;
+            tn->him_preferred[CURL_TELOPT_BINARY] = CURL_NO;
+          }
+        }
+        else
+          result = CURLE_UNKNOWN_OPTION;
+        break;
+      default:
+        failf(data, "Unknown telnet option %s", head->data);
+        result = CURLE_UNKNOWN_OPTION;
+        break;
+      }
     }
-    failf(data, "Syntax error in telnet option: %s", head->data);
-    result = CURLE_SETOPT_OPTION_SYNTAX;
-    break;
+    else {
+      failf(data, "Syntax error in telnet option: %s", head->data);
+      result = CURLE_SETOPT_OPTION_SYNTAX;
+    }
   }
 
   if(result) {
@@ -881,8 +923,6 @@
   ssize_t bytes_written;
   size_t len;
   int err;
-  char varname[128] = "";
-  char varval[128] = "";
   struct TELNET *tn = data->req.p.telnet;
   struct connectdata *conn = data->conn;
 
@@ -920,19 +960,18 @@
 
       for(v = tn->telnet_vars; v; v = v->next) {
         size_t tmplen = (strlen(v->data) + 1);
-        /* Add the variable only if it fits */
+        /* Add the variable if it fits */
         if(len + tmplen < (int)sizeof(temp)-6) {
-          int rv;
-          char sep[2] = "";
-          varval[0] = 0;
-          rv = sscanf(v->data, "%127[^,]%1[,]%127s", varname, sep, varval);
-          if(rv == 1)
+          char *s = strchr(v->data, ',');
+          if(!s)
             len += msnprintf((char *)&temp[len], sizeof(temp) - len,
-                             "%c%s", CURL_NEW_ENV_VAR, varname);
-          else if(rv >= 2)
+                             "%c%s", CURL_NEW_ENV_VAR, v->data);
+          else {
+            size_t vlen = s - v->data;
             len += msnprintf((char *)&temp[len], sizeof(temp) - len,
-                             "%c%s%c%s", CURL_NEW_ENV_VAR, varname,
-                             CURL_NEW_ENV_VALUE, varval);
+                             "%c%.*s%c%s", CURL_NEW_ENV_VAR,
+                             (int)vlen, v->data, CURL_NEW_ENV_VALUE, ++s);
+          }
         }
       }
       msnprintf((char *)&temp[len], sizeof(temp) - len,
diff --git a/Utilities/cmcurl/lib/telnet.h b/Utilities/cmcurl/lib/telnet.h
index 6dd99b4..30782d8 100644
--- a/Utilities/cmcurl/lib/telnet.h
+++ b/Utilities/cmcurl/lib/telnet.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/tftp.c b/Utilities/cmcurl/lib/tftp.c
index 9e6d949..164d3c7 100644
--- a/Utilities/cmcurl/lib/tftp.c
+++ b/Utilities/cmcurl/lib/tftp.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -48,6 +48,7 @@
 
 #include "urldata.h"
 #include <curl/curl.h>
+#include "cf-socket.h"
 #include "transfer.h"
 #include "sendf.h"
 #include "tftp.h"
@@ -529,8 +530,8 @@
        not have a size_t argument, like older unixes that want an 'int' */
     senddata = sendto(state->sockfd, (void *)state->spacket.data,
                       (SEND_TYPE_ARG3)sbytes, 0,
-                      data->conn->ip_addr->ai_addr,
-                      data->conn->ip_addr->ai_addrlen);
+                      &data->conn->remote_addr->sa_addr,
+                      data->conn->remote_addr->addrlen);
     if(senddata != (ssize_t)sbytes) {
       char buffer[STRERROR_LEN];
       failf(data, "%s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
@@ -1014,7 +1015,7 @@
   state->requested_blksize = blksize;
 
   ((struct sockaddr *)&state->local_addr)->sa_family =
-    (CURL_SA_FAMILY_T)(conn->ip_addr->ai_family);
+    (CURL_SA_FAMILY_T)(conn->remote_addr->family);
 
   tftp_set_timeouts(state);
 
@@ -1033,7 +1034,7 @@
      * IPv4 and IPv6...
      */
     int rc = bind(state->sockfd, (struct sockaddr *)&state->local_addr,
-                  conn->ip_addr->ai_addrlen);
+                  conn->remote_addr->addrlen);
     if(rc) {
       char buffer[STRERROR_LEN];
       failf(data, "bind() failed; %s",
diff --git a/Utilities/cmcurl/lib/tftp.h b/Utilities/cmcurl/lib/tftp.h
index 3f1fda6..5d2d5da 100644
--- a/Utilities/cmcurl/lib/tftp.h
+++ b/Utilities/cmcurl/lib/tftp.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/timediff.c b/Utilities/cmcurl/lib/timediff.c
index c589318..1b762bb 100644
--- a/Utilities/cmcurl/lib/timediff.c
+++ b/Utilities/cmcurl/lib/timediff.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/timediff.h b/Utilities/cmcurl/lib/timediff.h
index 90e5474..fb318d4 100644
--- a/Utilities/cmcurl/lib/timediff.h
+++ b/Utilities/cmcurl/lib/timediff.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/timeval.c b/Utilities/cmcurl/lib/timeval.c
index 647d7b0..dca1c6f 100644
--- a/Utilities/cmcurl/lib/timeval.c
+++ b/Utilities/cmcurl/lib/timeval.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/timeval.h b/Utilities/cmcurl/lib/timeval.h
index 8d4fef4..92e484a 100644
--- a/Utilities/cmcurl/lib/timeval.h
+++ b/Utilities/cmcurl/lib/timeval.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/transfer.c b/Utilities/cmcurl/lib/transfer.c
index ba0410f..a283952 100644
--- a/Utilities/cmcurl/lib/transfer.c
+++ b/Utilities/cmcurl/lib/transfer.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -73,6 +73,7 @@
 #include "url.h"
 #include "getinfo.h"
 #include "vtls/vtls.h"
+#include "vquic/vquic.h"
 #include "select.h"
 #include "multiif.h"
 #include "connect.h"
@@ -367,27 +368,12 @@
 {
   struct connectdata *conn = data->conn;
 
-#ifdef ENABLE_QUIC
-  if(conn->transport == TRNSPRT_QUIC)
-    return Curl_quic_data_pending(data);
-#endif
-
   if(conn->handler->protocol&PROTO_FAMILY_FTP)
     return Curl_conn_data_pending(data, SECONDARYSOCKET);
 
   /* in the case of libssh2, we can never be really sure that we have emptied
      its internal buffers so we MUST always try until we get EAGAIN back */
   return conn->handler->protocol&(CURLPROTO_SCP|CURLPROTO_SFTP) ||
-#ifdef USE_NGHTTP2
-    /* For HTTP/2, we may read up everything including response body
-       with header fields in Curl_http_readwrite_headers. If no
-       content-length is provided, curl waits for the connection
-       close, which we emulate it using conn->proto.httpc.closed =
-       TRUE. The thing is if we read everything, then http2_recv won't
-       be called and we cannot signal the HTTP/2 stream has closed. As
-       a workaround, we return nonzero here to call http2_recv. */
-    ((conn->handler->protocol&PROTO_FAMILY_HTTP) && conn->httpversion >= 20) ||
-#endif
     Curl_conn_data_pending(data, FIRSTSOCKET);
 }
 
@@ -454,29 +440,16 @@
     bool is_empty_data = FALSE;
     size_t buffersize = data->set.buffer_size;
     size_t bytestoread = buffersize;
-#ifdef USE_NGHTTP2
-    bool is_http2 = ((conn->handler->protocol & PROTO_FAMILY_HTTP) &&
-                     (conn->httpversion == 20));
-#endif
-    bool is_http3 =
-#ifdef ENABLE_QUIC
-      ((conn->handler->protocol & PROTO_FAMILY_HTTP) &&
-       (conn->httpversion == 30));
-#else
-      FALSE;
-#endif
+    /* For HTTP/2 and HTTP/3, read data without caring about the content
+       length. This is safe because body in HTTP/2 is always segmented
+       thanks to its framing layer. Meanwhile, we have to call Curl_read
+       to ensure that http2_handle_stream_close is called when we read all
+       incoming bytes for a particular stream. */
+    bool is_http3 = Curl_conn_is_http3(data, conn, FIRSTSOCKET);
+    bool data_eof_handled = is_http3
+                            || Curl_conn_is_http2(data, conn, FIRSTSOCKET);
 
-    if(
-#ifdef USE_NGHTTP2
-      /* For HTTP/2, read data without caring about the content length. This
-         is safe because body in HTTP/2 is always segmented thanks to its
-         framing layer. Meanwhile, we have to call Curl_read to ensure that
-         http2_handle_stream_close is called when we read all incoming bytes
-         for a particular stream. */
-      !is_http2 &&
-#endif
-      !is_http3 && /* Same reason mentioned above. */
-      k->size != -1 && !k->header) {
+    if(!data_eof_handled && k->size != -1 && !k->header) {
       /* make sure we don't read too much */
       curl_off_t totalleft = k->size - k->bytecount;
       if(totalleft < (curl_off_t)bytestoread)
@@ -499,7 +472,7 @@
     else {
       /* read nothing but since we wanted nothing we consider this an OK
          situation to proceed from */
-      DEBUGF(infof(data, "readwrite_data: we're done"));
+      DEBUGF(infof(data, DMSG(data, "readwrite_data: we're done")));
       nread = 0;
     }
 
@@ -518,14 +491,9 @@
       buf[nread] = 0;
     }
     else {
-      /* if we receive 0 or less here, either the http2 stream is closed or the
+      /* if we receive 0 or less here, either the data transfer is done or the
          server closed the connection and we bail out from this! */
-#ifdef USE_NGHTTP2
-      if(is_http2 && !nread)
-        DEBUGF(infof(data, "nread == 0, stream closed, bailing"));
-      else
-#endif
-      if(is_http3 && !nread)
+      if(data_eof_handled)
         DEBUGF(infof(data, "nread == 0, stream closed, bailing"));
       else
         DEBUGF(infof(data, "nread <= 0, server closed connection, bailing"));
@@ -776,8 +744,8 @@
       k->keepon &= ~KEEP_RECV;
     }
 
-    if(k->keepon & KEEP_RECV_PAUSE) {
-      /* this is a paused transfer */
+    if((k->keepon & KEEP_RECV_PAUSE) || !(k->keepon & KEEP_RECV)) {
+      /* this is a paused or stopped transfer */
       break;
     }
 
@@ -799,19 +767,18 @@
   }
 
 out:
-  DEBUGF(infof(data, "readwrite_data(handle=%p) -> %d", data, result));
+  if(result)
+    DEBUGF(infof(data, DMSG(data, "readwrite_data() -> %d"), result));
   return result;
 }
 
 CURLcode Curl_done_sending(struct Curl_easy *data,
                            struct SingleRequest *k)
 {
-  struct connectdata *conn = data->conn;
   k->keepon &= ~KEEP_SEND; /* we're done writing */
 
   /* These functions should be moved into the handler struct! */
-  Curl_http2_done_sending(data, conn);
-  Curl_quic_done_sending(data);
+  Curl_conn_ev_data_done_send(data);
 
   return CURLE_OK;
 }
@@ -1013,7 +980,15 @@
     if(result)
       return result;
 
-    win_update_buffer_size(conn->writesockfd);
+#if defined(WIN32) && defined(USE_WINSOCK)
+    {
+      struct curltime n = Curl_now();
+      if(Curl_timediff(n, k->last_sndbuf_update) > 1000) {
+        win_update_buffer_size(conn->writesockfd);
+        k->last_sndbuf_update = n;
+      }
+    }
+#endif
 
     if(k->pendingheader) {
       /* parts of what was sent was header */
@@ -1088,6 +1063,7 @@
 {
   struct SingleRequest *k = &data->req;
   CURLcode result;
+  struct curltime now;
   int didwhat = 0;
 
   curl_socket_t fd_read;
@@ -1113,6 +1089,8 @@
   if(data->state.drain) {
     select_res |= CURL_CSELECT_IN;
     DEBUGF(infof(data, "Curl_readwrite: forcibly told to drain data"));
+    if((k->keepon & KEEP_SENDBITS) == KEEP_SEND)
+      select_res |= CURL_CSELECT_OUT;
   }
 #endif
 
@@ -1155,7 +1133,7 @@
   }
 #endif
 
-  k->now = Curl_now();
+  now = Curl_now();
   if(!didwhat) {
     /* no read no write, this is a timeout? */
     if(k->exp100 == EXP100_AWAITING_CONTINUE) {
@@ -1172,7 +1150,7 @@
 
       */
 
-      timediff_t ms = Curl_timediff(k->now, k->start100);
+      timediff_t ms = Curl_timediff(now, k->start100);
       if(ms >= data->set.expect_100_timeout) {
         /* we've waited long enough, continue anyway */
         k->exp100 = EXP100_SEND_DATA;
@@ -1182,35 +1160,31 @@
       }
     }
 
-#ifdef ENABLE_QUIC
-    if(conn->transport == TRNSPRT_QUIC) {
-      result = Curl_quic_idle(data);
-      if(result)
-        goto out;
-    }
-#endif
+    result = Curl_conn_ev_data_idle(data);
+    if(result)
+      goto out;
   }
 
   if(Curl_pgrsUpdate(data))
     result = CURLE_ABORTED_BY_CALLBACK;
   else
-    result = Curl_speedcheck(data, k->now);
+    result = Curl_speedcheck(data, now);
   if(result)
     goto out;
 
   if(k->keepon) {
-    if(0 > Curl_timeleft(data, &k->now, FALSE)) {
+    if(0 > Curl_timeleft(data, &now, FALSE)) {
       if(k->size != -1) {
         failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T
               " milliseconds with %" CURL_FORMAT_CURL_OFF_T " out of %"
               CURL_FORMAT_CURL_OFF_T " bytes received",
-              Curl_timediff(k->now, data->progress.t_startsingle),
+              Curl_timediff(now, data->progress.t_startsingle),
               k->bytecount, k->size);
       }
       else {
         failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T
               " milliseconds with %" CURL_FORMAT_CURL_OFF_T " bytes received",
-              Curl_timediff(k->now, data->progress.t_startsingle),
+              Curl_timediff(now, data->progress.t_startsingle),
               k->bytecount);
       }
       result = CURLE_OPERATION_TIMEDOUT;
@@ -1260,11 +1234,11 @@
   }
 
   /* Now update the "done" boolean we return */
-  *done = (0 == (k->keepon&(KEEP_RECV|KEEP_SEND|
-                            KEEP_RECV_PAUSE|KEEP_SEND_PAUSE))) ? TRUE : FALSE;
+  *done = (0 == (k->keepon&(KEEP_RECVBITS|KEEP_SENDBITS))) ? TRUE : FALSE;
   result = CURLE_OK;
 out:
-  DEBUGF(infof(data, "Curl_readwrite(handle=%p) -> %d", data, result));
+  if(result)
+    DEBUGF(infof(data, DMSG(data, "Curl_readwrite() -> %d"), result));
   return result;
 }
 
@@ -1377,6 +1351,7 @@
   data->state.authhost.want = data->set.httpauth;
   data->state.authproxy.want = data->set.proxyauth;
   Curl_safefree(data->info.wouldredirect);
+  Curl_data_priority_clear_state(data);
 
   if(data->state.httpreq == HTTPREQ_PUT)
     data->state.infilesize = data->set.filesize;
@@ -1389,15 +1364,16 @@
   else
     data->state.infilesize = 0;
 
-#ifndef CURL_DISABLE_COOKIES
   /* If there is a list of cookie files to read, do it now! */
-  if(data->state.cookielist)
-    Curl_cookie_loadfiles(data);
-#endif
+  Curl_cookie_loadfiles(data);
+
   /* If there is a list of host pairs to deal with */
   if(data->state.resolve)
     result = Curl_loadhostpairs(data);
 
+  /* If there is a list of hsts files to read */
+  Curl_hsts_loadfiles(data);
+
   if(!result) {
     /* Allow data->set.use_port to set which port to use. This needs to be
      * disabled for example when we follow Location: headers to URLs using
@@ -1425,7 +1401,13 @@
 #ifndef CURL_DISABLE_FTP
     data->state.wildcardmatch = data->set.wildcard_enabled;
     if(data->state.wildcardmatch) {
-      struct WildcardData *wc = &data->wildcard;
+      struct WildcardData *wc;
+      if(!data->wildcard) {
+        data->wildcard = calloc(1, sizeof(struct WildcardData));
+        if(!data->wildcard)
+          return CURLE_OUT_OF_MEMORY;
+      }
+      wc = data->wildcard;
       if(wc->state < CURLWC_INIT) {
         result = Curl_wildcard_init(wc); /* init wildcard structures */
         if(result)
@@ -1433,7 +1415,6 @@
       }
     }
 #endif
-    Curl_http2_init_state(&data->state);
     result = Curl_hsts_loadcb(data, data->hsts);
   }
 
@@ -1871,7 +1852,7 @@
   httpsending = ((conn->handler->protocol&PROTO_FAMILY_HTTP) &&
                  (http->sending == HTTPSEND_REQUEST));
 
-  if(conn->bits.multiplex || conn->httpversion == 20 || httpsending) {
+  if(conn->bits.multiplex || conn->httpversion >= 20 || httpsending) {
     /* when multiplexing, the read/write sockets need to be the same! */
     conn->sockfd = sockindex == -1 ?
       ((writesockindex == -1 ? CURL_SOCKET_BAD : conn->sock[writesockindex])) :
diff --git a/Utilities/cmcurl/lib/transfer.h b/Utilities/cmcurl/lib/transfer.h
index 4092508..536ac24 100644
--- a/Utilities/cmcurl/lib/transfer.h
+++ b/Utilities/cmcurl/lib/transfer.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/url.c b/Utilities/cmcurl/lib/url.c
index 3ab63a0..f7b4bbb 100644
--- a/Utilities/cmcurl/lib/url.c
+++ b/Utilities/cmcurl/lib/url.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -288,33 +288,6 @@
   (struct Curl_handler *) NULL
 };
 
-/*
- * Dummy handler for undefined protocol schemes.
- */
-
-static const struct Curl_handler Curl_handler_dummy = {
-  "<no protocol>",                      /* scheme */
-  ZERO_NULL,                            /* setup_connection */
-  ZERO_NULL,                            /* do_it */
-  ZERO_NULL,                            /* done */
-  ZERO_NULL,                            /* do_more */
-  ZERO_NULL,                            /* connect_it */
-  ZERO_NULL,                            /* connecting */
-  ZERO_NULL,                            /* doing */
-  ZERO_NULL,                            /* proto_getsock */
-  ZERO_NULL,                            /* doing_getsock */
-  ZERO_NULL,                            /* domore_getsock */
-  ZERO_NULL,                            /* perform_getsock */
-  ZERO_NULL,                            /* disconnect */
-  ZERO_NULL,                            /* readwrite */
-  ZERO_NULL,                            /* connection_check */
-  ZERO_NULL,                            /* attach connection */
-  0,                                    /* defport */
-  0,                                    /* protocol */
-  0,                                    /* family */
-  PROTOPT_NONE                          /* flags */
-};
-
 void Curl_freeset(struct Curl_easy *data)
 {
   /* Free all dynamic strings stored in the data->set substructure. */
@@ -341,6 +314,11 @@
   data->state.url = NULL;
 
   Curl_mime_cleanpart(&data->set.mimepost);
+
+#ifndef CURL_DISABLE_COOKIES
+  curl_slist_free_all(data->set.cookielist);
+  data->set.cookielist = NULL;
+#endif
 }
 
 /* free the URL pieces */
@@ -434,7 +412,11 @@
   Curl_altsvc_save(data, data->asi, data->set.str[STRING_ALTSVC]);
   Curl_altsvc_cleanup(&data->asi);
   Curl_hsts_save(data, data->hsts, data->set.str[STRING_HSTS]);
-  Curl_hsts_cleanup(&data->hsts);
+#ifndef CURL_DISABLE_HSTS
+  if(!data->share || !data->share->hsts)
+    Curl_hsts_cleanup(&data->hsts);
+  curl_slist_free_all(data->set.hstslist); /* clean up list */
+#endif
 #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH)
   Curl_http_auth_cleanup_digest(data);
 #endif
@@ -445,7 +427,7 @@
   Curl_resolver_cancel(data);
   Curl_resolver_cleanup(data->state.async.resolver);
 
-  Curl_http2_cleanup_dependencies(data);
+  Curl_data_priority_cleanup(data);
 
   /* No longer a dirty share, if it exists */
   if(data->share) {
@@ -531,11 +513,11 @@
   /* Timeout every 24 hours by default */
   set->general_ssl.ca_cache_timeout = 24 * 60 * 60;
 
-  set->proxyport = 0;
-  set->proxytype = CURLPROXY_HTTP; /* defaults to HTTP proxy */
   set->httpauth = CURLAUTH_BASIC;  /* defaults to basic */
 
 #ifndef CURL_DISABLE_PROXY
+  set->proxyport = 0;
+  set->proxytype = CURLPROXY_HTTP; /* defaults to HTTP proxy */
   set->proxyauth = CURLAUTH_BASIC; /* defaults to basic */
   /* SOCKS5 proxy auth defaults to username/password + GSS-API */
   set->socks5auth = CURLAUTH_BASIC | CURLAUTH_GSSAPI;
@@ -556,11 +538,11 @@
 #endif
   set->ssl.primary.verifypeer = TRUE;
   set->ssl.primary.verifyhost = TRUE;
-#ifdef USE_TLS_SRP
-  set->ssl.primary.authtype = CURL_TLSAUTH_NONE;
-#endif
-   /* defaults to any auth type */
+#ifdef USE_SSH
+  /* defaults to any auth type */
   set->ssh_auth_types = CURLSSH_AUTH_DEFAULT;
+  set->new_directory_perms = 0755; /* Default permissions */
+#endif
   set->ssl.primary.sessionid = TRUE; /* session ID caching enabled by
                                         default */
 #ifndef CURL_DISABLE_PROXY
@@ -568,7 +550,6 @@
 #endif
 
   set->new_file_perms = 0644;    /* Default permissions */
-  set->new_directory_perms = 0755; /* Default permissions */
   set->allowed_protocols = (curl_prot_t) CURLPROTO_ALL;
   set->redir_protocols = CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP |
                          CURLPROTO_FTPS;
@@ -631,14 +612,15 @@
   set->maxage_conn = 118;
   set->maxlifetime_conn = 0;
   set->http09_allowed = FALSE;
-  set->httpwant =
 #ifdef USE_HTTP2
-    CURL_HTTP_VERSION_2TLS
+  set->httpwant = CURL_HTTP_VERSION_2TLS
 #else
-    CURL_HTTP_VERSION_1_1
+  set->httpwant = CURL_HTTP_VERSION_1_1
 #endif
     ;
-  Curl_http2_init_userset(set);
+#if defined(USE_HTTP2) || defined(USE_HTTP3)
+  memset(&set->priority, 0, sizeof(set->priority));
+#endif
   set->quick_exit = 0L;
   return result;
 }
@@ -698,45 +680,6 @@
   return result;
 }
 
-#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
-static void conn_reset_postponed_data(struct connectdata *conn, int num)
-{
-  struct postponed_data * const psnd = &(conn->postponed[num]);
-  if(psnd->buffer) {
-    DEBUGASSERT(psnd->allocated_size > 0);
-    DEBUGASSERT(psnd->recv_size <= psnd->allocated_size);
-    DEBUGASSERT(psnd->recv_size ?
-                (psnd->recv_processed < psnd->recv_size) :
-                (psnd->recv_processed == 0));
-    DEBUGASSERT(psnd->bindsock != CURL_SOCKET_BAD);
-    free(psnd->buffer);
-    psnd->buffer = NULL;
-    psnd->allocated_size = 0;
-    psnd->recv_size = 0;
-    psnd->recv_processed = 0;
-#ifdef DEBUGBUILD
-    psnd->bindsock = CURL_SOCKET_BAD; /* used only for DEBUGASSERT */
-#endif /* DEBUGBUILD */
-  }
-  else {
-    DEBUGASSERT(psnd->allocated_size == 0);
-    DEBUGASSERT(psnd->recv_size == 0);
-    DEBUGASSERT(psnd->recv_processed == 0);
-    DEBUGASSERT(psnd->bindsock == CURL_SOCKET_BAD);
-  }
-}
-
-static void conn_reset_all_postponed_data(struct connectdata *conn)
-{
-  conn_reset_postponed_data(conn, 0);
-  conn_reset_postponed_data(conn, 1);
-}
-#else  /* ! USE_RECV_BEFORE_SEND_WORKAROUND */
-/* Use "do-nothing" macro instead of function when workaround not used */
-#define conn_reset_all_postponed_data(c) do {} while(0)
-#endif /* ! USE_RECV_BEFORE_SEND_WORKAROUND */
-
-
 static void conn_shutdown(struct Curl_easy *data)
 {
   DEBUGASSERT(data);
@@ -777,14 +720,13 @@
   Curl_safefree(conn->sasl_authzid);
   Curl_safefree(conn->options);
   Curl_safefree(conn->oauth_bearer);
+#ifndef CURL_DISABLE_HTTP
   Curl_dyn_free(&conn->trailer);
+#endif
   Curl_safefree(conn->host.rawalloc); /* host name buffer */
   Curl_safefree(conn->conn_to_host.rawalloc); /* host name buffer */
   Curl_safefree(conn->hostname_resolve);
   Curl_safefree(conn->secondaryhostname);
-
-  conn_reset_all_postponed_data(conn);
-  Curl_llist_destroy(&conn->easyq, NULL);
   Curl_safefree(conn->localdev);
   Curl_free_primary_ssl_config(&conn->ssl_config);
 
@@ -854,7 +796,7 @@
      disconnect and shutdown */
   Curl_attach_connection(data, conn);
 
-  if(conn->handler->disconnect)
+  if(conn->handler && conn->handler->disconnect)
     /* This is set if protocol-specific cleanups should be made */
     conn->handler->disconnect(data, conn, dead_connection);
 
@@ -867,24 +809,6 @@
 }
 
 /*
- * This function should return TRUE if the socket is to be assumed to
- * be dead. Most commonly this happens when the server has closed the
- * connection due to inactivity.
- */
-static bool SocketIsDead(curl_socket_t sock)
-{
-  int sval;
-  bool ret_val = TRUE;
-
-  sval = SOCKET_READABLE(sock, 0);
-  if(sval == 0)
-    /* timeout */
-    ret_val = FALSE;
-
-  return ret_val;
-}
-
-/*
  * IsMultiplexingPossible()
  *
  * Return a bitmask with the available multiplexing options for the given
@@ -1014,8 +938,20 @@
 
     }
     else {
-      /* Use the general method for determining the death of a connection */
-      dead = SocketIsDead(conn->sock[FIRSTSOCKET]);
+      bool input_pending;
+
+      dead = !Curl_conn_is_alive(data, conn, &input_pending);
+      if(input_pending) {
+        /* For reuse, we want a "clean" connection state. The includes
+         * that we expect - in general - no waiting input data. Input
+         * waiting might be a TLS Notify Close, for example. We reject
+         * that.
+         * For protocols where data from other other end may arrive at
+         * any time (HTTP/2 PING for example), the protocol handler needs
+         * to install its own `connection_check` callback.
+         */
+        dead = TRUE;
+      }
     }
 
     if(dead) {
@@ -1220,14 +1156,14 @@
             continue;
           }
         }
+      }
 
-        if(!Curl_conn_is_connected(check, FIRSTSOCKET)) {
-          foundPendingCandidate = TRUE;
-          /* Don't pick a connection that hasn't connected yet */
-          infof(data, "Connection #%ld isn't open enough, can't reuse",
-                check->connection_id);
-          continue;
-        }
+      if(!Curl_conn_is_connected(check, FIRSTSOCKET)) {
+        foundPendingCandidate = TRUE;
+        /* Don't pick a connection that hasn't connected yet */
+        infof(data, "Connection #%ld isn't open enough, can't reuse",
+              check->connection_id);
+        continue;
       }
 
 #ifdef USE_UNIX_SOCKETS
@@ -1341,17 +1277,37 @@
         }
       }
 
+      /* GSS delegation differences do not actually affect every connection
+         and auth method, but this check takes precaution before efficiency */
+      if(needle->gssapi_delegation != check->gssapi_delegation)
+        continue;
+
       /* If multiplexing isn't enabled on the h2 connection and h1 is
          explicitly requested, handle it: */
       if((needle->handler->protocol & PROTO_FAMILY_HTTP) &&
-         (check->httpversion >= 20) &&
-         (data->state.httpwant < CURL_HTTP_VERSION_2_0))
+         (((check->httpversion >= 20) &&
+           (data->state.httpwant < CURL_HTTP_VERSION_2_0))
+          || ((check->httpversion >= 30) &&
+           (data->state.httpwant < CURL_HTTP_VERSION_3))))
         continue;
-
-      if(get_protocol_family(needle->handler) == PROTO_FAMILY_SSH) {
+#ifdef USE_SSH
+      else if(get_protocol_family(needle->handler) & PROTO_FAMILY_SSH) {
         if(!ssh_config_matches(needle, check))
           continue;
       }
+#endif
+#ifndef CURL_DISABLE_FTP
+      else if(get_protocol_family(needle->handler) & PROTO_FAMILY_FTP) {
+        /* Also match ACCOUNT, ALTERNATIVE-TO-USER, USE_SSL and CCC options */
+        if(Curl_timestrcmp(needle->proto.ftpc.account,
+                           check->proto.ftpc.account) ||
+           Curl_timestrcmp(needle->proto.ftpc.alternative_to_user,
+                           check->proto.ftpc.alternative_to_user) ||
+           (needle->proto.ftpc.use_ssl != check->proto.ftpc.use_ssl) ||
+           (needle->proto.ftpc.ccc != check->proto.ftpc.ccc))
+          continue;
+      }
+#endif
 
       if((needle->handler->flags&PROTOPT_SSL)
 #ifndef CURL_DISABLE_PROXY
@@ -1467,9 +1423,8 @@
 #ifdef USE_NGHTTP2
           /* If multiplexed, make sure we don't go over concurrency limit */
           if(check->bits.multiplex) {
-            /* Multiplexed connections can only be HTTP/2 for now */
-            struct http_conn *httpc = &check->proto.httpc;
-            if(multiplexed >= httpc->settings.max_concurrent_streams) {
+            if(multiplexed >= Curl_conn_get_max_concurrent(data, check,
+                                                           FIRSTSOCKET)) {
               infof(data, "MAX_CONCURRENT_STREAMS reached, skip (%zu)",
                     multiplexed);
               continue;
@@ -1543,23 +1498,13 @@
   if(!conn)
     return NULL;
 
-  conn->handler = &Curl_handler_dummy;  /* Be sure we have a handler defined
-                                           already from start to avoid NULL
-                                           situations and checks */
-
   /* and we setup a few fields in case we end up actually using this struct */
 
   conn->sock[FIRSTSOCKET] = CURL_SOCKET_BAD;     /* no file descriptor */
   conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */
-  conn->tempsock[0] = CURL_SOCKET_BAD; /* no file descriptor */
-  conn->tempsock[1] = CURL_SOCKET_BAD; /* no file descriptor */
   conn->connection_id = -1;    /* no ID */
   conn->port = -1; /* unknown at this point */
   conn->remote_port = -1; /* unknown at this point */
-#if defined(USE_RECV_BEFORE_SEND_WORKAROUND) && defined(DEBUGBUILD)
-  conn->postponed[0].bindsock = CURL_SOCKET_BAD; /* no file descriptor */
-  conn->postponed[1].bindsock = CURL_SOCKET_BAD; /* no file descriptor */
-#endif /* USE_RECV_BEFORE_SEND_WORKAROUND && DEBUGBUILD */
 
   /* Default protocol-independent behavior doesn't support persistent
      connections, so we set this to force-close. Protocols that support
@@ -1644,11 +1589,11 @@
   conn->fclosesocket = data->set.fclosesocket;
   conn->closesocket_client = data->set.closesocket_client;
   conn->lastused = Curl_now(); /* used now */
+  conn->gssapi_delegation = data->set.gssapi_delegation;
 
   return conn;
   error:
 
-  Curl_llist_destroy(&conn->easyq, NULL);
   free(conn->localdev);
   free(conn);
   return NULL;
@@ -2329,7 +2274,7 @@
       result = CURLE_OUT_OF_MEMORY;
       goto error;
     }
-    /* path will be "/", if no path was was found */
+    /* path will be "/", if no path was found */
     if(strcmp("/", path)) {
       is_unix_proxy = TRUE;
       free(host);
@@ -2412,6 +2357,7 @@
   char *socksproxy = NULL;
   char *no_proxy = NULL;
   CURLcode result = CURLE_OK;
+  bool spacesep = FALSE;
 
   /*************************************************************
    * Extract the user and password from the authentication string
@@ -2458,7 +2404,8 @@
   }
 
   if(Curl_check_noproxy(conn->host.name, data->set.str[STRING_NOPROXY] ?
-                        data->set.str[STRING_NOPROXY] : no_proxy)) {
+                        data->set.str[STRING_NOPROXY] : no_proxy,
+                        &spacesep)) {
     Curl_safefree(proxy);
     Curl_safefree(socksproxy);
   }
@@ -2467,6 +2414,8 @@
     /* if the host is not in the noproxy list, detect proxy. */
     proxy = detect_proxy(data, conn);
 #endif /* CURL_DISABLE_HTTP */
+  if(spacesep)
+    infof(data, "space-separated NOPROXY patterns are deprecated");
 
   Curl_safefree(no_proxy);
 
@@ -2795,7 +2744,7 @@
         return CURLE_OUT_OF_MEMORY;
     }
     /* no user was set but a password, set a blank user */
-    if(userp && !*userp && *passwdp) {
+    if(!*userp && *passwdp) {
       *userp = strdup("");
       if(!*userp)
         return CURLE_OUT_OF_MEMORY;
@@ -3345,12 +3294,6 @@
                        struct connectdata *temp,
                        struct connectdata *existing)
 {
-  /* 'local_ip' and 'local_port' get filled with local's numerical
-     ip address and port number whenever an outgoing connection is
-     **established** from the primary socket to a remote address. */
-  char local_ip[MAX_IPADR_LEN] = "";
-  int local_port = -1;
-
   /* get the user+password information from the temp struct since it may
    * be new for this request even when we re-use an existing connection */
   if(temp->user) {
@@ -3382,6 +3325,20 @@
   }
 #endif
 
+  /* Finding a connection for reuse in the cache matches, among other
+   * things on the "remote-relevant" hostname. This is not necessarily
+   * the authority of the URL, e.g. conn->host. For example:
+   * - we use a proxy (not tunneling). we want to send all requests
+   *   that use the same proxy on this connection.
+   * - we have a "connect-to" setting that may redirect the hostname of
+   *   a new request to the same remote endpoint of an existing conn.
+   *   We want to reuse an existing conn to the remote endpoint.
+   * Since connection reuse does not match on conn->host necessarily, we
+   * switch `existing` conn to `temp` conn's host settings.
+   * TODO: is this correct in the case of TLS connections that have
+   *       used the original hostname in SNI to negotiate? Do we send
+   *       requests for another host through the different SNI?
+   */
   Curl_free_idnconverted_hostname(&existing->host);
   Curl_free_idnconverted_hostname(&existing->conn_to_host);
   Curl_safefree(existing->host.rawalloc);
@@ -3398,15 +3355,6 @@
   existing->hostname_resolve = temp->hostname_resolve;
   temp->hostname_resolve = NULL;
 
-  /* persist connection info in session handle */
-  if(existing->transport == TRNSPRT_TCP) {
-    Curl_conninfo_local(data, existing->sock[FIRSTSOCKET],
-                        local_ip, &local_port);
-  }
-  Curl_persistconninfo(data, existing, local_ip, local_port);
-
-  conn_reset_all_postponed_data(temp); /* free buffers */
-
   /* re-use init */
   existing->bits.reuse = TRUE; /* yes, we're re-using here */
 
@@ -3880,6 +3828,13 @@
    * Resolve the address of the server or proxy
    *************************************************************/
   result = resolve_server(data, conn, async);
+  if(result)
+    goto out;
+
+  /* Everything general done, inform filters that they need
+   * to prepare for a data transfer.
+   */
+  result = Curl_conn_ev_data_setup(data);
 
 out:
   return result;
@@ -4007,7 +3962,6 @@
     data->state.httpreq = HTTPREQ_HEAD;
 
   k->start = Curl_now(); /* start time */
-  k->now = k->start;   /* current time is now */
   k->header = TRUE; /* assume header */
   k->bytecount = 0;
   k->ignorebody = FALSE;
@@ -4018,3 +3972,103 @@
 
   return CURLE_OK;
 }
+
+#if defined(USE_HTTP2) || defined(USE_HTTP3)
+
+#ifdef USE_NGHTTP2
+
+static void priority_remove_child(struct Curl_easy *parent,
+                                  struct Curl_easy *child)
+{
+  struct Curl_data_prio_node **pnext = &parent->set.priority.children;
+  struct Curl_data_prio_node *pnode = parent->set.priority.children;
+
+  DEBUGASSERT(child->set.priority.parent == parent);
+  while(pnode && pnode->data != child) {
+    pnext = &pnode->next;
+    pnode = pnode->next;
+  }
+
+  DEBUGASSERT(pnode);
+  if(pnode) {
+    *pnext = pnode->next;
+    free(pnode);
+  }
+
+  child->set.priority.parent = 0;
+  child->set.priority.exclusive = FALSE;
+}
+
+CURLcode Curl_data_priority_add_child(struct Curl_easy *parent,
+                                      struct Curl_easy *child,
+                                      bool exclusive)
+{
+  if(child->set.priority.parent) {
+    priority_remove_child(child->set.priority.parent, child);
+  }
+
+  if(parent) {
+    struct Curl_data_prio_node **tail;
+    struct Curl_data_prio_node *pnode;
+
+    pnode = calloc(1, sizeof(*pnode));
+    if(!pnode)
+      return CURLE_OUT_OF_MEMORY;
+    pnode->data = child;
+
+    if(parent->set.priority.children && exclusive) {
+      /* exclusive: move all existing children underneath the new child */
+      struct Curl_data_prio_node *node = parent->set.priority.children;
+      while(node) {
+        node->data->set.priority.parent = child;
+        node = node->next;
+      }
+
+      tail = &child->set.priority.children;
+      while(*tail)
+        tail = &(*tail)->next;
+
+      DEBUGASSERT(!*tail);
+      *tail = parent->set.priority.children;
+      parent->set.priority.children = 0;
+    }
+
+    tail = &parent->set.priority.children;
+    while(*tail) {
+      (*tail)->data->set.priority.exclusive = FALSE;
+      tail = &(*tail)->next;
+    }
+
+    DEBUGASSERT(!*tail);
+    *tail = pnode;
+  }
+
+  child->set.priority.parent = parent;
+  child->set.priority.exclusive = exclusive;
+  return CURLE_OK;
+}
+
+#endif /* USE_NGHTTP2 */
+
+void Curl_data_priority_cleanup(struct Curl_easy *data)
+{
+#ifdef USE_NGHTTP2
+  while(data->set.priority.children) {
+    struct Curl_easy *tmp = data->set.priority.children->data;
+    priority_remove_child(data, tmp);
+    if(data->set.priority.parent)
+      Curl_data_priority_add_child(data->set.priority.parent, tmp, FALSE);
+  }
+
+  if(data->set.priority.parent)
+    priority_remove_child(data->set.priority.parent, data);
+#endif
+  (void)data;
+}
+
+void Curl_data_priority_clear_state(struct Curl_easy *data)
+{
+  memset(&data->state.priority, 0, sizeof(data->state.priority));
+}
+
+#endif /* defined(USE_HTTP2) || defined(USE_HTTP3) */
diff --git a/Utilities/cmcurl/lib/url.h b/Utilities/cmcurl/lib/url.h
index 1a03c56..3b58df4 100644
--- a/Utilities/cmcurl/lib/url.h
+++ b/Utilities/cmcurl/lib/url.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -59,4 +59,20 @@
 void Curl_verboseconnect(struct Curl_easy *data, struct connectdata *conn);
 #endif
 
+#if defined(USE_HTTP2) || defined(USE_HTTP3)
+void Curl_data_priority_cleanup(struct Curl_easy *data);
+void Curl_data_priority_clear_state(struct Curl_easy *data);
+#else
+#define Curl_data_priority_cleanup(x)
+#define Curl_data_priority_clear_state(x)
+#endif /* !(defined(USE_HTTP2) || defined(USE_HTTP3)) */
+
+#ifdef USE_NGHTTP2
+CURLcode Curl_data_priority_add_child(struct Curl_easy *parent,
+                                      struct Curl_easy *child,
+                                      bool exclusive);
+#else
+#define Curl_data_priority_add_child(x, y, z) CURLE_NOT_BUILT_IN
+#endif
+
 #endif /* HEADER_CURL_URL_H */
diff --git a/Utilities/cmcurl/lib/urlapi-int.h b/Utilities/cmcurl/lib/urlapi-int.h
index 43a83ef..28e5dd7 100644
--- a/Utilities/cmcurl/lib/urlapi-int.h
+++ b/Utilities/cmcurl/lib/urlapi-int.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/urlapi.c b/Utilities/cmcurl/lib/urlapi.c
index b96af35..62e3233 100644
--- a/Utilities/cmcurl/lib/urlapi.c
+++ b/Utilities/cmcurl/lib/urlapi.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -33,6 +33,7 @@
 #include "inet_pton.h"
 #include "inet_ntop.h"
 #include "strdup.h"
+#include "idn.h"
 
 /* The last 3 #include files should be in this order */
 #include "curl_printf.h"
@@ -56,6 +57,15 @@
 /* scheme is not URL encoded, the longest libcurl supported ones are... */
 #define MAX_SCHEME_LEN 40
 
+/*
+ * If ENABLE_IPV6 is disabled, we still want to parse IPv6 addresses, so make
+ * sure we have _some_ value for AF_INET6 without polluting our fake value
+ * everywhere.
+ */
+#if !defined(ENABLE_IPV6) && !defined(AF_INET6)
+#define AF_INET6 (AF_INET + 1)
+#endif
+
 /* Internal representation of CURLU. Point to URL-encoded strings. */
 struct Curl_URL {
   char *scheme;
@@ -116,14 +126,11 @@
 }
 
 /*
- * Decide in an encoding-independent manner whether a character in a URL must
- * be escaped. This is used in urlencode_str().
+ * Decide whether a character in a URL must be escaped.
  */
-static bool urlchar_needs_escaping(int c)
-{
-  return !(ISCNTRL(c) || ISSPACE(c) || ISGRAPH(c));
-}
+#define urlchar_needs_escaping(c) (!(ISCNTRL(c) || ISSPACE(c) || ISGRAPH(c)))
 
+static const char hexdigits[] = "0123456789abcdef";
 /* urlencode_str() writes data into an output dynbuf and URL-encodes the
  * spaces in the source URL accordingly.
  *
@@ -167,7 +174,10 @@
       left = FALSE;
 
     if(urlchar_needs_escaping(*iptr)) {
-      if(Curl_dyn_addf(o, "%%%02x", *iptr))
+      char out[3]={'%'};
+      out[1] = hexdigits[*iptr>>4];
+      out[2] = hexdigits[*iptr & 0xf];
+      if(Curl_dyn_addn(o, out, 3))
         return CURLUE_OUT_OF_MEMORY;
     }
     else {
@@ -492,35 +502,21 @@
 UNITTEST CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host,
                                    bool has_scheme)
 {
-  char *portptr = NULL;
-  char endbracket;
-  int len;
+  char *portptr;
   char *hostname = Curl_dyn_ptr(host);
   /*
    * Find the end of an IPv6 address, either on the ']' ending bracket or
    * a percent-encoded zone index.
    */
-  if(1 == sscanf(hostname, "[%*45[0123456789abcdefABCDEF:.]%c%n",
-                 &endbracket, &len)) {
-    if(']' == endbracket)
-      portptr = &hostname[len];
-    else if('%' == endbracket) {
-      int zonelen = len;
-      if(1 == sscanf(hostname + zonelen, "%*[^]]%c%n", &endbracket, &len)) {
-        if(']' != endbracket)
-          return CURLUE_BAD_IPV6;
-        portptr = &hostname[--zonelen + len + 1];
-      }
-      else
-        return CURLUE_BAD_IPV6;
-    }
-    else
+  if(hostname[0] == '[') {
+    portptr = strchr(hostname, ']');
+    if(!portptr)
       return CURLUE_BAD_IPV6;
-
+    portptr++;
     /* this is a RFC2732-style specified IP-address */
-    if(portptr && *portptr) {
+    if(*portptr) {
       if(*portptr != ':')
-        return CURLUE_BAD_IPV6;
+        return CURLUE_BAD_PORT_NUMBER;
     }
     else
       portptr = NULL;
@@ -584,11 +580,9 @@
     hostname++;
     hlen -= 2;
 
-    if(hostname[hlen] != ']')
-      return CURLUE_BAD_IPV6;
-
-    /* only valid letters are ok */
+    /* only valid IPv6 letters are ok */
     len = strspn(hostname, l);
+
     if(hlen != len) {
       hlen = len;
       if(hostname[len] == '%') {
@@ -602,8 +596,7 @@
         while(*h && (*h != ']') && (i < 15))
           zoneid[i++] = *h++;
         if(!i || (']' != *h))
-          /* impossible to reach? */
-          return CURLUE_MALFORMED_INPUT;
+          return CURLUE_BAD_IPV6;
         zoneid[i] = 0;
         u->zoneid = strdup(zoneid);
         if(!u->zoneid)
@@ -615,7 +608,8 @@
         return CURLUE_BAD_IPV6;
       /* hostname is fine */
     }
-#ifdef ENABLE_IPV6
+
+    /* Check the IPv6 address. */
     {
       char dest[16]; /* fits a binary IPv6 address */
       char norm[MAX_IPADR_LEN];
@@ -632,11 +626,10 @@
       }
       hostname[hlen] = ']'; /* restore ending bracket */
     }
-#endif
   }
   else {
     /* letters from the second string are not ok */
-    len = strcspn(hostname, " \r\n\t/:#?!@{}[]\\$\'\"^`*<>=;,+&()");
+    len = strcspn(hostname, " \r\n\t/:#?!@{}[]\\$\'\"^`*<>=;,+&()%");
     if(hlen != len)
       /* hostname with bad content */
       return CURLUE_BAD_HOSTNAME;
@@ -782,25 +775,28 @@
  *
  * RETURNS
  *
- * an allocated dedotdotified output string
+ * Zero for success and 'out' set to an allocated dedotdotified string.
  */
-UNITTEST char *dedotdotify(const char *input, size_t clen);
-UNITTEST char *dedotdotify(const char *input, size_t clen)
+UNITTEST int dedotdotify(const char *input, size_t clen, char **outp);
+UNITTEST int dedotdotify(const char *input, size_t clen, char **outp)
 {
-  char *out = malloc(clen + 1);
   char *outptr;
   const char *orginput = input;
   char *queryp;
+  char *out;
+
+  *outp = NULL;
+  /* the path always starts with a slash, and a slash has not dot */
+  if((clen < 2) || !memchr(input, '.', clen))
+    return 0;
+
+  out = malloc(clen + 1);
   if(!out)
-    return NULL; /* out of memory */
+    return 1; /* out of memory */
 
   *out = 0; /* null-terminates, for inputs like "./" */
   outptr = out;
 
-  if(!*input)
-    /* zero length input string, return that */
-    return out;
-
   /*
    * To handle query-parts properly, we must find it and remove it during the
    * dotdot-operation and then append it again at the end to the output
@@ -905,7 +901,8 @@
     memcpy(outptr, &orginput[oindex], qlen + 1); /* include zero byte */
   }
 
-  return out;
+  *outp = out;
+  return 0; /* success */
 }
 
 static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
@@ -1152,7 +1149,7 @@
     size_t qlen = strlen(query) - fraglen; /* includes '?' */
     pathlen = strlen(path) - qlen - fraglen;
     if(qlen > 1) {
-      if(qlen && (flags & CURLU_URLENCODE)) {
+      if(flags & CURLU_URLENCODE) {
         struct dynbuf enc;
         Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
         /* skip the leading question mark */
@@ -1199,8 +1196,8 @@
     path = u->path = Curl_dyn_ptr(&enc);
   }
 
-  if(!pathlen) {
-    /* there is no path left, unset */
+  if(pathlen <= 1) {
+    /* there is no path left or just the slash, unset */
     path = NULL;
   }
   else {
@@ -1224,13 +1221,16 @@
 
     if(!(flags & CURLU_PATH_AS_IS)) {
       /* remove ../ and ./ sequences according to RFC3986 */
-      char *newp = dedotdotify((char *)path, pathlen);
-      if(!newp) {
+      char *dedot;
+      int err = dedotdotify((char *)path, pathlen, &dedot);
+      if(err) {
         result = CURLUE_OUT_OF_MEMORY;
         goto fail;
       }
-      free(u->path);
-      u->path = newp;
+      if(dedot) {
+        free(u->path);
+        u->path = dedot;
+      }
     }
   }
 
@@ -1350,7 +1350,7 @@
     }                                           \
   } while(0)
 
-CURLU *curl_url_dup(CURLU *in)
+CURLU *curl_url_dup(const CURLU *in)
 {
   struct Curl_URL *u = calloc(sizeof(struct Curl_URL), 1);
   if(u) {
@@ -1371,14 +1371,15 @@
   return NULL;
 }
 
-CURLUcode curl_url_get(CURLU *u, CURLUPart what,
+CURLUcode curl_url_get(const CURLU *u, CURLUPart what,
                        char **part, unsigned int flags)
 {
-  char *ptr;
+  const char *ptr;
   CURLUcode ifmissing = CURLUE_UNKNOWN_PART;
   char portbuf[7];
   bool urldecode = (flags & CURLU_URLDECODE)?1:0;
   bool urlencode = (flags & CURLU_URLENCODE)?1:0;
+  bool punycode = FALSE;
   bool plusdecode = FALSE;
   (void)flags;
   if(!u)
@@ -1408,6 +1409,7 @@
   case CURLUPART_HOST:
     ptr = u->host;
     ifmissing = CURLUE_NO_HOST;
+    punycode = (flags & CURLU_PUNYCODE)?1:0;
     break;
   case CURLUPART_ZONEID:
     ptr = u->zoneid;
@@ -1439,11 +1441,8 @@
     break;
   case CURLUPART_PATH:
     ptr = u->path;
-    if(!ptr) {
-      ptr = u->path = strdup("/");
-      if(!u->path)
-        return CURLUE_OUT_OF_MEMORY;
-    }
+    if(!ptr)
+      ptr = "/";
     break;
   case CURLUPART_QUERY:
     ptr = u->query;
@@ -1460,6 +1459,7 @@
     char *options = u->options;
     char *port = u->port;
     char *allochost = NULL;
+    punycode = (flags & CURLU_PUNYCODE)?1:0;
     if(u->scheme && strcasecompare("file", u->scheme)) {
       url = aprintf("file://%s%s%s",
                     u->path,
@@ -1514,6 +1514,17 @@
         if(!allochost)
           return CURLUE_OUT_OF_MEMORY;
       }
+      else if(punycode) {
+        if(!Curl_is_ASCII_name(u->host)) {
+#ifndef USE_IDN
+          return CURLUE_LACKS_IDN;
+#else
+          allochost = Curl_idn_decode(u->host);
+          if(!allochost)
+            return CURLUE_OUT_OF_MEMORY;
+#endif
+        }
+      }
       else {
         /* only encode '%' in output host name */
         char *host = u->host;
@@ -1541,8 +1552,7 @@
               return CURLUE_OUT_OF_MEMORY;
             host++;
           }
-          free(u->host);
-          u->host = Curl_dyn_ptr(&enc);
+          allochost = Curl_dyn_ptr(&enc);
         }
       }
 
@@ -1611,6 +1621,19 @@
       free(*part);
       *part = Curl_dyn_ptr(&enc);
     }
+    else if(punycode) {
+      if(!Curl_is_ASCII_name(u->host)) {
+#ifndef USE_IDN
+        return CURLUE_LACKS_IDN;
+#else
+        char *allochost = Curl_idn_decode(*part);
+        if(!allochost)
+          return CURLUE_OUT_OF_MEMORY;
+        free(*part);
+        *part = allochost;
+#endif
+      }
+    }
 
     return CURLUE_OK;
   }
@@ -1807,7 +1830,10 @@
             return CURLUE_OUT_OF_MEMORY;
         }
         else {
-          result = Curl_dyn_addf(&enc, "%%%02x", *i);
+          char out[3]={'%'};
+          out[1] = hexdigits[*i>>4];
+          out[2] = hexdigits[*i & 0xf];
+          result = Curl_dyn_addn(&enc, out, 3);
           if(result)
             return CURLUE_OUT_OF_MEMORY;
         }
diff --git a/Utilities/cmcurl/lib/urldata.h b/Utilities/cmcurl/lib/urldata.h
index 3d7545c..8b54518 100644
--- a/Utilities/cmcurl/lib/urldata.h
+++ b/Utilities/cmcurl/lib/urldata.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -113,23 +113,6 @@
    input easier and better. */
 #define CURL_MAX_INPUT_LENGTH 8000000
 
-/* Macros intended for DEBUGF logging, use like:
- * DEBUGF(infof(data, CFMSG(cf, "this filter %s rocks"), "very much"));
- * and it will output:
- * [CONN-1-0][CF-SSL] this filter very much rocks
- * on connection #1 with sockindex 0 for filter of type "SSL". */
-#define DMSG(d,msg)  \
-  "[CONN-%ld] "msg, (d)->conn->connection_id
-#define DMSGI(d,i,msg)  \
-  "[CONN-%ld-%d] "msg, (d)->conn->connection_id, (i)
-#define CMSG(c,msg)  \
-  "[CONN-%ld] "msg, (conn)->connection_id
-#define CMSGI(c,i,msg)  \
-  "[CONN-%ld-%d] "msg, (conn)->connection_id, (i)
-#define CFMSG(cf,msg)  \
-  "[CONN-%ld-%d][CF-%s] "msg, (cf)->conn->connection_id, \
-  (cf)->sockindex, (cf)->cft->name
-
 
 #include "cookie.h"
 #include "psl.h"
@@ -185,10 +168,10 @@
 #include "rtsp.h"
 #include "smb.h"
 #include "mqtt.h"
-#include "wildcard.h"
+#include "ftplistparser.h"
 #include "multihandle.h"
-#include "quic.h"
 #include "c-hyper.h"
+#include "cf-socket.h"
 
 #ifdef HAVE_GSSAPI
 # ifdef HAVE_GSSGNU
@@ -268,8 +251,6 @@
 struct ssl_backend_data;
 
 struct ssl_primary_config {
-  long version;          /* what version the client wants to use */
-  long version_max;      /* max supported version the client wants to use */
   char *CApath;          /* certificate dir (doesn't work on windows) */
   char *CAfile;          /* certificate to verify peer against */
   char *issuercert;      /* optional issuer certificate filename */
@@ -284,10 +265,11 @@
 #ifdef USE_TLS_SRP
   char *username; /* TLS username (for, e.g., SRP) */
   char *password; /* TLS password (for, e.g., SRP) */
-  enum CURL_TLSAUTH authtype; /* TLS authentication type (default SRP) */
 #endif
   char *curves;          /* list of curves to use */
   unsigned char ssl_options;  /* the CURLOPT_SSL_OPTIONS bitmask */
+  unsigned int version_max; /* max supported version the client wants to use */
+  unsigned char version;    /* what version the client wants to use */
   BIT(verifypeer);       /* set TRUE if this is desired */
   BIT(verifyhost);       /* set TRUE if CN/SAN must match hostname */
   BIT(verifystatus);     /* set TRUE if certificate status must be checked */
@@ -648,7 +630,6 @@
   curl_off_t pendingheader;      /* this many bytes left to send is actually
                                     header and not body */
   struct curltime start;         /* transfer started at this time */
-  struct curltime now;           /* current time */
   enum {
     HEADER_NORMAL,              /* no bad header at all */
     HEADER_PARTHEADER,          /* part of the chunk is a bad header, the rest
@@ -706,7 +687,12 @@
 #ifndef CURL_DISABLE_DOH
   struct dohdata *doh; /* DoH specific data for this request */
 #endif
+#if defined(WIN32) && defined(USE_WINSOCK)
+  struct curltime last_sndbuf_update;  /* last time readwrite_upload called
+                                          win_update_buffer_size */
+#endif
   unsigned char setcookies;
+  unsigned char writer_stack_depth; /* Unencoding stack depth. */
   BIT(header);        /* incoming data has HTTP header */
   BIT(content_range); /* set TRUE if Content-Range: was found */
   BIT(upload_done);   /* set to TRUE when doing chunked transfer-encoding
@@ -783,8 +769,8 @@
   /* This function *MAY* be set to a protocol-dependent function that is run
    * by the curl_disconnect(), as a step in the disconnection.  If the handler
    * is called because the connection has been considered dead,
-   * dead_connection is set to TRUE. The connection is already disassociated
-   * from the transfer here.
+   * dead_connection is set to TRUE. The connection is (again) associated with
+   * the transfer here.
    */
   CURLcode (*disconnect)(struct Curl_easy *, struct connectdata *,
                          bool dead_connection);
@@ -830,7 +816,7 @@
 #define PROTOPT_CREDSPERREQUEST (1<<7) /* requires login credentials per
                                           request instead of per connection */
 #define PROTOPT_ALPN (1<<8) /* set ALPN for this */
-#define PROTOPT_STREAM (1<<9) /* a protocol with individual logical streams */
+/* (1<<9) was PROTOPT_STREAM, now free */
 #define PROTOPT_URLOPTIONS (1<<10) /* allow options part in the userinfo field
                                       of the URL */
 #define PROTOPT_PROXY_AS_HTTP (1<<11) /* allow this non-HTTP scheme over a
@@ -848,20 +834,6 @@
 #define CONNRESULT_NONE 0                /* No extra information. */
 #define CONNRESULT_DEAD (1<<0)           /* The connection is dead. */
 
-#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
-struct postponed_data {
-  char *buffer;          /* Temporal store for received data during
-                            sending, must be freed */
-  size_t allocated_size; /* Size of temporal store */
-  size_t recv_size;      /* Size of received data during sending */
-  size_t recv_processed; /* Size of processed part of postponed data */
-#ifdef DEBUGBUILD
-  curl_socket_t bindsock;/* Structure must be bound to specific socket,
-                            used only for DEBUGASSERT */
-#endif /* DEBUGBUILD */
-};
-#endif /* USE_RECV_BEFORE_SEND_WORKAROUND */
-
 struct proxy_info {
   struct hostname host;
   int port;
@@ -909,16 +881,9 @@
      there is no name resolve done. */
   struct Curl_dns_entry *dns_entry;
 
-  /* 'ip_addr' is the particular IP we connected to. It points to a struct
-     within the DNS cache, so this pointer is only valid as long as the DNS
-     cache entry remains locked. It gets unlocked in multi_done() */
-  struct Curl_addrinfo *ip_addr;
-  struct Curl_addrinfo *tempaddr[2]; /* for happy eyeballs */
-
-#ifdef ENABLE_QUIC
-  struct quicsocket hequic[2]; /* two, for happy eyeballs! */
-  struct quicsocket *quic;
-#endif
+  /* 'remote_addr' is the particular IP we connected to. it is owned, set
+   * and NULLed by the connected socket filter (if there is one). */
+  const struct Curl_sockaddr_ex *remote_addr;
 
   struct hostname host;
   char *hostname_resolve; /* host name to resolve to address, allocated */
@@ -947,31 +912,16 @@
   struct curltime lastused; /* when returned to the connection cache */
   curl_socket_t sock[2]; /* two sockets, the second is used for the data
                             transfer when doing FTP */
-  curl_socket_t tempsock[2]; /* temporary sockets for happy eyeballs */
-  int tempfamily[2]; /* family used for the temp sockets */
   Curl_recv *recv[2];
   Curl_send *send[2];
   struct Curl_cfilter *cfilter[2]; /* connection filters */
 
-#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
-  struct postponed_data postponed[2]; /* two buffers for two sockets */
-#endif /* USE_RECV_BEFORE_SEND_WORKAROUND */
   struct ssl_primary_config ssl_config;
 #ifndef CURL_DISABLE_PROXY
   struct ssl_primary_config proxy_ssl_config;
 #endif
   struct ConnectBits bits;    /* various state-flags for this connection */
 
- /* connecttime: when connect() is called on the current IP address. Used to
-    be able to track when to move on to try next IP - but only when the multi
-    interface is used. */
-  struct curltime connecttime;
-
-  /* The field below gets set in Curl_connecthost */
-  /* how long time in milliseconds to spend on trying to connect to each IP
-     address, per family */
-  timediff_t timeoutms_per_addr[2];
-
   const struct Curl_handler *handler; /* Connection's protocol handler */
   const struct Curl_handler *given;   /* The protocol first given */
 
@@ -1034,16 +984,15 @@
   struct negotiatedata proxyneg; /* state data for proxy Negotiate auth */
 #endif
 
+#ifndef CURL_DISABLE_HTTP
   /* for chunked-encoded trailer */
   struct dynbuf trailer;
+#endif
 
   union {
 #ifndef CURL_DISABLE_FTP
     struct ftp_conn ftpc;
 #endif
-#ifndef CURL_DISABLE_HTTP
-    struct http_conn httpc;
-#endif
 #ifdef USE_SSH
     struct ssh_conn sshc;
 #endif
@@ -1070,6 +1019,9 @@
 #ifndef CURL_DISABLE_MQTT
     struct mqtt_conn mqtt;
 #endif
+#ifdef USE_WEBSOCKETS
+    struct ws_conn ws;
+#endif
   } proto;
 
   struct connectbundle *bundle; /* The bundle we are member of */
@@ -1086,14 +1038,13 @@
      that subsequent bound-requested connections aren't accidentally re-using
      wrong connections. */
   char *localdev;
-  int localportrange;
+  unsigned short localportrange;
   int cselect_bits; /* bitmask of socket events */
   int waitfor;      /* current READ/WRITE bits to wait for */
 #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
   int socks5_gssapi_enctype;
 #endif
-  /* The field below gets set in Curl_connecthost */
-  int num_addr; /* number of addresses to try to connect to */
+  /* The field below gets set in connect.c:connecthost() */
   int port;        /* which port to use locally - to connect to */
   int remote_port; /* the remote port, not the proxy port! */
   int conn_to_port; /* the remote port to connect to. valid only if
@@ -1110,6 +1061,7 @@
   unsigned char ip_version; /* copied from the Curl_easy at creation time */
   unsigned char httpversion; /* the HTTP version*10 reported by the server */
   unsigned char connect_only;
+  unsigned char gssapi_delegation; /* inherited from set.gssapi_delegation */
 };
 
 /* The end of connectdata. */
@@ -1238,10 +1190,29 @@
                    should be RFC compliant */
 };
 
-struct Curl_http2_dep {
-  struct Curl_http2_dep *next;
+#ifdef USE_NGHTTP2
+struct Curl_data_prio_node {
+  struct Curl_data_prio_node *next;
   struct Curl_easy *data;
 };
+#endif
+
+/**
+ * Priority information for an easy handle in relation to others
+ * on the same connection.
+ * TODO: we need to adapt it to the new priority scheme as defined in RFC 9218
+ */
+struct Curl_data_priority {
+#ifdef USE_NGHTTP2
+  /* tree like dependencies only implemented in nghttp2 */
+  struct Curl_easy *parent;
+  struct Curl_data_prio_node *children;
+#endif
+  int weight;
+#ifdef USE_NGHTTP2
+  BIT(exclusive);
+#endif
+};
 
 /*
  * This struct is for holding data that was attempted to get sent to the user's
@@ -1270,6 +1241,7 @@
   EXPIRE_TOOFAST,
   EXPIRE_QUIC,
   EXPIRE_FTP_ACCEPT,
+  EXPIRE_ALPN_EYEBALLS,
   EXPIRE_LAST /* not an actual timer, used as a marker only */
 } expire_id;
 
@@ -1389,24 +1361,17 @@
   size_t drain; /* Increased when this stream has data to read, even if its
                    socket is not necessarily is readable. Decreased when
                    checked. */
+  struct Curl_data_priority priority; /* shallow copy of data->set */
 #endif
 
   curl_read_callback fread_func; /* read callback/function */
   void *in;                      /* CURLOPT_READDATA */
-#ifdef USE_HTTP2
-  struct Curl_easy *stream_depends_on;
-  int stream_weight;
-#endif
   CURLU *uh; /* URL handle for the current parsed URL */
   struct urlpieces up;
   unsigned char httpreq; /* Curl_HttpReq; what kind of HTTP request (if any)
                             is this */
   char *url;        /* work URL, copied from UserDefined */
   char *referer;    /* referer string */
-#ifndef CURL_DISABLE_COOKIES
-  struct curl_slist *cookielist; /* list of cookie files set by
-                                    curl_easy_setopt(COOKIEFILE) calls */
-#endif
   struct curl_slist *resolve; /* set to point to the set.resolve list when
                                  this should be dealt with in pretransfer */
 #ifndef CURL_DISABLE_HTTP
@@ -1414,7 +1379,7 @@
   struct dynbuf trailers_buf; /* a buffer containing the compiled trailing
                                  headers */
   struct Curl_llist httphdrs; /* received headers */
-  struct curl_header headerout; /* for external purposes */
+  struct curl_header headerout[2]; /* for external purposes */
   struct Curl_header_store *prevhead; /* the latest added header */
   trailers_state trailers_state; /* whether we are sending trailers
                                     and what stage are we at */
@@ -1471,7 +1436,6 @@
   BIT(done); /* set to FALSE when Curl_init_do() is called and set to TRUE
                 when multi_done() is called, to prevent multi_done() to get
                 invoked twice when the multi interface is used. */
-  BIT(stream_depends_e); /* set or don't set the Exclusive bit */
   BIT(previouslypending); /* this transfer WAS in the multi->pending queue */
   BIT(cookie_engine);
   BIT(prefer_ascii);   /* ASCII rather than binary */
@@ -1620,15 +1584,9 @@
   void *out;         /* CURLOPT_WRITEDATA */
   void *in_set;      /* CURLOPT_READDATA */
   void *writeheader; /* write the header to this if non-NULL */
-  unsigned short proxyport; /* If non-zero, use this port number by
-                               default. If the proxy string features a
-                               ":[port]" that one will override this. */
   unsigned short use_port; /* which port to use (when not using default) */
   unsigned long httpauth;  /* kind of HTTP authentication to use (bitmask) */
   unsigned long proxyauth; /* kind of proxy authentication to use (bitmask) */
-#ifndef CURL_DISABLE_PROXY
-  unsigned char socks5auth;/* kind of SOCKS5 authentication to use (bitmask) */
-#endif
   long maxredirs;    /* maximum no. of http(s) redirects to follow, set to -1
                         for infinity */
 
@@ -1638,8 +1596,9 @@
                                of strlen(), and then the data *may* be binary
                                (contain zero bytes) */
   unsigned short localport; /* local port number to bind to */
-  int localportrange; /* number of additional port numbers to test in case the
-                         'localport' one can't be bind()ed */
+  unsigned short localportrange; /* number of additional port numbers to test
+                                    in case the 'localport' one can't be
+                                    bind()ed */
   curl_write_callback fwrite_func;   /* function that stores the output */
   curl_write_callback fwrite_header; /* function that stores headers */
   curl_write_callback fwrite_rtp;    /* function that stores interleaved RTP */
@@ -1661,7 +1620,13 @@
   void *prereq_userp; /* pre-initial request user data */
 
   void *seek_client;    /* pointer to pass to the seek callback */
+#ifndef CURL_DISABLE_COOKIES
+  struct curl_slist *cookielist; /* list of cookie files set by
+                                    curl_easy_setopt(COOKIEFILE) calls */
+#endif
 #ifndef CURL_DISABLE_HSTS
+  struct curl_slist *hstslist; /* list of HSTS files set by
+                                  curl_easy_setopt(HSTS) calls */
   curl_hstsread_callback hsts_read;
   void *hsts_read_userp;
   curl_hstswrite_callback hsts_write;
@@ -1688,17 +1653,8 @@
                                 download */
   curl_off_t set_resume_from;  /* continue [ftp] transfer from here */
   struct curl_slist *headers; /* linked list of extra headers */
-  struct curl_slist *proxyheaders; /* linked list of extra CONNECT headers */
   struct curl_httppost *httppost;  /* linked list of old POST data */
   curl_mimepart mimepost;  /* MIME/POST data. */
-  struct curl_slist *quote;     /* after connection is established */
-  struct curl_slist *postquote; /* after the transfer */
-  struct curl_slist *prequote; /* before the transfer, after type */
-  struct curl_slist *source_quote;  /* 3rd party quote */
-  struct curl_slist *source_prequote;  /* in 3rd party transfer mode - before
-                                          the transfer on source host */
-  struct curl_slist *source_postquote; /* in 3rd party transfer mode - after
-                                          the transfer on source host */
 #ifndef CURL_DISABLE_TELNET
   struct curl_slist *telnet_options; /* linked list of telnet options */
 #endif
@@ -1708,13 +1664,18 @@
                                     the hostname and port to connect to */
   time_t timevalue;       /* what time to compare with */
   unsigned char timecondition; /* kind of time comparison: curl_TimeCond */
-  unsigned char proxytype; /* what kind of proxy: curl_proxytype */
   unsigned char method;   /* what kind of HTTP request: Curl_HttpReq */
   unsigned char httpwant; /* when non-zero, a specific HTTP version requested
                              to be used in the library's request(s) */
   struct ssl_config_data ssl;  /* user defined SSL stuff */
 #ifndef CURL_DISABLE_PROXY
   struct ssl_config_data proxy_ssl;  /* user defined SSL stuff for proxy */
+  struct curl_slist *proxyheaders; /* linked list of extra CONNECT headers */
+  unsigned short proxyport; /* If non-zero, use this port number by
+                               default. If the proxy string features a
+                               ":[port]" that one will override this. */
+  unsigned char proxytype; /* what kind of proxy: curl_proxytype */
+  unsigned char socks5auth;/* kind of SOCKS5 authentication to use (bitmask) */
 #endif
   struct ssl_general_config general_ssl; /* general user defined SSL stuff */
   int dns_cache_timeout; /* DNS cache timeout (seconds) */
@@ -1722,7 +1683,9 @@
   unsigned int upload_buffer_size; /* size of upload buffer to use,
                                       keep it >= CURL_MAX_WRITE_SIZE */
   void *private_data; /* application-private data */
+#ifndef CURL_DISABLE_HTTP
   struct curl_slist *http200aliases; /* linked list of aliases for http200 */
+#endif
   unsigned char ipver; /* the CURL_IPRESOLVE_* defines in the public header
                           file 0 - whatever, 1 - v2, 2 - v6 */
   curl_off_t max_filesize; /* Maximum file size to download */
@@ -1732,26 +1695,30 @@
   unsigned char ftp_ccc;   /* FTP CCC options: curl_ftpccc */
   unsigned int accepttimeout;   /* in milliseconds, 0 means no timeout */
 #endif
-  /* Desppie the name ftp_create_missing_dirs is for FTP(S) and SFTP
+#if !defined(CURL_DISABLE_FTP) || defined(USE_SSH)
+  struct curl_slist *quote;     /* after connection is established */
+  struct curl_slist *postquote; /* after the transfer */
+  struct curl_slist *prequote; /* before the transfer, after type */
+  /* Despite the name, ftp_create_missing_dirs is for FTP(S) and SFTP
      1 - create directories that don't exist
      2 - the same but also allow MKD to fail once
   */
   unsigned char ftp_create_missing_dirs;
+#endif
 #ifdef USE_LIBSSH2
   curl_sshhostkeycallback ssh_hostkeyfunc; /* hostkey check callback */
   void *ssh_hostkeyfunc_userp;         /* custom pointer to callback */
 #endif
-
+#ifdef USE_SSH
   curl_sshkeycallback ssh_keyfunc; /* key matching callback */
   void *ssh_keyfunc_userp;         /* custom pointer to callback */
+  int ssh_auth_types;    /* allowed SSH auth types */
+  unsigned int new_directory_perms; /* when creating remote dirs */
+#endif
 #ifndef CURL_DISABLE_NETRC
   unsigned char use_netrc;        /* enum CURL_NETRC_OPTION values  */
 #endif
-  curl_usessl use_ssl;   /* if AUTH TLS is to be attempted etc, for FTP or
-                            IMAP or POP3 or others! */
   unsigned int new_file_perms;      /* when creating remote files */
-  unsigned int new_directory_perms; /* when creating remote dirs */
-  int ssh_auth_types;    /* allowed SSH auth types */
   char *str[STRING_LAST]; /* array of strings, pointing to allocated memory */
   struct curl_blob *blobs[BLOB_LAST];
 #ifdef ENABLE_IPV6
@@ -1759,8 +1726,9 @@
 #endif
   curl_prot_t allowed_protocols;
   curl_prot_t redir_protocols;
+#ifndef CURL_DISABLE_MIME
   unsigned int mime_options;      /* Mime option flags. */
-
+#endif
 #ifndef CURL_DISABLE_RTSP
   void *rtp_out;     /* write RTP to this if non-NULL */
   /* Common RTSP header options */
@@ -1774,9 +1742,11 @@
   curl_fnmatch_callback fnmatch; /* callback to decide which file corresponds
                                     to pattern (e.g. if WILDCARDMATCH is on) */
   void *fnmatch_data;
+  void *wildcardptr;
 #endif
-  long gssapi_delegation; /* GSS-API credential delegation, see the
-                             documentation of CURLOPT_GSSAPI_DELEGATION */
+ /* GSS-API credential delegation, see the documentation of
+    CURLOPT_GSSAPI_DELEGATION */
+  unsigned char gssapi_delegation;
 
   int tcp_keepidle;     /* seconds in idle before sending keepalive probe */
   int tcp_keepintvl;    /* seconds between TCP keepalive probes */
@@ -1784,10 +1754,8 @@
   size_t maxconnects;    /* Max idle connections in the connection cache */
 
   long expect_100_timeout; /* in milliseconds */
-#ifdef USE_HTTP2
-  struct Curl_easy *stream_depends_on;
-  int stream_weight;
-  struct Curl_http2_dep *stream_dependents;
+#if defined(USE_HTTP2) || defined(USE_HTTP3)
+  struct Curl_data_priority priority;
 #endif
   curl_resolver_start_callback resolver_start; /* optional callback called
                                                   before resolver start */
@@ -1798,8 +1766,10 @@
   struct Curl_easy *dohfor; /* this is a DoH request for that transfer */
 #endif
   CURLU *uh; /* URL handle for the current parsed URL */
+#ifndef CURL_DISABLE_HTTP
   void *trailer_data; /* pointer to pass to trailer data callback */
   curl_trailer_callback trailer_callback; /* trailing data callback */
+#endif
   char keep_post;     /* keep POSTs as POSTs after a 30x request; each
                          bit represents a request, from 301 to 303 */
 #ifndef CURL_DISABLE_SMTP
@@ -1807,6 +1777,8 @@
   BIT(mail_rcpt_allowfails); /* allow RCPT TO command to fail for some
                                 recipients */
 #endif
+  unsigned char use_ssl;   /* if AUTH TLS is to be attempted etc, for FTP or
+                              IMAP or POP3 or others! (type: curl_usessl)*/
   unsigned char connect_only; /* make connection/request, then let
                                  application use the socket */
   BIT(is_fread_set); /* has read callback been set to non-NULL? */
@@ -1877,7 +1849,6 @@
   BIT(suppress_connect_headers); /* suppress proxy CONNECT response headers
                                     from user callbacks */
   BIT(dns_shuffle_addresses); /* whether to shuffle addresses before use */
-  BIT(stream_depends_e); /* set or don't set the Exclusive bit */
   BIT(haproxyprotocol); /* whether to send HAProxy PROXY protocol v1
                            header */
   BIT(abstract_unix_socket);
@@ -1969,7 +1940,7 @@
   struct UrlState state;       /* struct for fields used for state info and
                                   other dynamic purposes */
 #ifndef CURL_DISABLE_FTP
-  struct WildcardData wildcard; /* wildcard download state info */
+  struct WildcardData *wildcard; /* wildcard download state info */
 #endif
   struct PureInfo info;        /* stats, reports and info data */
   struct curl_tlssessioninfo tsi; /* Information about the TLS session, only
diff --git a/Utilities/cmcurl/lib/vauth/cleartext.c b/Utilities/cmcurl/lib/vauth/cleartext.c
index b82b171..c651fc5 100644
--- a/Utilities/cmcurl/lib/vauth/cleartext.c
+++ b/Utilities/cmcurl/lib/vauth/cleartext.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -28,7 +28,8 @@
 #include "curl_setup.h"
 
 #if !defined(CURL_DISABLE_IMAP) || !defined(CURL_DISABLE_SMTP) ||       \
-  !defined(CURL_DISABLE_POP3)
+  !defined(CURL_DISABLE_POP3) || \
+  (!defined(CURL_DISABLE_LDAP) && defined(USE_OPENLDAP))
 
 #include <curl/curl.h>
 #include "urldata.h"
diff --git a/Utilities/cmcurl/lib/vauth/cram.c b/Utilities/cmcurl/lib/vauth/cram.c
index 475d31b..5894ed4 100644
--- a/Utilities/cmcurl/lib/vauth/cram.c
+++ b/Utilities/cmcurl/lib/vauth/cram.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vauth/digest.c b/Utilities/cmcurl/lib/vauth/digest.c
index c81ce10..b7a0d92 100644
--- a/Utilities/cmcurl/lib/vauth/digest.c
+++ b/Utilities/cmcurl/lib/vauth/digest.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vauth/digest.h b/Utilities/cmcurl/lib/vauth/digest.h
index d785bdd..68fdb28 100644
--- a/Utilities/cmcurl/lib/vauth/digest.h
+++ b/Utilities/cmcurl/lib/vauth/digest.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vauth/digest_sspi.c b/Utilities/cmcurl/lib/vauth/digest_sspi.c
index 6c95a3e..8fb8669 100644
--- a/Utilities/cmcurl/lib/vauth/digest_sspi.c
+++ b/Utilities/cmcurl/lib/vauth/digest_sspi.c
@@ -5,8 +5,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2014 - 2016, Steve Holme, <steve_holme@hotmail.com>.
- * Copyright (C) 2015 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Steve Holme, <steve_holme@hotmail.com>.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vauth/gsasl.c b/Utilities/cmcurl/lib/vauth/gsasl.c
index a73c644..c7d0a8d 100644
--- a/Utilities/cmcurl/lib/vauth/gsasl.c
+++ b/Utilities/cmcurl/lib/vauth/gsasl.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2020 - 2022, Simon Josefsson, <simon@josefsson.org>, et al.
+ * Copyright (C) Simon Josefsson, <simon@josefsson.org>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vauth/krb5_gssapi.c b/Utilities/cmcurl/lib/vauth/krb5_gssapi.c
index bac7804..65eb3e1 100644
--- a/Utilities/cmcurl/lib/vauth/krb5_gssapi.c
+++ b/Utilities/cmcurl/lib/vauth/krb5_gssapi.c
@@ -5,8 +5,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2014 - 2019, Steve Holme, <steve_holme@hotmail.com>.
- * Copyright (C) 2015 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Steve Holme, <steve_holme@hotmail.com>.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vauth/krb5_sspi.c b/Utilities/cmcurl/lib/vauth/krb5_sspi.c
index 015bc66..c487149 100644
--- a/Utilities/cmcurl/lib/vauth/krb5_sspi.c
+++ b/Utilities/cmcurl/lib/vauth/krb5_sspi.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2014 - 2022, Steve Holme, <steve_holme@hotmail.com>.
+ * Copyright (C) Steve Holme, <steve_holme@hotmail.com>.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vauth/ntlm.c b/Utilities/cmcurl/lib/vauth/ntlm.c
index 0141e17..2a5d4a4 100644
--- a/Utilities/cmcurl/lib/vauth/ntlm.c
+++ b/Utilities/cmcurl/lib/vauth/ntlm.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vauth/ntlm.h b/Utilities/cmcurl/lib/vauth/ntlm.h
index 14ebba2..31ce921 100644
--- a/Utilities/cmcurl/lib/vauth/ntlm.h
+++ b/Utilities/cmcurl/lib/vauth/ntlm.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vauth/ntlm_sspi.c b/Utilities/cmcurl/lib/vauth/ntlm_sspi.c
index 193576a..5118963 100644
--- a/Utilities/cmcurl/lib/vauth/ntlm_sspi.c
+++ b/Utilities/cmcurl/lib/vauth/ntlm_sspi.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vauth/oauth2.c b/Utilities/cmcurl/lib/vauth/oauth2.c
index 1604b30..a4adbdc 100644
--- a/Utilities/cmcurl/lib/vauth/oauth2.c
+++ b/Utilities/cmcurl/lib/vauth/oauth2.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -27,7 +27,8 @@
 #include "curl_setup.h"
 
 #if !defined(CURL_DISABLE_IMAP) || !defined(CURL_DISABLE_SMTP) || \
-  !defined(CURL_DISABLE_POP3)
+  !defined(CURL_DISABLE_POP3) || \
+  (!defined(CURL_DISABLE_LDAP) && defined(USE_OPENLDAP))
 
 #include <curl/curl.h>
 #include "urldata.h"
diff --git a/Utilities/cmcurl/lib/vauth/spnego_gssapi.c b/Utilities/cmcurl/lib/vauth/spnego_gssapi.c
index 25dff96..e1d52b7 100644
--- a/Utilities/cmcurl/lib/vauth/spnego_gssapi.c
+++ b/Utilities/cmcurl/lib/vauth/spnego_gssapi.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vauth/spnego_sspi.c b/Utilities/cmcurl/lib/vauth/spnego_sspi.c
index d845cac..d3245d0 100644
--- a/Utilities/cmcurl/lib/vauth/spnego_sspi.c
+++ b/Utilities/cmcurl/lib/vauth/spnego_sspi.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vauth/vauth.c b/Utilities/cmcurl/lib/vauth/vauth.c
index 58fe051..62fc7c4 100644
--- a/Utilities/cmcurl/lib/vauth/vauth.c
+++ b/Utilities/cmcurl/lib/vauth/vauth.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2014 - 2022, Steve Holme, <steve_holme@hotmail.com>.
+ * Copyright (C) Steve Holme, <steve_holme@hotmail.com>.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vauth/vauth.h b/Utilities/cmcurl/lib/vauth/vauth.h
index c310c66..e17d7aa 100644
--- a/Utilities/cmcurl/lib/vauth/vauth.h
+++ b/Utilities/cmcurl/lib/vauth/vauth.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2014 - 2022, Steve Holme, <steve_holme@hotmail.com>.
+ * Copyright (C) Steve Holme, <steve_holme@hotmail.com>.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/version.c b/Utilities/cmcurl/lib/version.c
index c80841c..5800ad3 100644
--- a/Utilities/cmcurl/lib/version.c
+++ b/Utilities/cmcurl/lib/version.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -24,12 +24,16 @@
 
 #include "curl_setup.h"
 
+#ifdef USE_NGHTTP2
+#include <nghttp2/nghttp2.h>
+#endif
+
 #include <curl/curl.h>
 #include "urldata.h"
 #include "vtls/vtls.h"
 #include "http2.h"
 #include "vssh/ssh.h"
-#include "quic.h"
+#include "vquic/vquic.h"
 #include "curl_printf.h"
 #include "easy_lock.h"
 
@@ -58,7 +62,15 @@
 #endif
 
 #ifdef HAVE_BROTLI
+#if defined(__GNUC__)
+/* Ignore -Wvla warnings in brotli headers */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wvla"
+#endif
 #include <brotli/decode.h>
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
 #endif
 
 #ifdef HAVE_ZSTD
@@ -353,8 +365,7 @@
 #ifdef USE_SSH
   "sftp",
 #endif
-#if !defined(CURL_DISABLE_SMB) && defined(USE_CURL_NTLM_CORE) && \
-   (SIZEOF_CURL_OFF_T > 4)
+#if !defined(CURL_DISABLE_SMB) && defined(USE_CURL_NTLM_CORE)
   "smb",
 #  ifdef USE_SSL
   "smbs",
diff --git a/Utilities/cmcurl/lib/version_win32.c b/Utilities/cmcurl/lib/version_win32.c
index e8f14f9..872d5b4 100644
--- a/Utilities/cmcurl/lib/version_win32.c
+++ b/Utilities/cmcurl/lib/version_win32.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2016 - 2022, Steve Holme, <steve_holme@hotmail.com>.
+ * Copyright (C) Steve Holme, <steve_holme@hotmail.com>.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/version_win32.h b/Utilities/cmcurl/lib/version_win32.h
index 7a9a6a1..3899174 100644
--- a/Utilities/cmcurl/lib/version_win32.h
+++ b/Utilities/cmcurl/lib/version_win32.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2016 - 2022, Steve Holme, <steve_holme@hotmail.com>.
+ * Copyright (C) Steve Holme, <steve_holme@hotmail.com>.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vquic/curl_msh3.c b/Utilities/cmcurl/lib/vquic/curl_msh3.c
new file mode 100644
index 0000000..5308999
--- /dev/null
+++ b/Utilities/cmcurl/lib/vquic/curl_msh3.c
@@ -0,0 +1,850 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef USE_MSH3
+
+#include "urldata.h"
+#include "timeval.h"
+#include "multiif.h"
+#include "sendf.h"
+#include "curl_log.h"
+#include "cfilters.h"
+#include "cf-socket.h"
+#include "connect.h"
+#include "progress.h"
+#include "h2h3.h"
+#include "curl_msh3.h"
+#include "socketpair.h"
+#include "vquic/vquic.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+#define DEBUG_CF 1
+
+#if DEBUG_CF && defined(DEBUGBUILD)
+#define CF_DEBUGF(x) x
+#else
+#define CF_DEBUGF(x) do { } while(0)
+#endif
+
+#define MSH3_REQ_INIT_BUF_LEN 16384
+#define MSH3_REQ_MAX_BUF_LEN 0x100000
+
+#ifdef _WIN32
+#define msh3_lock CRITICAL_SECTION
+#define msh3_lock_initialize(lock) InitializeCriticalSection(lock)
+#define msh3_lock_uninitialize(lock) DeleteCriticalSection(lock)
+#define msh3_lock_acquire(lock) EnterCriticalSection(lock)
+#define msh3_lock_release(lock) LeaveCriticalSection(lock)
+#else /* !_WIN32 */
+#include <pthread.h>
+#define msh3_lock pthread_mutex_t
+#define msh3_lock_initialize(lock) do { \
+  pthread_mutexattr_t attr; \
+  pthread_mutexattr_init(&attr); \
+  pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); \
+  pthread_mutex_init(lock, &attr); \
+  pthread_mutexattr_destroy(&attr); \
+}while(0)
+#define msh3_lock_uninitialize(lock) pthread_mutex_destroy(lock)
+#define msh3_lock_acquire(lock) pthread_mutex_lock(lock)
+#define msh3_lock_release(lock) pthread_mutex_unlock(lock)
+#endif /* _WIN32 */
+
+
+static void MSH3_CALL msh3_conn_connected(MSH3_CONNECTION *Connection,
+                                          void *IfContext);
+static void MSH3_CALL msh3_conn_shutdown_complete(MSH3_CONNECTION *Connection,
+                                          void *IfContext);
+static void MSH3_CALL msh3_conn_new_request(MSH3_CONNECTION *Connection,
+                                          void *IfContext,
+                                          MSH3_REQUEST *Request);
+static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request,
+                                           void *IfContext,
+                                           const MSH3_HEADER *Header);
+static bool MSH3_CALL msh3_data_received(MSH3_REQUEST *Request,
+                                        void *IfContext, uint32_t *Length,
+                                        const uint8_t *Data);
+static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext,
+                                    bool Aborted, uint64_t AbortError);
+static void MSH3_CALL msh3_shutdown_complete(MSH3_REQUEST *Request,
+                                             void *IfContext);
+static void MSH3_CALL msh3_data_sent(MSH3_REQUEST *Request,
+                                     void *IfContext, void *SendContext);
+
+
+void Curl_msh3_ver(char *p, size_t len)
+{
+  uint32_t v[4];
+  MsH3Version(v);
+  (void)msnprintf(p, len, "msh3/%d.%d.%d.%d", v[0], v[1], v[2], v[3]);
+}
+
+#define SP_LOCAL   0
+#define SP_REMOTE  1
+
+struct cf_msh3_ctx {
+  MSH3_API *api;
+  MSH3_CONNECTION *qconn;
+  struct Curl_sockaddr_ex addr;
+  curl_socket_t sock[2]; /* fake socket pair until we get support in msh3 */
+  char l_ip[MAX_IPADR_LEN];          /* local IP as string */
+  int l_port;                        /* local port number */
+  struct curltime connect_started;   /* time the current attempt started */
+  struct curltime handshake_at;      /* time connect handshake finished */
+  /* Flags written by msh3/msquic thread */
+  bool handshake_complete;
+  bool handshake_succeeded;
+  bool connected;
+  /* Flags written by curl thread */
+  BIT(verbose);
+  BIT(active);
+};
+
+static const MSH3_CONNECTION_IF msh3_conn_if = {
+  msh3_conn_connected,
+  msh3_conn_shutdown_complete,
+  msh3_conn_new_request
+};
+
+static void MSH3_CALL msh3_conn_connected(MSH3_CONNECTION *Connection,
+                                          void *IfContext)
+{
+  struct cf_msh3_ctx *ctx = IfContext;
+  (void)Connection;
+  if(ctx->verbose)
+    CF_DEBUGF(fprintf(stderr, "* [MSH3] evt: connected\n"));
+  ctx->handshake_succeeded = true;
+  ctx->connected = true;
+  ctx->handshake_complete = true;
+}
+
+static void MSH3_CALL msh3_conn_shutdown_complete(MSH3_CONNECTION *Connection,
+                                          void *IfContext)
+{
+  struct cf_msh3_ctx *ctx = IfContext;
+  (void)Connection;
+  if(ctx->verbose)
+    CF_DEBUGF(fprintf(stderr, "* [MSH3] evt: shutdown complete\n"));
+  ctx->connected = false;
+  ctx->handshake_complete = true;
+}
+
+static void MSH3_CALL msh3_conn_new_request(MSH3_CONNECTION *Connection,
+                                          void *IfContext,
+                                          MSH3_REQUEST *Request)
+{
+  (void)Connection;
+  (void)IfContext;
+  (void)Request;
+}
+
+static const MSH3_REQUEST_IF msh3_request_if = {
+  msh3_header_received,
+  msh3_data_received,
+  msh3_complete,
+  msh3_shutdown_complete,
+  msh3_data_sent
+};
+
+static CURLcode msh3_data_setup(struct Curl_cfilter *cf,
+                                struct Curl_easy *data)
+{
+  struct HTTP *stream = data->req.p.http;
+  (void)cf;
+
+  DEBUGASSERT(stream);
+  if(!stream->recv_buf) {
+    DEBUGF(LOG_CF(data, cf, "req: setup"));
+    stream->recv_buf = malloc(MSH3_REQ_INIT_BUF_LEN);
+    if(!stream->recv_buf) {
+      return CURLE_OUT_OF_MEMORY;
+    }
+    stream->req = ZERO_NULL;
+    msh3_lock_initialize(&stream->recv_lock);
+    stream->recv_buf_alloc = MSH3_REQ_INIT_BUF_LEN;
+    stream->recv_buf_max = MSH3_REQ_MAX_BUF_LEN;
+    stream->recv_header_len = 0;
+    stream->recv_header_complete = false;
+    stream->recv_data_len = 0;
+    stream->recv_data_complete = false;
+    stream->recv_error = CURLE_OK;
+  }
+  return CURLE_OK;
+}
+
+/* Requires stream->recv_lock to be held */
+static bool msh3request_ensure_room(struct HTTP *stream, size_t len)
+{
+  uint8_t *new_recv_buf;
+  const size_t cur_recv_len = stream->recv_header_len + stream->recv_data_len;
+
+  if(cur_recv_len + len > stream->recv_buf_alloc) {
+    size_t new_recv_buf_alloc_len = stream->recv_buf_alloc;
+    do {
+      new_recv_buf_alloc_len <<= 1; /* TODO - handle overflow */
+    } while(cur_recv_len + len > new_recv_buf_alloc_len);
+    CF_DEBUGF(fprintf(stderr, "* enlarging buffer to %zu\n",
+              new_recv_buf_alloc_len));
+    new_recv_buf = malloc(new_recv_buf_alloc_len);
+    if(!new_recv_buf) {
+      CF_DEBUGF(fprintf(stderr, "* FAILED: enlarging buffer to %zu\n",
+                new_recv_buf_alloc_len));
+      return false;
+    }
+    if(cur_recv_len) {
+      memcpy(new_recv_buf, stream->recv_buf, cur_recv_len);
+    }
+    stream->recv_buf_alloc = new_recv_buf_alloc_len;
+    free(stream->recv_buf);
+    stream->recv_buf = new_recv_buf;
+  }
+  return true;
+}
+
+static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request,
+                                           void *IfContext,
+                                           const MSH3_HEADER *Header)
+{
+  struct Curl_easy *data = IfContext;
+  struct HTTP *stream = data->req.p.http;
+  size_t total_len;
+  (void)Request;
+
+  if(stream->recv_header_complete) {
+    CF_DEBUGF(fprintf(stderr, "* ignoring header after data\n"));
+    return;
+  }
+
+  msh3_lock_acquire(&stream->recv_lock);
+
+  if((Header->NameLength == 7) &&
+     !strncmp(H2H3_PSEUDO_STATUS, (char *)Header->Name, 7)) {
+    total_len = 10 + Header->ValueLength;
+    if(!msh3request_ensure_room(stream, total_len)) {
+      CF_DEBUGF(fprintf(stderr, "* ERROR: unable to buffer: %.*s\n",
+                (int)Header->NameLength, Header->Name));
+      stream->recv_error = CURLE_OUT_OF_MEMORY;
+      goto release_lock;
+    }
+    msnprintf((char *)stream->recv_buf + stream->recv_header_len,
+              stream->recv_buf_alloc - stream->recv_header_len,
+              "HTTP/3 %.*s \r\n", (int)Header->ValueLength, Header->Value);
+  }
+  else {
+    total_len = 4 + Header->NameLength + Header->ValueLength;
+    if(!msh3request_ensure_room(stream, total_len)) {
+      CF_DEBUGF(fprintf(stderr, "* ERROR: unable to buffer: %.*s\n",
+                (int)Header->NameLength, Header->Name));
+      stream->recv_error = CURLE_OUT_OF_MEMORY;
+      goto release_lock;
+    }
+    msnprintf((char *)stream->recv_buf + stream->recv_header_len,
+              stream->recv_buf_alloc - stream->recv_header_len,
+              "%.*s: %.*s\r\n",
+              (int)Header->NameLength, Header->Name,
+              (int)Header->ValueLength, Header->Value);
+  }
+
+  stream->recv_header_len += total_len;
+  data->state.drain = 1;
+
+release_lock:
+  msh3_lock_release(&stream->recv_lock);
+}
+
+static bool MSH3_CALL msh3_data_received(MSH3_REQUEST *Request,
+                                         void *IfContext, uint32_t *Length,
+                                         const uint8_t *Data)
+{
+  struct Curl_easy *data = IfContext;
+  struct HTTP *stream = data->req.p.http;
+  size_t cur_recv_len = stream->recv_header_len + stream->recv_data_len;
+
+  (void)Request;
+  if(data && data->set.verbose)
+    CF_DEBUGF(fprintf(stderr, "* [MSH3] req: evt: received %u. %zu buffered, "
+              "%zu allocated\n",
+              *Length, cur_recv_len, stream->recv_buf_alloc));
+  /* TODO - Update this code to limit data bufferring by `stream->recv_buf_max`
+     and return `false` when we reach that limit. Then, when curl drains some
+     of the buffer, making room, call MsH3RequestSetReceiveEnabled to enable
+     receive callbacks again. */
+  msh3_lock_acquire(&stream->recv_lock);
+
+  if(!stream->recv_header_complete) {
+    if(data && data->set.verbose)
+      CF_DEBUGF(fprintf(stderr, "* [MSH3] req: Headers complete!\n"));
+    if(!msh3request_ensure_room(stream, 2)) {
+      stream->recv_error = CURLE_OUT_OF_MEMORY;
+      goto release_lock;
+    }
+    stream->recv_buf[stream->recv_header_len++] = '\r';
+    stream->recv_buf[stream->recv_header_len++] = '\n';
+    stream->recv_header_complete = true;
+    cur_recv_len += 2;
+  }
+  if(!msh3request_ensure_room(stream, *Length)) {
+    stream->recv_error = CURLE_OUT_OF_MEMORY;
+    goto release_lock;
+  }
+  memcpy(stream->recv_buf + cur_recv_len, Data, *Length);
+  stream->recv_data_len += (size_t)*Length;
+  data->state.drain = 1;
+
+release_lock:
+  msh3_lock_release(&stream->recv_lock);
+  return true;
+}
+
+static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext,
+                                    bool Aborted, uint64_t AbortError)
+{
+  struct Curl_easy *data = IfContext;
+  struct HTTP *stream = data->req.p.http;
+
+  (void)Request;
+  (void)AbortError;
+  if(data && data->set.verbose)
+    CF_DEBUGF(fprintf(stderr, "* [MSH3] req: evt: complete, aborted=%s\n",
+              Aborted ? "true" : "false"));
+  msh3_lock_acquire(&stream->recv_lock);
+  if(Aborted) {
+    stream->recv_error = CURLE_HTTP3; /* TODO - how do we pass AbortError? */
+  }
+  stream->recv_header_complete = true;
+  stream->recv_data_complete = true;
+  msh3_lock_release(&stream->recv_lock);
+}
+
+static void MSH3_CALL msh3_shutdown_complete(MSH3_REQUEST *Request,
+                                             void *IfContext)
+{
+  struct Curl_easy *data = IfContext;
+  struct HTTP *stream = data->req.p.http;
+  (void)Request;
+  (void)stream;
+}
+
+static void MSH3_CALL msh3_data_sent(MSH3_REQUEST *Request,
+                                     void *IfContext, void *SendContext)
+{
+  struct Curl_easy *data = IfContext;
+  struct HTTP *stream = data->req.p.http;
+  (void)Request;
+  (void)stream;
+  (void)SendContext;
+}
+
+static ssize_t cf_msh3_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
+                            char *buf, size_t len, CURLcode *err)
+{
+  struct HTTP *stream = data->req.p.http;
+  size_t outsize = 0;
+
+  (void)cf;
+  DEBUGF(LOG_CF(data, cf, "req: recv with %zu byte buffer", len));
+
+  if(stream->recv_error) {
+    failf(data, "request aborted");
+    data->state.drain = 0;
+    *err = stream->recv_error;
+    return -1;
+  }
+
+  *err = CURLE_OK;
+  msh3_lock_acquire(&stream->recv_lock);
+
+  if(stream->recv_header_len) {
+    outsize = len;
+    if(stream->recv_header_len < outsize) {
+      outsize = stream->recv_header_len;
+    }
+    memcpy(buf, stream->recv_buf, outsize);
+    if(outsize < stream->recv_header_len + stream->recv_data_len) {
+      memmove(stream->recv_buf, stream->recv_buf + outsize,
+              stream->recv_header_len + stream->recv_data_len - outsize);
+    }
+    stream->recv_header_len -= outsize;
+    DEBUGF(LOG_CF(data, cf, "req: returned %zu bytes of header", outsize));
+  }
+  else if(stream->recv_data_len) {
+    outsize = len;
+    if(stream->recv_data_len < outsize) {
+      outsize = stream->recv_data_len;
+    }
+    memcpy(buf, stream->recv_buf, outsize);
+    if(outsize < stream->recv_data_len) {
+      memmove(stream->recv_buf, stream->recv_buf + outsize,
+              stream->recv_data_len - outsize);
+    }
+    stream->recv_data_len -= outsize;
+    DEBUGF(LOG_CF(data, cf, "req: returned %zu bytes of data", outsize));
+    if(stream->recv_data_len == 0 && stream->recv_data_complete)
+      data->state.drain = 1;
+  }
+  else if(stream->recv_data_complete) {
+    DEBUGF(LOG_CF(data, cf, "req: receive complete"));
+    data->state.drain = 0;
+  }
+  else {
+    DEBUGF(LOG_CF(data, cf, "req: nothing here, call again"));
+    *err = CURLE_AGAIN;
+    outsize = -1;
+  }
+
+  msh3_lock_release(&stream->recv_lock);
+
+  return (ssize_t)outsize;
+}
+
+static ssize_t cf_msh3_send(struct Curl_cfilter *cf, struct Curl_easy *data,
+                            const void *buf, size_t len, CURLcode *err)
+{
+  struct cf_msh3_ctx *ctx = cf->ctx;
+  struct HTTP *stream = data->req.p.http;
+  struct h2h3req *hreq;
+  size_t hdrlen = 0;
+  size_t sentlen = 0;
+
+  /* Sizes must match for cast below to work" */
+  DEBUGASSERT(sizeof(MSH3_HEADER) == sizeof(struct h2h3pseudo));
+  DEBUGF(LOG_CF(data, cf, "req: send %zu bytes", len));
+
+  if(!stream->req) {
+    /* The first send on the request contains the headers and possibly some
+       data. Parse out the headers and create the request, then if there is
+       any data left over go ahead and send it too. */
+
+    *err = msh3_data_setup(cf, data);
+    if(*err) {
+      failf(data, "could not setup data");
+      return -1;
+    }
+
+    *err = Curl_pseudo_headers(data, buf, len, &hdrlen, &hreq);
+    if(*err) {
+      failf(data, "Curl_pseudo_headers failed");
+      return -1;
+    }
+
+    DEBUGF(LOG_CF(data, cf, "req: send %zu headers", hreq->entries));
+    stream->req = MsH3RequestOpen(ctx->qconn, &msh3_request_if, data,
+                                  (MSH3_HEADER*)hreq->header, hreq->entries,
+                                  hdrlen == len ? MSH3_REQUEST_FLAG_FIN :
+                                  MSH3_REQUEST_FLAG_NONE);
+    Curl_pseudo_free(hreq);
+    if(!stream->req) {
+      failf(data, "request open failed");
+      *err = CURLE_SEND_ERROR;
+      return -1;
+    }
+    *err = CURLE_OK;
+    return len;
+  }
+
+  DEBUGF(LOG_CF(data, cf, "req: send %zd body bytes", len));
+  if(len > 0xFFFFFFFF) {
+    /* msh3 doesn't support size_t sends currently. */
+    *err = CURLE_SEND_ERROR;
+    return -1;
+  }
+
+  /* TODO - Need an explicit signal to know when to FIN. */
+  if(!MsH3RequestSend(stream->req, MSH3_REQUEST_FLAG_FIN, buf, (uint32_t)len,
+                      stream)) {
+    *err = CURLE_SEND_ERROR;
+    return -1;
+  }
+
+  /* TODO - msh3/msquic will hold onto this memory until the send complete
+     event. How do we make sure curl doesn't free it until then? */
+  sentlen += len;
+  *err = CURLE_OK;
+  return sentlen;
+}
+
+static int cf_msh3_get_select_socks(struct Curl_cfilter *cf,
+                                    struct Curl_easy *data,
+                                    curl_socket_t *socks)
+{
+  struct cf_msh3_ctx *ctx = cf->ctx;
+  struct HTTP *stream = data->req.p.http;
+  int bitmap = GETSOCK_BLANK;
+
+  if(stream && ctx->sock[SP_LOCAL] != CURL_SOCKET_BAD) {
+    socks[0] = ctx->sock[SP_LOCAL];
+
+    if(stream->recv_error) {
+      bitmap |= GETSOCK_READSOCK(0);
+      data->state.drain = 1;
+    }
+    else if(stream->recv_header_len || stream->recv_data_len) {
+      bitmap |= GETSOCK_READSOCK(0);
+      data->state.drain = 1;
+    }
+  }
+  DEBUGF(LOG_CF(data, cf, "select_sock %u -> %d",
+                (uint32_t)data->state.drain, bitmap));
+
+  return bitmap;
+}
+
+static bool cf_msh3_data_pending(struct Curl_cfilter *cf,
+                                 const struct Curl_easy *data)
+{
+  struct HTTP *stream = data->req.p.http;
+
+  (void)cf;
+  DEBUGF(LOG_CF((struct Curl_easy *)data, cf, "data pending = %hhu",
+                (bool)(stream->recv_header_len || stream->recv_data_len)));
+  return stream->recv_header_len || stream->recv_data_len;
+}
+
+static void cf_msh3_active(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_msh3_ctx *ctx = cf->ctx;
+
+  /* use this socket from now on */
+  cf->conn->sock[cf->sockindex] = ctx->sock[SP_LOCAL];
+  /* the first socket info gets set at conn and data */
+  if(cf->sockindex == FIRSTSOCKET) {
+    cf->conn->remote_addr = &ctx->addr;
+  #ifdef ENABLE_IPV6
+    cf->conn->bits.ipv6 = (ctx->addr.family == AF_INET6)? TRUE : FALSE;
+  #endif
+    Curl_persistconninfo(data, cf->conn, ctx->l_ip, ctx->l_port);
+  }
+  ctx->active = TRUE;
+}
+
+static CURLcode cf_msh3_data_event(struct Curl_cfilter *cf,
+                                   struct Curl_easy *data,
+                                   int event, int arg1, void *arg2)
+{
+  struct HTTP *stream = data->req.p.http;
+  CURLcode result = CURLE_OK;
+
+  (void)arg1;
+  (void)arg2;
+  switch(event) {
+  case CF_CTRL_DATA_SETUP:
+    result = msh3_data_setup(cf, data);
+    break;
+  case CF_CTRL_DATA_DONE:
+    DEBUGF(LOG_CF(data, cf, "req: done"));
+    if(stream) {
+      if(stream->recv_buf) {
+        Curl_safefree(stream->recv_buf);
+        msh3_lock_uninitialize(&stream->recv_lock);
+      }
+      if(stream->req) {
+        MsH3RequestClose(stream->req);
+        stream->req = ZERO_NULL;
+      }
+    }
+    break;
+  case CF_CTRL_DATA_DONE_SEND:
+    DEBUGF(LOG_CF(data, cf, "req: send done"));
+    stream->upload_done = TRUE;
+    break;
+  case CF_CTRL_CONN_INFO_UPDATE:
+    DEBUGF(LOG_CF(data, cf, "req: update info"));
+    cf_msh3_active(cf, data);
+    break;
+  default:
+    break;
+  }
+  return result;
+}
+
+static CURLcode cf_connect_start(struct Curl_cfilter *cf,
+                                 struct Curl_easy *data)
+{
+  struct cf_msh3_ctx *ctx = cf->ctx;
+  bool verify = !!cf->conn->ssl_config.verifypeer;
+  MSH3_ADDR addr = {0};
+  memcpy(&addr, &ctx->addr.sa_addr, ctx->addr.addrlen);
+  MSH3_SET_PORT(&addr, (uint16_t)cf->conn->remote_port);
+  ctx->verbose = (data && data->set.verbose);
+
+  if(verify && (cf->conn->ssl_config.CAfile || cf->conn->ssl_config.CApath)) {
+    /* TODO: need a way to provide trust anchors to MSH3 */
+#ifdef DEBUGBUILD
+    /* we need this for our test cases to run */
+    DEBUGF(LOG_CF(data, cf, "non-standard CA not supported, "
+                  "switching off verifypeer in DEBUG mode"));
+    verify = 0;
+#else
+    DEBUGF(LOG_CF(data, cf, "non-standard CA not supported, "
+                  "attempting with built-in verification"));
+#endif
+  }
+
+  DEBUGF(LOG_CF(data, cf, "connecting to %s:%d (verify=%d)",
+                cf->conn->host.name, (int)cf->conn->remote_port, verify));
+
+  ctx->api = MsH3ApiOpen();
+  if(!ctx->api) {
+    failf(data, "can't create msh3 api");
+    return CURLE_FAILED_INIT;
+  }
+
+  ctx->qconn = MsH3ConnectionOpen(ctx->api,
+                                  &msh3_conn_if,
+                                  ctx,
+                                  cf->conn->host.name,
+                                  &addr,
+                                  !verify);
+  if(!ctx->qconn) {
+    failf(data, "can't create msh3 connection");
+    if(ctx->api) {
+      MsH3ApiClose(ctx->api);
+      ctx->api = NULL;
+    }
+    return CURLE_FAILED_INIT;
+  }
+
+  return CURLE_OK;
+}
+
+static CURLcode cf_msh3_connect(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                bool blocking, bool *done)
+{
+  struct cf_msh3_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OK;
+
+  (void)blocking;
+  if(cf->connected) {
+    *done = TRUE;
+    return CURLE_OK;
+  }
+
+  if(ctx->sock[SP_LOCAL] == CURL_SOCKET_BAD) {
+    if(Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx->sock[0]) < 0) {
+      ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD;
+      ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD;
+      return CURLE_COULDNT_CONNECT;
+    }
+  }
+
+  *done = FALSE;
+  if(!ctx->qconn) {
+    ctx->connect_started = Curl_now();
+    result = cf_connect_start(cf, data);
+    if(result)
+      goto out;
+  }
+
+  if(ctx->handshake_complete) {
+    ctx->handshake_at = Curl_now();
+    if(ctx->handshake_succeeded) {
+      cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
+      cf->conn->httpversion = 30;
+      cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX;
+      cf->connected = TRUE;
+      cf->conn->alpn = CURL_HTTP_VERSION_3;
+      *done = TRUE;
+      connkeep(cf->conn, "HTTP/3 default");
+      Curl_pgrsTime(data, TIMER_APPCONNECT);
+    }
+    else {
+      failf(data, "failed to connect, handshake failed");
+      result = CURLE_COULDNT_CONNECT;
+    }
+  }
+
+out:
+  return result;
+}
+
+static void cf_msh3_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_msh3_ctx *ctx = cf->ctx;
+
+  (void)data;
+  if(ctx) {
+    DEBUGF(LOG_CF(data, cf, "destroying"));
+    if(ctx->qconn)
+      MsH3ConnectionClose(ctx->qconn);
+    if(ctx->api)
+      MsH3ApiClose(ctx->api);
+
+    if(ctx->active) {
+      /* We share our socket at cf->conn->sock[cf->sockindex] when active.
+       * If it is no longer there, someone has stolen (and hopefully
+       * closed it) and we just forget about it.
+       */
+      if(ctx->sock[SP_LOCAL] == cf->conn->sock[cf->sockindex]) {
+        DEBUGF(LOG_CF(data, cf, "cf_msh3_close(%d) active",
+                      (int)ctx->sock[SP_LOCAL]));
+        cf->conn->sock[cf->sockindex] = CURL_SOCKET_BAD;
+      }
+      else {
+        DEBUGF(LOG_CF(data, cf, "cf_socket_close(%d) no longer at "
+                      "conn->sock[], discarding", (int)ctx->sock[SP_LOCAL]));
+        ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD;
+      }
+      if(cf->sockindex == FIRSTSOCKET)
+        cf->conn->remote_addr = NULL;
+    }
+    if(ctx->sock[SP_LOCAL] != CURL_SOCKET_BAD) {
+      sclose(ctx->sock[SP_LOCAL]);
+    }
+    if(ctx->sock[SP_REMOTE] != CURL_SOCKET_BAD) {
+      sclose(ctx->sock[SP_REMOTE]);
+    }
+    memset(ctx, 0, sizeof(*ctx));
+    ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD;
+    ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD;
+  }
+}
+
+static void cf_msh3_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  cf_msh3_close(cf, data);
+  free(cf->ctx);
+  cf->ctx = NULL;
+}
+
+static CURLcode cf_msh3_query(struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
+                              int query, int *pres1, void *pres2)
+{
+  struct cf_msh3_ctx *ctx = cf->ctx;
+
+  switch(query) {
+  case CF_QUERY_MAX_CONCURRENT: {
+    /* TODO: we do not have access to this so far, fake it */
+    (void)ctx;
+    *pres1 = 100;
+    return CURLE_OK;
+  }
+  case CF_QUERY_TIMER_CONNECT: {
+    struct curltime *when = pres2;
+    /* we do not know when the first byte arrived */
+    if(cf->connected)
+      *when = ctx->handshake_at;
+    return CURLE_OK;
+  }
+  case CF_QUERY_TIMER_APPCONNECT: {
+    struct curltime *when = pres2;
+    if(cf->connected)
+      *when = ctx->handshake_at;
+    return CURLE_OK;
+  }
+  default:
+    break;
+  }
+  return cf->next?
+    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
+    CURLE_UNKNOWN_OPTION;
+}
+
+static bool cf_msh3_conn_is_alive(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  bool *input_pending)
+{
+  struct cf_msh3_ctx *ctx = cf->ctx;
+
+  (void)data;
+  *input_pending = FALSE;
+  return ctx && ctx->sock[SP_LOCAL] != CURL_SOCKET_BAD && ctx->qconn &&
+         ctx->connected;
+}
+
+struct Curl_cftype Curl_cft_http3 = {
+  "HTTP/3",
+  CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX,
+  0,
+  cf_msh3_destroy,
+  cf_msh3_connect,
+  cf_msh3_close,
+  Curl_cf_def_get_host,
+  cf_msh3_get_select_socks,
+  cf_msh3_data_pending,
+  cf_msh3_send,
+  cf_msh3_recv,
+  cf_msh3_data_event,
+  cf_msh3_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  cf_msh3_query,
+};
+
+CURLcode Curl_cf_msh3_create(struct Curl_cfilter **pcf,
+                             struct Curl_easy *data,
+                             struct connectdata *conn,
+                             const struct Curl_addrinfo *ai)
+{
+  struct cf_msh3_ctx *ctx = NULL;
+  struct Curl_cfilter *cf = NULL;
+  CURLcode result;
+
+  (void)data;
+  (void)conn;
+  (void)ai; /* TODO: msh3 resolves itself? */
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+  Curl_sock_assign_addr(&ctx->addr, ai, TRNSPRT_QUIC);
+  ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD;
+  ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD;
+
+  result = Curl_cf_create(&cf, &Curl_cft_http3, ctx);
+
+out:
+  *pcf = (!result)? cf : NULL;
+  if(result) {
+    Curl_safefree(cf);
+    Curl_safefree(ctx);
+  }
+
+  return result;
+}
+
+bool Curl_conn_is_msh3(const struct Curl_easy *data,
+                       const struct connectdata *conn,
+                       int sockindex)
+{
+  struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL;
+
+  (void)data;
+  for(; cf; cf = cf->next) {
+    if(cf->cft == &Curl_cft_http3)
+      return TRUE;
+    if(cf->cft->flags & CF_TYPE_IP_CONNECT)
+      return FALSE;
+  }
+  return FALSE;
+}
+
+#endif /* USE_MSH3 */
diff --git a/Utilities/cmcurl/lib/vquic/curl_msh3.h b/Utilities/cmcurl/lib/vquic/curl_msh3.h
new file mode 100644
index 0000000..33931f5
--- /dev/null
+++ b/Utilities/cmcurl/lib/vquic/curl_msh3.h
@@ -0,0 +1,46 @@
+#ifndef HEADER_CURL_VQUIC_CURL_MSH3_H
+#define HEADER_CURL_VQUIC_CURL_MSH3_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef USE_MSH3
+
+#include <msh3.h>
+
+void Curl_msh3_ver(char *p, size_t len);
+
+CURLcode Curl_cf_msh3_create(struct Curl_cfilter **pcf,
+                             struct Curl_easy *data,
+                             struct connectdata *conn,
+                             const struct Curl_addrinfo *ai);
+
+bool Curl_conn_is_msh3(const struct Curl_easy *data,
+                       const struct connectdata *conn,
+                       int sockindex);
+
+#endif /* USE_MSQUIC */
+
+#endif /* HEADER_CURL_VQUIC_CURL_MSH3_H */
diff --git a/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c
new file mode 100644
index 0000000..d2d0a3a
--- /dev/null
+++ b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c
@@ -0,0 +1,2550 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef USE_NGTCP2
+#include <ngtcp2/ngtcp2.h>
+#include <nghttp3/nghttp3.h>
+
+#ifdef USE_OPENSSL
+#include <openssl/err.h>
+#ifdef OPENSSL_IS_BORINGSSL
+#include <ngtcp2/ngtcp2_crypto_boringssl.h>
+#else
+#include <ngtcp2/ngtcp2_crypto_openssl.h>
+#endif
+#include "vtls/openssl.h"
+#elif defined(USE_GNUTLS)
+#include <ngtcp2/ngtcp2_crypto_gnutls.h>
+#include "vtls/gtls.h"
+#elif defined(USE_WOLFSSL)
+#include <ngtcp2/ngtcp2_crypto_wolfssl.h>
+#include "vtls/wolfssl.h"
+#endif
+
+#include "urldata.h"
+#include "sendf.h"
+#include "strdup.h"
+#include "rand.h"
+#include "multiif.h"
+#include "strcase.h"
+#include "cfilters.h"
+#include "cf-socket.h"
+#include "connect.h"
+#include "progress.h"
+#include "strerror.h"
+#include "dynbuf.h"
+#include "select.h"
+#include "vquic.h"
+#include "vquic_int.h"
+#include "h2h3.h"
+#include "vtls/keylog.h"
+#include "vtls/vtls.h"
+#include "curl_ngtcp2.h"
+
+#include "warnless.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+
+#define H3_ALPN_H3_29 "\x5h3-29"
+#define H3_ALPN_H3 "\x2h3"
+
+/*
+ * This holds outgoing HTTP/3 stream data that is used by nghttp3 until acked.
+ * It is used as a circular buffer. Add new bytes at the end until it reaches
+ * the far end, then start over at index 0 again.
+ */
+
+#define H3_SEND_SIZE (256*1024)
+struct h3out {
+  uint8_t buf[H3_SEND_SIZE];
+  size_t used;   /* number of bytes used in the buffer */
+  size_t windex; /* index in the buffer where to start writing the next
+                    data block */
+};
+
+#define QUIC_MAX_STREAMS (256*1024)
+#define QUIC_MAX_DATA (1*1024*1024)
+#define QUIC_IDLE_TIMEOUT (60*NGTCP2_SECONDS)
+#define QUIC_HANDSHAKE_TIMEOUT (10*NGTCP2_SECONDS)
+
+#ifdef USE_OPENSSL
+#define QUIC_CIPHERS                                                          \
+  "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_"               \
+  "POLY1305_SHA256:TLS_AES_128_CCM_SHA256"
+#define QUIC_GROUPS "P-256:X25519:P-384:P-521"
+#elif defined(USE_GNUTLS)
+#define QUIC_PRIORITY \
+  "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:" \
+  "+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:" \
+  "+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1:" \
+  "%DISABLE_TLS13_COMPAT_MODE"
+#elif defined(USE_WOLFSSL)
+#define QUIC_CIPHERS                                                          \
+  "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_"               \
+  "POLY1305_SHA256:TLS_AES_128_CCM_SHA256"
+#define QUIC_GROUPS "P-256:P-384:P-521"
+#endif
+
+
+/*
+ * Store ngtcp2 version info in this buffer.
+ */
+void Curl_ngtcp2_ver(char *p, size_t len)
+{
+  const ngtcp2_info *ng2 = ngtcp2_version(0);
+  const nghttp3_info *ht3 = nghttp3_version(0);
+  (void)msnprintf(p, len, "ngtcp2/%s nghttp3/%s",
+                  ng2->version_str, ht3->version_str);
+}
+
+struct cf_ngtcp2_ctx {
+  struct cf_quic_ctx q;
+  ngtcp2_path connected_path;
+  ngtcp2_conn *qconn;
+  ngtcp2_cid dcid;
+  ngtcp2_cid scid;
+  uint32_t version;
+  ngtcp2_settings settings;
+  ngtcp2_transport_params transport_params;
+  ngtcp2_connection_close_error last_error;
+  ngtcp2_crypto_conn_ref conn_ref;
+#ifdef USE_OPENSSL
+  SSL_CTX *sslctx;
+  SSL *ssl;
+#elif defined(USE_GNUTLS)
+  struct gtls_instance *gtls;
+#elif defined(USE_WOLFSSL)
+  WOLFSSL_CTX *sslctx;
+  WOLFSSL *ssl;
+#endif
+  struct cf_call_data call_data;
+  nghttp3_conn *h3conn;
+  nghttp3_settings h3settings;
+  int qlogfd;
+  struct curltime started_at;        /* time the current attempt started */
+  struct curltime handshake_at;      /* time connect handshake finished */
+  struct curltime first_byte_at;     /* when first byte was recvd */
+  struct curltime reconnect_at;    /* time the next attempt should start */
+  BIT(got_first_byte);               /* if first byte was received */
+};
+
+/* How to access `call_data` from a cf_ngtcp2 filter */
+#define CF_CTX_CALL_DATA(cf)  \
+  ((struct cf_ngtcp2_ctx *)(cf)->ctx)->call_data
+
+
+/* ngtcp2 default congestion controller does not perform pacing. Limit
+   the maximum packet burst to MAX_PKT_BURST packets. */
+#define MAX_PKT_BURST 10
+
+static CURLcode cf_process_ingress(struct Curl_cfilter *cf,
+                                   struct Curl_easy *data);
+static CURLcode cf_flush_egress(struct Curl_cfilter *cf,
+                                struct Curl_easy *data);
+static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id,
+                                   uint64_t datalen, void *user_data,
+                                   void *stream_user_data);
+
+static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref)
+{
+  struct Curl_cfilter *cf = conn_ref->user_data;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  return ctx->qconn;
+}
+
+static ngtcp2_tstamp timestamp(void)
+{
+  struct curltime ct = Curl_now();
+  return ct.tv_sec * NGTCP2_SECONDS + ct.tv_usec * NGTCP2_MICROSECONDS;
+}
+
+#ifdef DEBUG_NGTCP2
+static void quic_printf(void *user_data, const char *fmt, ...)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+
+  (void)ctx;  /* TODO: need an easy handle to infof() message */
+  va_list ap;
+  va_start(ap, fmt);
+  vfprintf(stderr, fmt, ap);
+  va_end(ap);
+  fprintf(stderr, "\n");
+}
+#endif
+
+static void qlog_callback(void *user_data, uint32_t flags,
+                          const void *data, size_t datalen)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  (void)flags;
+  if(ctx->qlogfd != -1) {
+    ssize_t rc = write(ctx->qlogfd, data, datalen);
+    if(rc == -1) {
+      /* on write error, stop further write attempts */
+      close(ctx->qlogfd);
+      ctx->qlogfd = -1;
+    }
+  }
+
+}
+
+static void quic_settings(struct cf_ngtcp2_ctx *ctx,
+                          struct Curl_easy *data)
+{
+  ngtcp2_settings *s = &ctx->settings;
+  ngtcp2_transport_params *t = &ctx->transport_params;
+  size_t stream_win_size = CURL_MAX_READ_SIZE;
+
+  ngtcp2_settings_default(s);
+  ngtcp2_transport_params_default(t);
+#ifdef DEBUG_NGTCP2
+  s->log_printf = quic_printf;
+#else
+  s->log_printf = NULL;
+#endif
+
+  (void)data;
+  s->initial_ts = timestamp();
+  s->handshake_timeout = QUIC_HANDSHAKE_TIMEOUT;
+  s->max_window = 100 * stream_win_size;
+  s->max_stream_window = stream_win_size;
+
+  t->initial_max_data = 10 * stream_win_size;
+  t->initial_max_stream_data_bidi_local = stream_win_size;
+  t->initial_max_stream_data_bidi_remote = stream_win_size;
+  t->initial_max_stream_data_uni = stream_win_size;
+  t->initial_max_streams_bidi = QUIC_MAX_STREAMS;
+  t->initial_max_streams_uni = QUIC_MAX_STREAMS;
+  t->max_idle_timeout = QUIC_IDLE_TIMEOUT;
+  if(ctx->qlogfd != -1) {
+    s->qlog.write = qlog_callback;
+  }
+}
+
+#ifdef USE_OPENSSL
+static void keylog_callback(const SSL *ssl, const char *line)
+{
+  (void)ssl;
+  Curl_tls_keylog_write_line(line);
+}
+#elif defined(USE_GNUTLS)
+static int keylog_callback(gnutls_session_t session, const char *label,
+                    const gnutls_datum_t *secret)
+{
+  gnutls_datum_t crandom;
+  gnutls_datum_t srandom;
+
+  gnutls_session_get_random(session, &crandom, &srandom);
+  if(crandom.size != 32) {
+    return -1;
+  }
+
+  Curl_tls_keylog_write(label, crandom.data, secret->data, secret->size);
+  return 0;
+}
+#elif defined(USE_WOLFSSL)
+#if defined(HAVE_SECRET_CALLBACK)
+static void keylog_callback(const WOLFSSL *ssl, const char *line)
+{
+  (void)ssl;
+  Curl_tls_keylog_write_line(line);
+}
+#endif
+#endif
+
+static int init_ngh3_conn(struct Curl_cfilter *cf);
+
+#ifdef USE_OPENSSL
+static CURLcode quic_ssl_ctx(SSL_CTX **pssl_ctx,
+                             struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct connectdata *conn = cf->conn;
+  CURLcode result = CURLE_FAILED_INIT;
+  SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method());
+
+  if(!ssl_ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+
+#ifdef OPENSSL_IS_BORINGSSL
+  if(ngtcp2_crypto_boringssl_configure_client_context(ssl_ctx) != 0) {
+    failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed");
+    goto out;
+  }
+#else
+  if(ngtcp2_crypto_openssl_configure_client_context(ssl_ctx) != 0) {
+    failf(data, "ngtcp2_crypto_openssl_configure_client_context failed");
+    goto out;
+  }
+#endif
+
+  SSL_CTX_set_default_verify_paths(ssl_ctx);
+
+#ifdef OPENSSL_IS_BORINGSSL
+  if(SSL_CTX_set1_curves_list(ssl_ctx, QUIC_GROUPS) != 1) {
+    failf(data, "SSL_CTX_set1_curves_list failed");
+    goto out;
+  }
+#else
+  if(SSL_CTX_set_ciphersuites(ssl_ctx, QUIC_CIPHERS) != 1) {
+    char error_buffer[256];
+    ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer));
+    failf(data, "SSL_CTX_set_ciphersuites: %s", error_buffer);
+    goto out;
+  }
+
+  if(SSL_CTX_set1_groups_list(ssl_ctx, QUIC_GROUPS) != 1) {
+    failf(data, "SSL_CTX_set1_groups_list failed");
+    goto out;
+  }
+#endif
+
+  /* Open the file if a TLS or QUIC backend has not done this before. */
+  Curl_tls_keylog_open();
+  if(Curl_tls_keylog_enabled()) {
+    SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback);
+  }
+
+  result = Curl_ssl_setup_x509_store(cf, data, ssl_ctx);
+  if(result)
+    goto out;
+
+  /* OpenSSL always tries to verify the peer, this only says whether it should
+   * fail to connect if the verification fails, or if it should continue
+   * anyway. In the latter case the result of the verification is checked with
+   * SSL_get_verify_result() below. */
+  SSL_CTX_set_verify(ssl_ctx, conn->ssl_config.verifypeer ?
+                     SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL);
+
+  /* give application a chance to interfere with SSL set up. */
+  if(data->set.ssl.fsslctx) {
+    Curl_set_in_callback(data, true);
+    result = (*data->set.ssl.fsslctx)(data, ssl_ctx,
+                                      data->set.ssl.fsslctxp);
+    Curl_set_in_callback(data, false);
+    if(result) {
+      failf(data, "error signaled by ssl ctx callback");
+      goto out;
+    }
+  }
+  result = CURLE_OK;
+
+out:
+  *pssl_ctx = result? NULL : ssl_ctx;
+  if(result && ssl_ctx)
+    SSL_CTX_free(ssl_ctx);
+  return result;
+}
+
+static CURLcode quic_set_client_cert(struct Curl_cfilter *cf,
+                                     struct Curl_easy *data)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  SSL_CTX *ssl_ctx = ctx->sslctx;
+  const struct ssl_config_data *ssl_config;
+
+  ssl_config = Curl_ssl_get_config(data, FIRSTSOCKET);
+  DEBUGASSERT(ssl_config);
+
+  if(ssl_config->primary.clientcert || ssl_config->primary.cert_blob
+     || ssl_config->cert_type) {
+    return Curl_ossl_set_client_cert(
+        data, ssl_ctx, ssl_config->primary.clientcert,
+        ssl_config->primary.cert_blob, ssl_config->cert_type,
+        ssl_config->key, ssl_config->key_blob,
+        ssl_config->key_type, ssl_config->key_passwd);
+  }
+
+  return CURLE_OK;
+}
+
+/** SSL callbacks ***/
+
+static CURLcode quic_init_ssl(struct Curl_cfilter *cf,
+                              struct Curl_easy *data)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  const uint8_t *alpn = NULL;
+  size_t alpnlen = 0;
+
+  (void)data;
+  DEBUGASSERT(!ctx->ssl);
+  ctx->ssl = SSL_new(ctx->sslctx);
+
+  SSL_set_app_data(ctx->ssl, &ctx->conn_ref);
+  SSL_set_connect_state(ctx->ssl);
+  SSL_set_quic_use_legacy_codepoint(ctx->ssl, 0);
+
+  alpn = (const uint8_t *)H3_ALPN_H3_29 H3_ALPN_H3;
+  alpnlen = sizeof(H3_ALPN_H3_29) - 1 + sizeof(H3_ALPN_H3) - 1;
+  if(alpn)
+    SSL_set_alpn_protos(ctx->ssl, alpn, (int)alpnlen);
+
+  /* set SNI */
+  SSL_set_tlsext_host_name(ctx->ssl, cf->conn->host.name);
+  return CURLE_OK;
+}
+#elif defined(USE_GNUTLS)
+static CURLcode quic_init_ssl(struct Curl_cfilter *cf,
+                              struct Curl_easy *data)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  CURLcode result;
+  gnutls_datum_t alpn[2];
+  /* this will need some attention when HTTPS proxy over QUIC get fixed */
+  const char * const hostname = cf->conn->host.name;
+  long * const pverifyresult = &data->set.ssl.certverifyresult;
+  int rc;
+
+  DEBUGASSERT(ctx->gtls == NULL);
+  ctx->gtls = calloc(1, sizeof(*(ctx->gtls)));
+  if(!ctx->gtls)
+    return CURLE_OUT_OF_MEMORY;
+
+  result = gtls_client_init(data, &cf->conn->ssl_config, &data->set.ssl,
+                            hostname, ctx->gtls, pverifyresult);
+  if(result)
+    return result;
+
+  gnutls_session_set_ptr(ctx->gtls->session, &ctx->conn_ref);
+
+  if(ngtcp2_crypto_gnutls_configure_client_session(ctx->gtls->session) != 0) {
+    DEBUGF(LOG_CF(data, cf,
+                  "ngtcp2_crypto_gnutls_configure_client_session failed\n"));
+    return CURLE_QUIC_CONNECT_ERROR;
+  }
+
+  rc = gnutls_priority_set_direct(ctx->gtls->session, QUIC_PRIORITY, NULL);
+  if(rc < 0) {
+    DEBUGF(LOG_CF(data, cf, "gnutls_priority_set_direct failed: %s\n",
+                  gnutls_strerror(rc)));
+    return CURLE_QUIC_CONNECT_ERROR;
+  }
+
+  /* Open the file if a TLS or QUIC backend has not done this before. */
+  Curl_tls_keylog_open();
+  if(Curl_tls_keylog_enabled()) {
+    gnutls_session_set_keylog_function(ctx->gtls->session, keylog_callback);
+  }
+
+  /* strip the first byte (the length) from NGHTTP3_ALPN_H3 */
+  alpn[0].data = (unsigned char *)H3_ALPN_H3_29 + 1;
+  alpn[0].size = sizeof(H3_ALPN_H3_29) - 2;
+  alpn[1].data = (unsigned char *)H3_ALPN_H3 + 1;
+  alpn[1].size = sizeof(H3_ALPN_H3) - 2;
+
+  gnutls_alpn_set_protocols(ctx->gtls->session,
+                            alpn, 2, GNUTLS_ALPN_MANDATORY);
+  return CURLE_OK;
+}
+#elif defined(USE_WOLFSSL)
+
+static CURLcode quic_ssl_ctx(WOLFSSL_CTX **pssl_ctx,
+                             struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct connectdata *conn = cf->conn;
+  CURLcode result = CURLE_FAILED_INIT;
+  WOLFSSL_CTX *ssl_ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
+
+  if(!ssl_ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+
+  if(ngtcp2_crypto_wolfssl_configure_client_context(ssl_ctx) != 0) {
+    failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed");
+    goto out;
+  }
+
+  wolfSSL_CTX_set_default_verify_paths(ssl_ctx);
+
+  if(wolfSSL_CTX_set_cipher_list(ssl_ctx, QUIC_CIPHERS) != 1) {
+    char error_buffer[256];
+    ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer));
+    failf(data, "SSL_CTX_set_ciphersuites: %s", error_buffer);
+    goto out;
+  }
+
+  if(wolfSSL_CTX_set1_groups_list(ssl_ctx, (char *)QUIC_GROUPS) != 1) {
+    failf(data, "SSL_CTX_set1_groups_list failed");
+    goto out;
+  }
+
+  /* Open the file if a TLS or QUIC backend has not done this before. */
+  Curl_tls_keylog_open();
+  if(Curl_tls_keylog_enabled()) {
+#if defined(HAVE_SECRET_CALLBACK)
+    wolfSSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback);
+#else
+    failf(data, "wolfSSL was built without keylog callback");
+    goto out;
+#endif
+  }
+
+  if(conn->ssl_config.verifypeer) {
+    const char * const ssl_cafile = conn->ssl_config.CAfile;
+    const char * const ssl_capath = conn->ssl_config.CApath;
+
+    wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
+    if(conn->ssl_config.CAfile || conn->ssl_config.CApath) {
+      /* tell wolfSSL where to find CA certificates that are used to verify
+         the server's certificate. */
+      if(!wolfSSL_CTX_load_verify_locations(ssl_ctx, ssl_cafile, ssl_capath)) {
+        /* Fail if we insist on successfully verifying the server. */
+        failf(data, "error setting certificate verify locations:"
+              "  CAfile: %s CApath: %s",
+              ssl_cafile ? ssl_cafile : "none",
+              ssl_capath ? ssl_capath : "none");
+        goto out;
+      }
+      infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
+      infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
+    }
+#ifdef CURL_CA_FALLBACK
+    else {
+      /* verifying the peer without any CA certificates won't work so
+         use wolfssl's built-in default as fallback */
+      wolfSSL_CTX_set_default_verify_paths(ssl_ctx);
+    }
+#endif
+  }
+  else {
+    wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL);
+  }
+
+  /* give application a chance to interfere with SSL set up. */
+  if(data->set.ssl.fsslctx) {
+    Curl_set_in_callback(data, true);
+    result = (*data->set.ssl.fsslctx)(data, ssl_ctx,
+                                      data->set.ssl.fsslctxp);
+    Curl_set_in_callback(data, false);
+    if(result) {
+      failf(data, "error signaled by ssl ctx callback");
+      goto out;
+    }
+  }
+  result = CURLE_OK;
+
+out:
+  *pssl_ctx = result? NULL : ssl_ctx;
+  if(result && ssl_ctx)
+    SSL_CTX_free(ssl_ctx);
+  return result;
+}
+
+/** SSL callbacks ***/
+
+static CURLcode quic_init_ssl(struct Curl_cfilter *cf,
+                              struct Curl_easy *data)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  const uint8_t *alpn = NULL;
+  size_t alpnlen = 0;
+  /* this will need some attention when HTTPS proxy over QUIC get fixed */
+  const char * const hostname = cf->conn->host.name;
+
+  (void)data;
+  DEBUGASSERT(!ctx->ssl);
+  ctx->ssl = wolfSSL_new(ctx->sslctx);
+
+  wolfSSL_set_app_data(ctx->ssl, &ctx->conn_ref);
+  wolfSSL_set_connect_state(ctx->ssl);
+  wolfSSL_set_quic_use_legacy_codepoint(ctx->ssl, 0);
+
+  alpn = (const uint8_t *)H3_ALPN_H3_29 H3_ALPN_H3;
+  alpnlen = sizeof(H3_ALPN_H3_29) - 1 + sizeof(H3_ALPN_H3) - 1;
+  if(alpn)
+    wolfSSL_set_alpn_protos(ctx->ssl, alpn, (int)alpnlen);
+
+  /* set SNI */
+  wolfSSL_UseSNI(ctx->ssl, WOLFSSL_SNI_HOST_NAME,
+                 hostname, (unsigned short)strlen(hostname));
+
+  return CURLE_OK;
+}
+#endif /* defined(USE_WOLFSSL) */
+
+static int cb_handshake_completed(ngtcp2_conn *tconn, void *user_data)
+{
+  (void)user_data;
+  (void)tconn;
+  return 0;
+}
+
+static void report_consumed_data(struct Curl_cfilter *cf,
+                                 struct Curl_easy *data,
+                                 size_t consumed)
+{
+  struct HTTP *stream = data->req.p.http;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+
+  /* the HTTP/1.1 response headers are written to the buffer, but
+   * consuming those does not count against flow control. */
+  if(stream->recv_buf_nonflow) {
+    if(consumed >= stream->recv_buf_nonflow) {
+      consumed -= stream->recv_buf_nonflow;
+      stream->recv_buf_nonflow = 0;
+    }
+    else {
+      stream->recv_buf_nonflow -= consumed;
+      consumed = 0;
+    }
+  }
+  if(consumed > 0) {
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] consumed %zu DATA bytes",
+                  stream->stream3_id, consumed));
+    ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream->stream3_id,
+                                         consumed);
+    ngtcp2_conn_extend_max_offset(ctx->qconn, consumed);
+  }
+  if(!stream->closed && data->state.drain
+     && !stream->memlen
+     && !Curl_dyn_len(&stream->overflow)) {
+     /* nothing buffered any more */
+     data->state.drain = 0;
+  }
+}
+
+static int cb_recv_stream_data(ngtcp2_conn *tconn, uint32_t flags,
+                               int64_t stream_id, uint64_t offset,
+                               const uint8_t *buf, size_t buflen,
+                               void *user_data, void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  nghttp3_ssize nconsumed;
+  int fin = (flags & NGTCP2_STREAM_DATA_FLAG_FIN) ? 1 : 0;
+  struct Curl_easy *data = stream_user_data;
+  (void)offset;
+  (void)data;
+
+  nconsumed =
+    nghttp3_conn_read_stream(ctx->h3conn, stream_id, buf, buflen, fin);
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] read_stream(len=%zu) -> %zd",
+                stream_id, buflen, nconsumed));
+  if(nconsumed < 0) {
+    ngtcp2_connection_close_error_set_application_error(
+        &ctx->last_error,
+        nghttp3_err_infer_quic_app_error_code((int)nconsumed), NULL, 0);
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  /* number of bytes inside buflen which consists of framing overhead
+   * including QPACK HEADERS. In other words, it does not consume payload of
+   * DATA frame. */
+  ngtcp2_conn_extend_max_stream_offset(tconn, stream_id, nconsumed);
+  ngtcp2_conn_extend_max_offset(tconn, nconsumed);
+
+  return 0;
+}
+
+static int
+cb_acked_stream_data_offset(ngtcp2_conn *tconn, int64_t stream_id,
+                            uint64_t offset, uint64_t datalen, void *user_data,
+                            void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  int rv;
+  (void)stream_id;
+  (void)tconn;
+  (void)offset;
+  (void)datalen;
+  (void)stream_user_data;
+
+  rv = nghttp3_conn_add_ack_offset(ctx->h3conn, stream_id, datalen);
+  if(rv) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int cb_stream_close(ngtcp2_conn *tconn, uint32_t flags,
+                           int64_t stream3_id, uint64_t app_error_code,
+                           void *user_data, void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct Curl_easy *data = stream_user_data;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  int rv;
+
+  (void)tconn;
+  (void)data;
+  /* stream is closed... */
+
+  if(!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) {
+    app_error_code = NGHTTP3_H3_NO_ERROR;
+  }
+
+  rv = nghttp3_conn_close_stream(ctx->h3conn, stream3_id,
+                                 app_error_code);
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] quic close(err=%"
+                PRIu64 ") -> %d", stream3_id, app_error_code, rv));
+  if(rv) {
+    ngtcp2_connection_close_error_set_application_error(
+        &ctx->last_error, nghttp3_err_infer_quic_app_error_code(rv), NULL, 0);
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int cb_stream_reset(ngtcp2_conn *tconn, int64_t stream_id,
+                           uint64_t final_size, uint64_t app_error_code,
+                           void *user_data, void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  struct Curl_easy *data = stream_user_data;
+  int rv;
+  (void)tconn;
+  (void)final_size;
+  (void)app_error_code;
+  (void)data;
+
+  rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id);
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] reset -> %d", stream_id, rv));
+  if(rv) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int cb_stream_stop_sending(ngtcp2_conn *tconn, int64_t stream_id,
+                                  uint64_t app_error_code, void *user_data,
+                                  void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  int rv;
+  (void)tconn;
+  (void)app_error_code;
+  (void)stream_user_data;
+
+  rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id);
+  if(rv) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int cb_extend_max_local_streams_bidi(ngtcp2_conn *tconn,
+                                            uint64_t max_streams,
+                                            void *user_data)
+{
+  (void)tconn;
+  (void)max_streams;
+  (void)user_data;
+
+  return 0;
+}
+
+static int cb_extend_max_stream_data(ngtcp2_conn *tconn, int64_t stream_id,
+                                     uint64_t max_data, void *user_data,
+                                     void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  int rv;
+  (void)tconn;
+  (void)max_data;
+  (void)stream_user_data;
+
+  rv = nghttp3_conn_unblock_stream(ctx->h3conn, stream_id);
+  if(rv) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static void cb_rand(uint8_t *dest, size_t destlen,
+                    const ngtcp2_rand_ctx *rand_ctx)
+{
+  CURLcode result;
+  (void)rand_ctx;
+
+  result = Curl_rand(NULL, dest, destlen);
+  if(result) {
+    /* cb_rand is only used for non-cryptographic context.  If Curl_rand
+       failed, just fill 0 and call it *random*. */
+    memset(dest, 0, destlen);
+  }
+}
+
+static int cb_get_new_connection_id(ngtcp2_conn *tconn, ngtcp2_cid *cid,
+                                    uint8_t *token, size_t cidlen,
+                                    void *user_data)
+{
+  CURLcode result;
+  (void)tconn;
+  (void)user_data;
+
+  result = Curl_rand(NULL, cid->data, cidlen);
+  if(result)
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  cid->datalen = cidlen;
+
+  result = Curl_rand(NULL, token, NGTCP2_STATELESS_RESET_TOKENLEN);
+  if(result)
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+
+  return 0;
+}
+
+static int cb_recv_rx_key(ngtcp2_conn *tconn, ngtcp2_crypto_level level,
+                          void *user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  (void)tconn;
+
+  if(level != NGTCP2_CRYPTO_LEVEL_APPLICATION) {
+    return 0;
+  }
+
+  if(init_ngh3_conn(cf) != CURLE_OK) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static ngtcp2_callbacks ng_callbacks = {
+  ngtcp2_crypto_client_initial_cb,
+  NULL, /* recv_client_initial */
+  ngtcp2_crypto_recv_crypto_data_cb,
+  cb_handshake_completed,
+  NULL, /* recv_version_negotiation */
+  ngtcp2_crypto_encrypt_cb,
+  ngtcp2_crypto_decrypt_cb,
+  ngtcp2_crypto_hp_mask_cb,
+  cb_recv_stream_data,
+  cb_acked_stream_data_offset,
+  NULL, /* stream_open */
+  cb_stream_close,
+  NULL, /* recv_stateless_reset */
+  ngtcp2_crypto_recv_retry_cb,
+  cb_extend_max_local_streams_bidi,
+  NULL, /* extend_max_local_streams_uni */
+  cb_rand,
+  cb_get_new_connection_id,
+  NULL, /* remove_connection_id */
+  ngtcp2_crypto_update_key_cb, /* update_key */
+  NULL, /* path_validation */
+  NULL, /* select_preferred_addr */
+  cb_stream_reset,
+  NULL, /* extend_max_remote_streams_bidi */
+  NULL, /* extend_max_remote_streams_uni */
+  cb_extend_max_stream_data,
+  NULL, /* dcid_status */
+  NULL, /* handshake_confirmed */
+  NULL, /* recv_new_token */
+  ngtcp2_crypto_delete_crypto_aead_ctx_cb,
+  ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
+  NULL, /* recv_datagram */
+  NULL, /* ack_datagram */
+  NULL, /* lost_datagram */
+  ngtcp2_crypto_get_path_challenge_data_cb,
+  cb_stream_stop_sending,
+  NULL, /* version_negotiation */
+  cb_recv_rx_key,
+  NULL, /* recv_tx_key */
+  NULL, /* early_data_rejected */
+};
+
+static int cf_ngtcp2_get_select_socks(struct Curl_cfilter *cf,
+                                      struct Curl_easy *data,
+                                      curl_socket_t *socks)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  struct SingleRequest *k = &data->req;
+  int rv = GETSOCK_BLANK;
+  struct HTTP *stream = data->req.p.http;
+  struct cf_call_data save;
+
+  CF_DATA_SAVE(save, cf, data);
+  socks[0] = ctx->q.sockfd;
+
+  /* in an HTTP/3 connection we can basically always get a frame so we should
+     always be ready for one */
+  rv |= GETSOCK_READSOCK(0);
+
+  /* we're still uploading or the HTTP/2 layer wants to send data */
+  if((k->keepon & KEEP_SENDBITS) == KEEP_SEND &&
+     (!stream->h3out || stream->h3out->used < H3_SEND_SIZE) &&
+     ngtcp2_conn_get_cwnd_left(ctx->qconn) &&
+     ngtcp2_conn_get_max_data_left(ctx->qconn) &&
+     nghttp3_conn_is_stream_writable(ctx->h3conn, stream->stream3_id))
+    rv |= GETSOCK_WRITESOCK(0);
+
+  DEBUGF(LOG_CF(data, cf, "get_select_socks -> %x (sock=%d)",
+                rv, (int)socks[0]));
+  CF_DATA_RESTORE(cf, save);
+  return rv;
+}
+
+static void notify_drain(struct Curl_cfilter *cf,
+                         struct Curl_easy *data)
+{
+  (void)cf;
+  if(!data->state.drain) {
+    data->state.drain = 1;
+    Curl_expire(data, 0, EXPIRE_RUN_NOW);
+  }
+}
+
+
+static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id,
+                              uint64_t app_error_code, void *user_data,
+                              void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct Curl_easy *data = stream_user_data;
+  struct HTTP *stream = data->req.p.http;
+  (void)conn;
+  (void)stream_id;
+  (void)app_error_code;
+  (void)cf;
+
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] h3 close(err=%" PRIx64 ")",
+                stream_id, app_error_code));
+  stream->closed = TRUE;
+  stream->error3 = app_error_code;
+  if(app_error_code == NGHTTP3_H3_INTERNAL_ERROR) {
+    /* TODO: we do not get a specific error when the remote end closed
+     * the response before it was complete. */
+    stream->reset = TRUE;
+  }
+  notify_drain(cf, data);
+  return 0;
+}
+
+/*
+ * write_resp_raw() copies response data in raw format to the `data`'s
+  * receive buffer. If not enough space is available, it appends to the
+ * `data`'s overflow buffer.
+ */
+static CURLcode write_resp_raw(struct Curl_cfilter *cf,
+                               struct Curl_easy *data,
+                               const void *mem, size_t memlen,
+                               bool flow)
+{
+  struct HTTP *stream = data->req.p.http;
+  CURLcode result = CURLE_OK;
+  const char *buf = mem;
+  size_t ncopy = memlen;
+  /* copy as much as possible to the receive buffer */
+  if(stream->len) {
+    size_t len = CURLMIN(ncopy, stream->len);
+    memcpy(stream->mem + stream->memlen, buf, len);
+    stream->len -= len;
+    stream->memlen += len;
+    buf += len;
+    ncopy -= len;
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] resp_raw: added %zu bytes"
+                  " to data buffer", stream->stream3_id, len));
+  }
+  /* copy the rest to the overflow buffer */
+  if(ncopy) {
+    result = Curl_dyn_addn(&stream->overflow, buf, ncopy);
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] resp_raw: added %zu bytes"
+                  " to overflow buffer -> %d",
+                  stream->stream3_id, ncopy, result));
+    notify_drain(cf, data);
+  }
+
+  if(!flow)
+    stream->recv_buf_nonflow += memlen;
+  if(CF_DATA_CURRENT(cf) != data) {
+    notify_drain(cf, data);
+  }
+  return result;
+}
+
+static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id,
+                           const uint8_t *buf, size_t buflen,
+                           void *user_data, void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct Curl_easy *data = stream_user_data;
+  CURLcode result;
+
+  (void)conn;
+  (void)stream3_id;
+
+  result = write_resp_raw(cf, data, buf, buflen, TRUE);
+  return result? -1 : 0;
+}
+
+static int cb_h3_deferred_consume(nghttp3_conn *conn, int64_t stream3_id,
+                                  size_t consumed, void *user_data,
+                                  void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  (void)conn;
+  (void)stream_user_data;
+
+  /* nghttp3 has consumed bytes on the QUIC stream and we need to
+   * tell the QUIC connection to increase its flow control */
+  ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream3_id, consumed);
+  ngtcp2_conn_extend_max_offset(ctx->qconn, consumed);
+  return 0;
+}
+
+/* Decode HTTP status code.  Returns -1 if no valid status code was
+   decoded. (duplicate from http2.c) */
+static int decode_status_code(const uint8_t *value, size_t len)
+{
+  int i;
+  int res;
+
+  if(len != 3) {
+    return -1;
+  }
+
+  res = 0;
+
+  for(i = 0; i < 3; ++i) {
+    char c = value[i];
+
+    if(c < '0' || c > '9') {
+      return -1;
+    }
+
+    res *= 10;
+    res += c - '0';
+  }
+
+  return res;
+}
+
+static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id,
+                             int fin, void *user_data, void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct Curl_easy *data = stream_user_data;
+  struct HTTP *stream = data->req.p.http;
+  CURLcode result = CURLE_OK;
+  (void)conn;
+  (void)stream_id;
+  (void)fin;
+  (void)cf;
+
+  /* add a CRLF only if we've received some headers */
+  if(stream->firstheader) {
+    result = write_resp_raw(cf, data, "\r\n", 2, FALSE);
+    if(result) {
+      return -1;
+    }
+  }
+
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] end_headers(status_code=%d",
+                stream_id, stream->status_code));
+  if(stream->status_code / 100 != 1) {
+    stream->bodystarted = TRUE;
+  }
+  return 0;
+}
+
+static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id,
+                             int32_t token, nghttp3_rcbuf *name,
+                             nghttp3_rcbuf *value, uint8_t flags,
+                             void *user_data, void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  nghttp3_vec h3name = nghttp3_rcbuf_get_buf(name);
+  nghttp3_vec h3val = nghttp3_rcbuf_get_buf(value);
+  struct Curl_easy *data = stream_user_data;
+  struct HTTP *stream = data->req.p.http;
+  CURLcode result = CURLE_OK;
+  (void)conn;
+  (void)stream_id;
+  (void)token;
+  (void)flags;
+  (void)cf;
+
+  if(token == NGHTTP3_QPACK_TOKEN__STATUS) {
+    char line[14]; /* status line is always 13 characters long */
+    size_t ncopy;
+
+    DEBUGASSERT(!stream->firstheader);
+    stream->status_code = decode_status_code(h3val.base, h3val.len);
+    DEBUGASSERT(stream->status_code != -1);
+    ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n",
+                      stream->status_code);
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] status: %s",
+                  stream_id, line));
+    result = write_resp_raw(cf, data, line, ncopy, FALSE);
+    if(result) {
+      return -1;
+    }
+    stream->firstheader = TRUE;
+  }
+  else {
+    /* store as an HTTP1-style header */
+    DEBUGASSERT(stream->firstheader);
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] header: %.*s: %.*s",
+                  stream_id, (int)h3name.len, h3name.base,
+                  (int)h3val.len, h3val.base));
+    result = write_resp_raw(cf, data, h3name.base, h3name.len, FALSE);
+    if(result) {
+      return -1;
+    }
+    result = write_resp_raw(cf, data, ": ", 2, FALSE);
+    if(result) {
+      return -1;
+    }
+    result = write_resp_raw(cf, data, h3val.base, h3val.len, FALSE);
+    if(result) {
+      return -1;
+    }
+    result = write_resp_raw(cf, data, "\r\n", 2, FALSE);
+    if(result) {
+      return -1;
+    }
+  }
+  return 0;
+}
+
+static int cb_h3_stop_sending(nghttp3_conn *conn, int64_t stream_id,
+                              uint64_t app_error_code, void *user_data,
+                              void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  int rv;
+  (void)conn;
+  (void)stream_user_data;
+
+  rv = ngtcp2_conn_shutdown_stream_read(ctx->qconn, stream_id, app_error_code);
+  if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t stream_id,
+                              uint64_t app_error_code, void *user_data,
+                              void *stream_user_data) {
+  struct Curl_cfilter *cf = user_data;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  struct Curl_easy *data = stream_user_data;
+  int rv;
+  (void)conn;
+  (void)data;
+
+  rv = ngtcp2_conn_shutdown_stream_write(ctx->qconn, stream_id,
+                                         app_error_code);
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] reset -> %d", stream_id, rv));
+  if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static nghttp3_callbacks ngh3_callbacks = {
+  cb_h3_acked_stream_data, /* acked_stream_data */
+  cb_h3_stream_close,
+  cb_h3_recv_data,
+  cb_h3_deferred_consume,
+  NULL, /* begin_headers */
+  cb_h3_recv_header,
+  cb_h3_end_headers,
+  NULL, /* begin_trailers */
+  cb_h3_recv_header,
+  NULL, /* end_trailers */
+  cb_h3_stop_sending,
+  NULL, /* end_stream */
+  cb_h3_reset_stream,
+  NULL /* shutdown */
+};
+
+static int init_ngh3_conn(struct Curl_cfilter *cf)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  CURLcode result;
+  int rc;
+  int64_t ctrl_stream_id, qpack_enc_stream_id, qpack_dec_stream_id;
+
+  if(ngtcp2_conn_get_max_local_streams_uni(ctx->qconn) < 3) {
+    return CURLE_QUIC_CONNECT_ERROR;
+  }
+
+  nghttp3_settings_default(&ctx->h3settings);
+
+  rc = nghttp3_conn_client_new(&ctx->h3conn,
+                               &ngh3_callbacks,
+                               &ctx->h3settings,
+                               nghttp3_mem_default(),
+                               cf);
+  if(rc) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto fail;
+  }
+
+  rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &ctrl_stream_id, NULL);
+  if(rc) {
+    result = CURLE_QUIC_CONNECT_ERROR;
+    goto fail;
+  }
+
+  rc = nghttp3_conn_bind_control_stream(ctx->h3conn, ctrl_stream_id);
+  if(rc) {
+    result = CURLE_QUIC_CONNECT_ERROR;
+    goto fail;
+  }
+
+  rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &qpack_enc_stream_id, NULL);
+  if(rc) {
+    result = CURLE_QUIC_CONNECT_ERROR;
+    goto fail;
+  }
+
+  rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &qpack_dec_stream_id, NULL);
+  if(rc) {
+    result = CURLE_QUIC_CONNECT_ERROR;
+    goto fail;
+  }
+
+  rc = nghttp3_conn_bind_qpack_streams(ctx->h3conn, qpack_enc_stream_id,
+                                       qpack_dec_stream_id);
+  if(rc) {
+    result = CURLE_QUIC_CONNECT_ERROR;
+    goto fail;
+  }
+
+  return CURLE_OK;
+  fail:
+
+  return result;
+}
+
+static void drain_overflow_buffer(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data)
+{
+  struct HTTP *stream = data->req.p.http;
+  size_t overlen = Curl_dyn_len(&stream->overflow);
+  size_t ncopy = CURLMIN(overlen, stream->len);
+
+  (void)cf;
+  if(ncopy > 0) {
+    memcpy(stream->mem + stream->memlen,
+           Curl_dyn_ptr(&stream->overflow), ncopy);
+    stream->len -= ncopy;
+    stream->memlen += ncopy;
+    if(ncopy != overlen)
+      /* make the buffer only keep the tail */
+      (void)Curl_dyn_tail(&stream->overflow, overlen - ncopy);
+    else {
+      Curl_dyn_reset(&stream->overflow);
+    }
+  }
+}
+
+static ssize_t recv_closed_stream(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  CURLcode *err)
+{
+  struct HTTP *stream = data->req.p.http;
+  ssize_t nread = -1;
+
+  (void)cf;
+
+  if(stream->reset) {
+    failf(data,
+          "HTTP/3 stream %" PRId64 " reset by server", stream->stream3_id);
+    *err = CURLE_PARTIAL_FILE;
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, was reset -> %d",
+                  stream->stream3_id, *err));
+    goto out;
+  }
+  else if(stream->error3 != NGHTTP3_H3_NO_ERROR) {
+    failf(data,
+          "HTTP/3 stream %" PRId64 " was not closed cleanly: (err 0x%" PRIx64
+          ")",
+          stream->stream3_id, stream->error3);
+    *err = CURLE_HTTP3;
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, closed uncleanly"
+                  " -> %d", stream->stream3_id, *err));
+    goto out;
+  }
+
+  if(!stream->bodystarted) {
+    failf(data,
+          "HTTP/3 stream %" PRId64 " was closed cleanly, but before getting"
+          " all response header fields, treated as error",
+          stream->stream3_id);
+    *err = CURLE_HTTP3;
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, closed incomplete"
+                  " -> %d", stream->stream3_id, *err));
+    goto out;
+  }
+  else {
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, closed ok"
+                  " -> %d", stream->stream3_id, *err));
+  }
+  *err = CURLE_OK;
+  nread = 0;
+
+out:
+  data->state.drain = 0;
+  return nread;
+}
+
+/* incoming data frames on the h3 stream */
+static ssize_t cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
+                              char *buf, size_t len, CURLcode *err)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  struct HTTP *stream = data->req.p.http;
+  ssize_t nread = -1;
+  struct cf_call_data save;
+
+  (void)ctx;
+
+  CF_DATA_SAVE(save, cf, data);
+  DEBUGASSERT(cf->connected);
+  DEBUGASSERT(ctx);
+  DEBUGASSERT(ctx->qconn);
+  DEBUGASSERT(ctx->h3conn);
+  *err = CURLE_OK;
+
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv(len=%zu) start",
+                stream->stream3_id, len));
+  /* TODO: this implementation of response DATA buffering is fragile.
+   * It makes the following assumptions:
+   * - the `buf` passed here has the same lifetime as the easy handle
+   * - data returned in `buf` from this call is immediately used and `buf`
+   *   can be overwritten during any handling of other transfers at
+   *   this connection.
+   */
+  if(!stream->memlen) {
+    /* `buf` was not known before or is currently not used by stream,
+     * assign it (again). */
+    stream->mem = buf;
+    stream->len = len;
+  }
+
+  /* if there's data in the overflow buffer, move as much
+     as possible to the receive buffer now */
+  drain_overflow_buffer(cf, data);
+
+  if(cf_process_ingress(cf, data)) {
+    *err = CURLE_RECV_ERROR;
+    nread = -1;
+    goto out;
+  }
+
+  if(stream->memlen) {
+    nread = stream->memlen;
+    /* reset to allow more data to come */
+    /* TODO: very brittle buffer use design:
+     * - stream->mem has now `nread` bytes of response data
+     * - we assume that the caller will use those immediately and
+     *   we can overwrite that with new data on our next invocation from
+     *   anywhere.
+     */
+    stream->mem = buf;
+    stream->memlen = 0;
+    stream->len = len;
+    /* extend the stream window with the data we're consuming and send out
+       any additional packets to tell the server that we can receive more */
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv -> %zd bytes",
+                  stream->stream3_id, nread));
+    report_consumed_data(cf, data, nread);
+    if(cf_flush_egress(cf, data)) {
+      *err = CURLE_SEND_ERROR;
+      nread = -1;
+    }
+    goto out;
+  }
+
+  if(stream->closed) {
+    nread = recv_closed_stream(cf, data, err);
+    goto out;
+  }
+
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv -> EAGAIN",
+                stream->stream3_id));
+  *err = CURLE_AGAIN;
+  nread = -1;
+out:
+  if(cf_flush_egress(cf, data)) {
+    *err = CURLE_SEND_ERROR;
+    nread = -1;
+    goto out;
+  }
+
+  CF_DATA_RESTORE(cf, save);
+  return nread;
+}
+
+/* this amount of data has now been acked on this stream */
+static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id,
+                                   uint64_t datalen, void *user_data,
+                                   void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct Curl_easy *data = stream_user_data;
+  struct HTTP *stream = data->req.p.http;
+  (void)user_data;
+
+  (void)cf;
+  if(!data->set.postfields) {
+    stream->h3out->used -= datalen;
+    DEBUGF(LOG_CF(data, cf, "cb_h3_acked_stream_data, %"PRIu64" bytes, "
+                  "%zd left unacked", datalen, stream->h3out->used));
+    DEBUGASSERT(stream->h3out->used < H3_SEND_SIZE);
+
+    if(stream->h3out->used == 0) {
+      int rv = nghttp3_conn_resume_stream(conn, stream_id);
+      if(rv) {
+        return NGTCP2_ERR_CALLBACK_FAILURE;
+      }
+    }
+  }
+  return 0;
+}
+
+static nghttp3_ssize cb_h3_readfunction(nghttp3_conn *conn, int64_t stream_id,
+                                        nghttp3_vec *vec, size_t veccnt,
+                                        uint32_t *pflags, void *user_data,
+                                        void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct Curl_easy *data = stream_user_data;
+  size_t nread;
+  struct HTTP *stream = data->req.p.http;
+  (void)cf;
+  (void)conn;
+  (void)stream_id;
+  (void)user_data;
+  (void)veccnt;
+
+  if(data->set.postfields) {
+    vec[0].base = data->set.postfields;
+    vec[0].len = data->state.infilesize;
+    *pflags = NGHTTP3_DATA_FLAG_EOF;
+    return 1;
+  }
+
+  if(stream->upload_len && H3_SEND_SIZE <= stream->h3out->used) {
+    return NGHTTP3_ERR_WOULDBLOCK;
+  }
+
+  nread = CURLMIN(stream->upload_len, H3_SEND_SIZE - stream->h3out->used);
+  if(nread > 0) {
+    /* nghttp3 wants us to hold on to the data until it tells us it is okay to
+       delete it. Append the data at the end of the h3out buffer. Since we can
+       only return consecutive data, copy the amount that fits and the next
+       part comes in next invoke. */
+    struct h3out *out = stream->h3out;
+    if(nread + out->windex > H3_SEND_SIZE)
+      nread = H3_SEND_SIZE - out->windex;
+
+    memcpy(&out->buf[out->windex], stream->upload_mem, nread);
+
+    /* that's the chunk we return to nghttp3 */
+    vec[0].base = &out->buf[out->windex];
+    vec[0].len = nread;
+
+    out->windex += nread;
+    out->used += nread;
+
+    if(out->windex == H3_SEND_SIZE)
+      out->windex = 0; /* wrap */
+    stream->upload_mem += nread;
+    stream->upload_len -= nread;
+    if(data->state.infilesize != -1) {
+      stream->upload_left -= nread;
+      if(!stream->upload_left)
+        *pflags = NGHTTP3_DATA_FLAG_EOF;
+    }
+    DEBUGF(LOG_CF(data, cf, "cb_h3_readfunction %zd bytes%s (at %zd unacked)",
+                  nread, *pflags == NGHTTP3_DATA_FLAG_EOF?" EOF":"",
+                  out->used));
+  }
+  if(stream->upload_done && !stream->upload_len &&
+     (stream->upload_left <= 0)) {
+    DEBUGF(LOG_CF(data, cf, "cb_h3_readfunction sets EOF"));
+    *pflags = NGHTTP3_DATA_FLAG_EOF;
+    return nread ? 1 : 0;
+  }
+  else if(!nread) {
+    return NGHTTP3_ERR_WOULDBLOCK;
+  }
+  return 1;
+}
+
+/* Index where :authority header field will appear in request header
+   field list. */
+#define AUTHORITY_DST_IDX 3
+
+static CURLcode h3_stream_open(struct Curl_cfilter *cf,
+                               struct Curl_easy *data,
+                               const void *mem,
+                               size_t len)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  struct HTTP *stream = data->req.p.http;
+  size_t nheader;
+  CURLcode result = CURLE_OK;
+  nghttp3_nv *nva = NULL;
+  int64_t stream3_id;
+  int rc = 0;
+  struct h3out *h3out = NULL;
+  struct h2h3req *hreq = NULL;
+
+  rc = ngtcp2_conn_open_bidi_stream(ctx->qconn, &stream3_id, NULL);
+  if(rc) {
+    failf(data, "can get bidi streams");
+    goto fail;
+  }
+
+  stream->stream3_id = stream3_id;
+  stream->h3req = TRUE;
+  Curl_dyn_init(&stream->overflow, CURL_MAX_READ_SIZE);
+  stream->recv_buf_nonflow = 0;
+
+  result = Curl_pseudo_headers(data, mem, len, NULL, &hreq);
+  if(result)
+    goto fail;
+  nheader = hreq->entries;
+
+  nva = malloc(sizeof(nghttp3_nv) * nheader);
+  if(!nva) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto fail;
+  }
+  else {
+    unsigned int i;
+    for(i = 0; i < nheader; i++) {
+      nva[i].name = (unsigned char *)hreq->header[i].name;
+      nva[i].namelen = hreq->header[i].namelen;
+      nva[i].value = (unsigned char *)hreq->header[i].value;
+      nva[i].valuelen = hreq->header[i].valuelen;
+      nva[i].flags = NGHTTP3_NV_FLAG_NONE;
+    }
+  }
+
+  switch(data->state.httpreq) {
+  case HTTPREQ_POST:
+  case HTTPREQ_POST_FORM:
+  case HTTPREQ_POST_MIME:
+  case HTTPREQ_PUT: {
+    nghttp3_data_reader data_reader;
+    if(data->state.infilesize != -1)
+      stream->upload_left = data->state.infilesize;
+    else
+      /* data sending without specifying the data amount up front */
+      stream->upload_left = -1; /* unknown, but not zero */
+
+    data_reader.read_data = cb_h3_readfunction;
+
+    h3out = calloc(sizeof(struct h3out), 1);
+    if(!h3out) {
+      result = CURLE_OUT_OF_MEMORY;
+      goto fail;
+    }
+    stream->h3out = h3out;
+
+    rc = nghttp3_conn_submit_request(ctx->h3conn, stream->stream3_id,
+                                     nva, nheader, &data_reader, data);
+    if(rc)
+      goto fail;
+    break;
+  }
+  default:
+    stream->upload_left = 0; /* nothing left to send */
+    rc = nghttp3_conn_submit_request(ctx->h3conn, stream->stream3_id,
+                                     nva, nheader, NULL, data);
+    if(rc)
+      goto fail;
+    break;
+  }
+
+  Curl_safefree(nva);
+
+  infof(data, "Using HTTP/3 Stream ID: %" PRId64 " (easy handle %p)",
+        stream3_id, (void *)data);
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] opened for %s",
+                stream3_id, data->state.url));
+
+  Curl_pseudo_free(hreq);
+  return CURLE_OK;
+
+fail:
+  if(rc) {
+    switch(rc) {
+    case NGHTTP3_ERR_CONN_CLOSING:
+      DEBUGF(LOG_CF(data, cf, "h3sid[%"PRId64"] failed to send, "
+                    "connection is closing", stream->stream3_id));
+      result = CURLE_RECV_ERROR;
+      break;
+    default:
+      DEBUGF(LOG_CF(data, cf, "h3sid[%"PRId64"] failed to send -> %d (%s)",
+                    stream->stream3_id, rc, ngtcp2_strerror(rc)));
+      result = CURLE_SEND_ERROR;
+      break;
+    }
+  }
+  free(nva);
+  Curl_pseudo_free(hreq);
+  return result;
+}
+
+static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
+                              const void *buf, size_t len, CURLcode *err)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  ssize_t sent = 0;
+  struct HTTP *stream = data->req.p.http;
+  struct cf_call_data save;
+
+  CF_DATA_SAVE(save, cf, data);
+  DEBUGASSERT(cf->connected);
+  DEBUGASSERT(ctx->qconn);
+  DEBUGASSERT(ctx->h3conn);
+  *err = CURLE_OK;
+
+  if(stream->closed) {
+    *err = CURLE_HTTP3;
+    sent = -1;
+    goto out;
+  }
+
+  if(!stream->h3req) {
+    CURLcode result = h3_stream_open(cf, data, buf, len);
+    if(result) {
+      DEBUGF(LOG_CF(data, cf, "failed to open stream -> %d", result));
+      sent = -1;
+      goto out;
+    }
+    /* Assume that mem of length len only includes HTTP/1.1 style
+       header fields.  In other words, it does not contain request
+       body. */
+    sent = len;
+  }
+  else {
+    DEBUGF(LOG_CF(data, cf, "ngh3_stream_send() wants to send %zd bytes",
+                  len));
+    if(!stream->upload_len) {
+      stream->upload_mem = buf;
+      stream->upload_len = len;
+      (void)nghttp3_conn_resume_stream(ctx->h3conn, stream->stream3_id);
+    }
+    else {
+      *err = CURLE_AGAIN;
+      sent = -1;
+      goto out;
+    }
+  }
+
+  if(cf_flush_egress(cf, data)) {
+    *err = CURLE_SEND_ERROR;
+    sent = -1;
+    goto out;
+  }
+
+  /* Reset post upload buffer after resumed. */
+  if(stream->upload_mem) {
+    if(data->set.postfields) {
+      sent = len;
+    }
+    else {
+      sent = len - stream->upload_len;
+    }
+
+    stream->upload_mem = NULL;
+    stream->upload_len = 0;
+
+    if(sent == 0) {
+      *err = CURLE_AGAIN;
+      sent = -1;
+      goto out;
+    }
+  }
+out:
+  CF_DATA_RESTORE(cf, save);
+  return sent;
+}
+
+static CURLcode qng_verify_peer(struct Curl_cfilter *cf,
+                                struct Curl_easy *data)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OK;
+  const char *hostname, *disp_hostname;
+  int port;
+  char *snihost;
+
+  Curl_conn_get_host(data, cf->sockindex, &hostname, &disp_hostname, &port);
+  snihost = Curl_ssl_snihost(data, hostname, NULL);
+  if(!snihost)
+      return CURLE_PEER_FAILED_VERIFICATION;
+
+  cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
+  cf->conn->httpversion = 30;
+  cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX;
+
+  if(cf->conn->ssl_config.verifyhost) {
+#ifdef USE_OPENSSL
+    X509 *server_cert;
+    server_cert = SSL_get_peer_certificate(ctx->ssl);
+    if(!server_cert) {
+      return CURLE_PEER_FAILED_VERIFICATION;
+    }
+    result = Curl_ossl_verifyhost(data, cf->conn, server_cert);
+    X509_free(server_cert);
+    if(result)
+      return result;
+#elif defined(USE_GNUTLS)
+    result = Curl_gtls_verifyserver(data, ctx->gtls->session,
+                                    &cf->conn->ssl_config, &data->set.ssl,
+                                    hostname, disp_hostname,
+                                    data->set.str[STRING_SSL_PINNEDPUBLICKEY]);
+    if(result)
+      return result;
+#elif defined(USE_WOLFSSL)
+    if(wolfSSL_check_domain_name(ctx->ssl, snihost) == SSL_FAILURE)
+      return CURLE_PEER_FAILED_VERIFICATION;
+#endif
+    infof(data, "Verified certificate just fine");
+  }
+  else
+    infof(data, "Skipped certificate verification");
+#ifdef USE_OPENSSL
+  if(data->set.ssl.certinfo)
+    /* asked to gather certificate info */
+    (void)Curl_ossl_certchain(data, ctx->ssl);
+#endif
+  return result;
+}
+
+static CURLcode cf_process_ingress(struct Curl_cfilter *cf,
+                                   struct Curl_easy *data)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  ssize_t recvd;
+  int rv;
+  uint8_t buf[65536];
+  int bufsize = (int)sizeof(buf);
+  size_t pktcount = 0, total_recvd = 0;
+  struct sockaddr_storage remote_addr;
+  socklen_t remote_addrlen;
+  ngtcp2_path path;
+  ngtcp2_tstamp ts = timestamp();
+  ngtcp2_pkt_info pi = { 0 };
+
+  for(;;) {
+    remote_addrlen = sizeof(remote_addr);
+    while((recvd = recvfrom(ctx->q.sockfd, (char *)buf, bufsize, 0,
+                            (struct sockaddr *)&remote_addr,
+                            &remote_addrlen)) == -1 &&
+          SOCKERRNO == EINTR)
+      ;
+    if(recvd == -1) {
+      if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) {
+        DEBUGF(LOG_CF(data, cf, "ingress, recvfrom -> EAGAIN"));
+        goto out;
+      }
+      if(!cf->connected && SOCKERRNO == ECONNREFUSED) {
+        const char *r_ip;
+        int r_port;
+        Curl_cf_socket_peek(cf->next, data, NULL, NULL,
+                            &r_ip, &r_port, NULL, NULL);
+        failf(data, "ngtcp2: connection to %s port %u refused",
+              r_ip, r_port);
+        return CURLE_COULDNT_CONNECT;
+      }
+      failf(data, "ngtcp2: recvfrom() unexpectedly returned %zd (errno=%d)",
+                  recvd, SOCKERRNO);
+      return CURLE_RECV_ERROR;
+    }
+
+    if(recvd > 0 && !ctx->got_first_byte) {
+      ctx->first_byte_at = Curl_now();
+      ctx->got_first_byte = TRUE;
+    }
+
+    ++pktcount;
+    total_recvd += recvd;
+
+    ngtcp2_addr_init(&path.local, (struct sockaddr *)&ctx->q.local_addr,
+                     ctx->q.local_addrlen);
+    ngtcp2_addr_init(&path.remote, (struct sockaddr *)&remote_addr,
+                     remote_addrlen);
+
+    rv = ngtcp2_conn_read_pkt(ctx->qconn, &path, &pi, buf, recvd, ts);
+    if(rv) {
+      DEBUGF(LOG_CF(data, cf, "ingress, read_pkt -> %s",
+                    ngtcp2_strerror(rv)));
+      if(!ctx->last_error.error_code) {
+        if(rv == NGTCP2_ERR_CRYPTO) {
+          ngtcp2_connection_close_error_set_transport_error_tls_alert(
+              &ctx->last_error,
+              ngtcp2_conn_get_tls_alert(ctx->qconn), NULL, 0);
+        }
+        else {
+          ngtcp2_connection_close_error_set_transport_error_liberr(
+              &ctx->last_error, rv, NULL, 0);
+        }
+      }
+
+      if(rv == NGTCP2_ERR_CRYPTO)
+        /* this is a "TLS problem", but a failed certificate verification
+           is a common reason for this */
+        return CURLE_PEER_FAILED_VERIFICATION;
+      return CURLE_RECV_ERROR;
+    }
+  }
+
+out:
+  (void)pktcount;
+  (void)total_recvd;
+  DEBUGF(LOG_CF(data, cf, "ingress, recvd %zu packets with %zd bytes",
+                pktcount, total_recvd));
+  return CURLE_OK;
+}
+
+static CURLcode cf_flush_egress(struct Curl_cfilter *cf,
+                                struct Curl_easy *data)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  int rv;
+  size_t sent;
+  ngtcp2_ssize outlen;
+  uint8_t *outpos = ctx->q.pktbuf;
+  size_t max_udp_payload_size =
+      ngtcp2_conn_get_max_tx_udp_payload_size(ctx->qconn);
+  size_t path_max_udp_payload_size =
+      ngtcp2_conn_get_path_max_tx_udp_payload_size(ctx->qconn);
+  size_t max_pktcnt =
+      CURLMIN(MAX_PKT_BURST, ctx->q.pktbuflen / max_udp_payload_size);
+  size_t pktcnt = 0;
+  size_t gsolen = 0;  /* this disables gso until we have a clue */
+  ngtcp2_path_storage ps;
+  ngtcp2_tstamp ts = timestamp();
+  ngtcp2_tstamp expiry;
+  ngtcp2_duration timeout;
+  int64_t stream_id;
+  nghttp3_ssize veccnt;
+  int fin;
+  nghttp3_vec vec[16];
+  ngtcp2_ssize ndatalen;
+  uint32_t flags;
+  CURLcode curlcode;
+
+  rv = ngtcp2_conn_handle_expiry(ctx->qconn, ts);
+  if(rv) {
+    failf(data, "ngtcp2_conn_handle_expiry returned error: %s",
+          ngtcp2_strerror(rv));
+    ngtcp2_connection_close_error_set_transport_error_liberr(&ctx->last_error,
+                                                             rv, NULL, 0);
+    return CURLE_SEND_ERROR;
+  }
+
+  if(ctx->q.num_blocked_pkt) {
+    curlcode = vquic_send_blocked_pkt(cf, data, &ctx->q);
+    if(curlcode) {
+      if(curlcode == CURLE_AGAIN) {
+        Curl_expire(data, 1, EXPIRE_QUIC);
+        return CURLE_OK;
+      }
+      return curlcode;
+    }
+  }
+
+  ngtcp2_path_storage_zero(&ps);
+
+  for(;;) {
+    veccnt = 0;
+    stream_id = -1;
+    fin = 0;
+
+    if(ctx->h3conn && ngtcp2_conn_get_max_data_left(ctx->qconn)) {
+      veccnt = nghttp3_conn_writev_stream(ctx->h3conn, &stream_id, &fin, vec,
+                                          sizeof(vec) / sizeof(vec[0]));
+      if(veccnt < 0) {
+        failf(data, "nghttp3_conn_writev_stream returned error: %s",
+              nghttp3_strerror((int)veccnt));
+        ngtcp2_connection_close_error_set_application_error(
+            &ctx->last_error,
+            nghttp3_err_infer_quic_app_error_code((int)veccnt), NULL, 0);
+        return CURLE_SEND_ERROR;
+      }
+    }
+
+    flags = NGTCP2_WRITE_STREAM_FLAG_MORE |
+            (fin ? NGTCP2_WRITE_STREAM_FLAG_FIN : 0);
+    outlen = ngtcp2_conn_writev_stream(ctx->qconn, &ps.path, NULL, outpos,
+                                       max_udp_payload_size,
+                                       &ndatalen, flags, stream_id,
+                                       (const ngtcp2_vec *)vec, veccnt, ts);
+    if(outlen == 0) {
+      /* ngtcp2 does not want to send more packets, if the buffer is
+       * not empty, send that now */
+      if(outpos != ctx->q.pktbuf) {
+        curlcode = vquic_send_packet(cf, data, &ctx->q, ctx->q.pktbuf,
+                               outpos - ctx->q.pktbuf, gsolen, &sent);
+        if(curlcode) {
+          if(curlcode == CURLE_AGAIN) {
+            vquic_push_blocked_pkt(cf, &ctx->q, ctx->q.pktbuf + sent,
+                                   outpos - ctx->q.pktbuf - sent,
+                                   gsolen);
+            Curl_expire(data, 1, EXPIRE_QUIC);
+            return CURLE_OK;
+          }
+          return curlcode;
+        }
+      }
+      /* done for now */
+      goto out;
+    }
+    if(outlen < 0) {
+      switch(outlen) {
+      case NGTCP2_ERR_STREAM_DATA_BLOCKED:
+        assert(ndatalen == -1);
+        nghttp3_conn_block_stream(ctx->h3conn, stream_id);
+        continue;
+      case NGTCP2_ERR_STREAM_SHUT_WR:
+        assert(ndatalen == -1);
+        nghttp3_conn_shutdown_stream_write(ctx->h3conn, stream_id);
+        continue;
+      case NGTCP2_ERR_WRITE_MORE:
+        /* ngtcp2 wants to send more. update the flow of the stream whose data
+         * is in the buffer and continue */
+        assert(ndatalen >= 0);
+        rv = nghttp3_conn_add_write_offset(ctx->h3conn, stream_id, ndatalen);
+        if(rv) {
+          failf(data, "nghttp3_conn_add_write_offset returned error: %s\n",
+                nghttp3_strerror(rv));
+          return CURLE_SEND_ERROR;
+        }
+        continue;
+      default:
+        assert(ndatalen == -1);
+        failf(data, "ngtcp2_conn_writev_stream returned error: %s",
+              ngtcp2_strerror((int)outlen));
+        ngtcp2_connection_close_error_set_transport_error_liberr(
+            &ctx->last_error, (int)outlen, NULL, 0);
+        return CURLE_SEND_ERROR;
+      }
+    }
+    else if(ndatalen >= 0) {
+      /* ngtcp2 thinks it has added all it wants. Update the stream  */
+      rv = nghttp3_conn_add_write_offset(ctx->h3conn, stream_id, ndatalen);
+      if(rv) {
+        failf(data, "nghttp3_conn_add_write_offset returned error: %s\n",
+              nghttp3_strerror(rv));
+        return CURLE_SEND_ERROR;
+      }
+    }
+
+    /* advance to the end of the buffered packet data */
+    outpos += outlen;
+
+    if(pktcnt == 0) {
+      /* first packet buffer chunk. use this as gsolen. It's how ngtcp2
+       * indicates the intended segment size. */
+      gsolen = outlen;
+    }
+    else if((size_t)outlen > gsolen ||
+            (gsolen > path_max_udp_payload_size && (size_t)outlen != gsolen)) {
+      /* Packet larger than path_max_udp_payload_size is PMTUD probe
+         packet and it might not be sent because of EMSGSIZE. Send
+         them separately to minimize the loss. */
+      /* send the pktbuf *before* the last addition */
+      curlcode = vquic_send_packet(cf, data, &ctx->q, ctx->q.pktbuf,
+                             outpos - outlen - ctx->q.pktbuf, gsolen, &sent);
+      if(curlcode) {
+        if(curlcode == CURLE_AGAIN) {
+          /* blocked, add the pktbuf *before* and *at* the last addition
+           * separately to the blocked packages */
+          vquic_push_blocked_pkt(cf, &ctx->q, ctx->q.pktbuf + sent,
+                           outpos - outlen - ctx->q.pktbuf - sent, gsolen);
+          vquic_push_blocked_pkt(cf, &ctx->q, outpos - outlen, outlen, outlen);
+          Curl_expire(data, 1, EXPIRE_QUIC);
+          return CURLE_OK;
+        }
+        return curlcode;
+      }
+      /* send the pktbuf *at* the last addition */
+      curlcode = vquic_send_packet(cf, data, &ctx->q, outpos - outlen, outlen,
+                                   outlen, &sent);
+      if(curlcode) {
+        if(curlcode == CURLE_AGAIN) {
+          assert(0 == sent);
+          vquic_push_blocked_pkt(cf, &ctx->q, outpos - outlen, outlen, outlen);
+          Curl_expire(data, 1, EXPIRE_QUIC);
+          return CURLE_OK;
+        }
+        return curlcode;
+      }
+      /* pktbuf has been completely sent */
+      pktcnt = 0;
+      outpos = ctx->q.pktbuf;
+      continue;
+    }
+
+    if(++pktcnt >= max_pktcnt || (size_t)outlen < gsolen) {
+      /* enough packets or last one is shorter than the intended
+       * segment size, indicating that it is time to send. */
+      curlcode = vquic_send_packet(cf, data, &ctx->q, ctx->q.pktbuf,
+                                   outpos - ctx->q.pktbuf, gsolen, &sent);
+      if(curlcode) {
+        if(curlcode == CURLE_AGAIN) {
+          vquic_push_blocked_pkt(cf, &ctx->q, ctx->q.pktbuf + sent,
+                                 outpos - ctx->q.pktbuf - sent, gsolen);
+          Curl_expire(data, 1, EXPIRE_QUIC);
+          return CURLE_OK;
+        }
+        return curlcode;
+      }
+      /* pktbuf has been completely sent */
+      pktcnt = 0;
+      outpos = ctx->q.pktbuf;
+    }
+  }
+
+out:
+  /* non-errored exit. check when we should run again. */
+  expiry = ngtcp2_conn_get_expiry(ctx->qconn);
+  if(expiry != UINT64_MAX) {
+    if(expiry <= ts) {
+      timeout = 0;
+    }
+    else {
+      timeout = expiry - ts;
+      if(timeout % NGTCP2_MILLISECONDS) {
+        timeout += NGTCP2_MILLISECONDS;
+      }
+    }
+    Curl_expire(data, timeout / NGTCP2_MILLISECONDS, EXPIRE_QUIC);
+  }
+
+  return CURLE_OK;
+}
+
+/*
+ * Called from transfer.c:data_pending to know if we should keep looping
+ * to receive more data from the connection.
+ */
+static bool cf_ngtcp2_data_pending(struct Curl_cfilter *cf,
+                                   const struct Curl_easy *data)
+{
+  /* We may have received more data than we're able to hold in the receive
+     buffer and allocated an overflow buffer. Since it's possible that
+     there's no more data coming on the socket, we need to keep reading
+     until the overflow buffer is empty. */
+  const struct HTTP *stream = data->req.p.http;
+  (void)cf;
+  return Curl_dyn_len(&stream->overflow) > 0;
+}
+
+static CURLcode cf_ngtcp2_data_event(struct Curl_cfilter *cf,
+                                     struct Curl_easy *data,
+                                     int event, int arg1, void *arg2)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OK;
+  struct cf_call_data save;
+
+  CF_DATA_SAVE(save, cf, data);
+  (void)arg1;
+  (void)arg2;
+  switch(event) {
+  case CF_CTRL_DATA_DONE: {
+    struct HTTP *stream = data->req.p.http;
+    Curl_dyn_free(&stream->overflow);
+    free(stream->h3out);
+    break;
+  }
+  case CF_CTRL_DATA_DONE_SEND: {
+    struct HTTP *stream = data->req.p.http;
+    stream->upload_done = TRUE;
+    (void)nghttp3_conn_resume_stream(ctx->h3conn, stream->stream3_id);
+    break;
+  }
+  case CF_CTRL_DATA_IDLE:
+    if(timestamp() >= ngtcp2_conn_get_expiry(ctx->qconn)) {
+      if(cf_flush_egress(cf, data)) {
+        result = CURLE_SEND_ERROR;
+      }
+    }
+    break;
+  default:
+    break;
+  }
+  CF_DATA_RESTORE(cf, save);
+  return result;
+}
+
+static void cf_ngtcp2_ctx_clear(struct cf_ngtcp2_ctx *ctx)
+{
+  struct cf_call_data save = ctx->call_data;
+
+  if(ctx->qlogfd != -1) {
+    close(ctx->qlogfd);
+  }
+#ifdef USE_OPENSSL
+  if(ctx->ssl)
+    SSL_free(ctx->ssl);
+  if(ctx->sslctx)
+    SSL_CTX_free(ctx->sslctx);
+#elif defined(USE_GNUTLS)
+  if(ctx->gtls) {
+    if(ctx->gtls->cred)
+      gnutls_certificate_free_credentials(ctx->gtls->cred);
+    if(ctx->gtls->session)
+      gnutls_deinit(ctx->gtls->session);
+    free(ctx->gtls);
+  }
+#elif defined(USE_WOLFSSL)
+  if(ctx->ssl)
+    wolfSSL_free(ctx->ssl);
+  if(ctx->sslctx)
+    wolfSSL_CTX_free(ctx->sslctx);
+#endif
+  vquic_ctx_free(&ctx->q);
+  if(ctx->h3conn)
+    nghttp3_conn_del(ctx->h3conn);
+  if(ctx->qconn)
+    ngtcp2_conn_del(ctx->qconn);
+
+  memset(ctx, 0, sizeof(*ctx));
+  ctx->qlogfd = -1;
+  ctx->call_data = save;
+}
+
+static void cf_ngtcp2_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  struct cf_call_data save;
+
+  CF_DATA_SAVE(save, cf, data);
+  if(ctx && ctx->qconn) {
+    char buffer[NGTCP2_MAX_UDP_PAYLOAD_SIZE];
+    ngtcp2_tstamp ts;
+    ngtcp2_ssize rc;
+
+    DEBUGF(LOG_CF(data, cf, "close"));
+    ts = timestamp();
+    rc = ngtcp2_conn_write_connection_close(ctx->qconn, NULL, /* path */
+                                            NULL, /* pkt_info */
+                                            (uint8_t *)buffer, sizeof(buffer),
+                                            &ctx->last_error, ts);
+    if(rc > 0) {
+      while((send(ctx->q.sockfd, buffer, (SEND_TYPE_ARG3)rc, 0) == -1) &&
+            SOCKERRNO == EINTR);
+    }
+
+    cf_ngtcp2_ctx_clear(ctx);
+  }
+
+  cf->connected = FALSE;
+  CF_DATA_RESTORE(cf, save);
+}
+
+static void cf_ngtcp2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  struct cf_call_data save;
+
+  CF_DATA_SAVE(save, cf, data);
+  DEBUGF(LOG_CF(data, cf, "destroy"));
+  if(ctx) {
+    cf_ngtcp2_ctx_clear(ctx);
+    free(ctx);
+  }
+  cf->ctx = NULL;
+  /* No CF_DATA_RESTORE(cf, save) possible */
+  (void)save;
+}
+
+/*
+ * Might be called twice for happy eyeballs.
+ */
+static CURLcode cf_connect_start(struct Curl_cfilter *cf,
+                                 struct Curl_easy *data)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  int rc;
+  int rv;
+  CURLcode result;
+  const struct Curl_sockaddr_ex *sockaddr;
+  int qfd;
+
+  ctx->version = NGTCP2_PROTO_VER_MAX;
+#ifdef USE_OPENSSL
+  result = quic_ssl_ctx(&ctx->sslctx, cf, data);
+  if(result)
+    return result;
+
+  result = quic_set_client_cert(cf, data);
+  if(result)
+    return result;
+#elif defined(USE_WOLFSSL)
+  result = quic_ssl_ctx(&ctx->sslctx, cf, data);
+  if(result)
+    return result;
+#endif
+
+  result = quic_init_ssl(cf, data);
+  if(result)
+    return result;
+
+  ctx->dcid.datalen = NGTCP2_MAX_CIDLEN;
+  result = Curl_rand(data, ctx->dcid.data, NGTCP2_MAX_CIDLEN);
+  if(result)
+    return result;
+
+  ctx->scid.datalen = NGTCP2_MAX_CIDLEN;
+  result = Curl_rand(data, ctx->scid.data, NGTCP2_MAX_CIDLEN);
+  if(result)
+    return result;
+
+  (void)Curl_qlogdir(data, ctx->scid.data, NGTCP2_MAX_CIDLEN, &qfd);
+  ctx->qlogfd = qfd; /* -1 if failure above */
+  quic_settings(ctx, data);
+
+  result = vquic_ctx_init(&ctx->q,
+                          NGTCP2_MAX_PMTUD_UDP_PAYLOAD_SIZE * MAX_PKT_BURST);
+  if(result)
+    return result;
+
+  Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd,
+                      &sockaddr, NULL, NULL, NULL, NULL);
+  ctx->q.local_addrlen = sizeof(ctx->q.local_addr);
+  rv = getsockname(ctx->q.sockfd, (struct sockaddr *)&ctx->q.local_addr,
+                   &ctx->q.local_addrlen);
+  if(rv == -1)
+    return CURLE_QUIC_CONNECT_ERROR;
+
+  ngtcp2_addr_init(&ctx->connected_path.local,
+                   (struct sockaddr *)&ctx->q.local_addr,
+                   ctx->q.local_addrlen);
+  ngtcp2_addr_init(&ctx->connected_path.remote,
+                   &sockaddr->sa_addr, sockaddr->addrlen);
+
+  rc = ngtcp2_conn_client_new(&ctx->qconn, &ctx->dcid, &ctx->scid,
+                              &ctx->connected_path,
+                              NGTCP2_PROTO_VER_V1, &ng_callbacks,
+                              &ctx->settings, &ctx->transport_params,
+                              NULL, cf);
+  if(rc)
+    return CURLE_QUIC_CONNECT_ERROR;
+
+#ifdef USE_GNUTLS
+  ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->gtls->session);
+#else
+  ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->ssl);
+#endif
+
+  ngtcp2_connection_close_error_default(&ctx->last_error);
+
+  ctx->conn_ref.get_conn = get_conn;
+  ctx->conn_ref.user_data = cf;
+
+  return CURLE_OK;
+}
+
+static CURLcode cf_ngtcp2_connect(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  bool blocking, bool *done)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OK;
+  struct cf_call_data save;
+  struct curltime now;
+
+  if(cf->connected) {
+    *done = TRUE;
+    return CURLE_OK;
+  }
+
+  /* Connect the UDP filter first */
+  if(!cf->next->connected) {
+    result = Curl_conn_cf_connect(cf->next, data, blocking, done);
+    if(result || !*done)
+      return result;
+  }
+
+  *done = FALSE;
+  now = Curl_now();
+
+  CF_DATA_SAVE(save, cf, data);
+
+  if(ctx->reconnect_at.tv_sec && Curl_timediff(now, ctx->reconnect_at) < 0) {
+    /* Not time yet to attempt the next connect */
+    DEBUGF(LOG_CF(data, cf, "waiting for reconnect time"));
+    goto out;
+  }
+
+  if(!ctx->qconn) {
+    ctx->started_at = now;
+    result = cf_connect_start(cf, data);
+    if(result)
+      goto out;
+    result = cf_flush_egress(cf, data);
+    /* we do not expect to be able to recv anything yet */
+    goto out;
+  }
+
+  result = cf_process_ingress(cf, data);
+  if(result)
+    goto out;
+
+  result = cf_flush_egress(cf, data);
+  if(result)
+    goto out;
+
+  if(ngtcp2_conn_get_handshake_completed(ctx->qconn)) {
+    ctx->handshake_at = now;
+    DEBUGF(LOG_CF(data, cf, "handshake complete after %dms",
+           (int)Curl_timediff(now, ctx->started_at)));
+    result = qng_verify_peer(cf, data);
+    if(!result) {
+      DEBUGF(LOG_CF(data, cf, "peer verified"));
+      cf->connected = TRUE;
+      cf->conn->alpn = CURL_HTTP_VERSION_3;
+      *done = TRUE;
+      connkeep(cf->conn, "HTTP/3 default");
+    }
+  }
+
+out:
+  if(result == CURLE_RECV_ERROR && ctx->qconn &&
+     ngtcp2_conn_is_in_draining_period(ctx->qconn)) {
+    /* When a QUIC server instance is shutting down, it may send us a
+     * CONNECTION_CLOSE right away. Our connection then enters the DRAINING
+     * state.
+     * This may be a stopping of the service or it may be that the server
+     * is reloading and a new instance will start serving soon.
+     * In any case, we tear down our socket and start over with a new one.
+     * We re-open the underlying UDP cf right now, but do not start
+     * connecting until called again.
+     */
+    int reconn_delay_ms = 200;
+
+    DEBUGF(LOG_CF(data, cf, "connect, remote closed, reconnect after %dms",
+                  reconn_delay_ms));
+    Curl_conn_cf_close(cf->next, data);
+    cf_ngtcp2_ctx_clear(ctx);
+    result = Curl_conn_cf_connect(cf->next, data, FALSE, done);
+    if(!result && *done) {
+      *done = FALSE;
+      ctx->reconnect_at = now;
+      ctx->reconnect_at.tv_usec += reconn_delay_ms * 1000;
+      Curl_expire(data, reconn_delay_ms, EXPIRE_QUIC);
+      result = CURLE_OK;
+    }
+  }
+
+#ifndef CURL_DISABLE_VERBOSE_STRINGS
+  if(result) {
+    const char *r_ip;
+    int r_port;
+
+    Curl_cf_socket_peek(cf->next, data, NULL, NULL,
+                        &r_ip, &r_port, NULL, NULL);
+    infof(data, "QUIC connect to %s port %u failed: %s",
+          r_ip, r_port, curl_easy_strerror(result));
+  }
+#endif
+  DEBUGF(LOG_CF(data, cf, "connect -> %d, done=%d", result, *done));
+  CF_DATA_RESTORE(cf, save);
+  return result;
+}
+
+static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                int query, int *pres1, void *pres2)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  struct cf_call_data save;
+
+  switch(query) {
+  case CF_QUERY_MAX_CONCURRENT: {
+    const ngtcp2_transport_params *rp;
+    DEBUGASSERT(pres1);
+
+    CF_DATA_SAVE(save, cf, data);
+    rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn);
+    if(rp)
+      *pres1 = (rp->initial_max_streams_bidi > INT_MAX)?
+                 INT_MAX : (int)rp->initial_max_streams_bidi;
+    else  /* not arrived yet? */
+      *pres1 = Curl_multi_max_concurrent_streams(data->multi);
+    DEBUGF(LOG_CF(data, cf, "query max_conncurrent -> %d", *pres1));
+    CF_DATA_RESTORE(cf, save);
+    return CURLE_OK;
+  }
+  case CF_QUERY_CONNECT_REPLY_MS:
+    if(ctx->got_first_byte) {
+      timediff_t ms = Curl_timediff(ctx->first_byte_at, ctx->started_at);
+      *pres1 = (ms < INT_MAX)? (int)ms : INT_MAX;
+    }
+    else
+      *pres1 = -1;
+    return CURLE_OK;
+  case CF_QUERY_TIMER_CONNECT: {
+    struct curltime *when = pres2;
+    if(ctx->got_first_byte)
+      *when = ctx->first_byte_at;
+    return CURLE_OK;
+  }
+  case CF_QUERY_TIMER_APPCONNECT: {
+    struct curltime *when = pres2;
+    if(cf->connected)
+      *when = ctx->handshake_at;
+    return CURLE_OK;
+  }
+  default:
+    break;
+  }
+  return cf->next?
+    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
+    CURLE_UNKNOWN_OPTION;
+}
+
+static bool cf_ngtcp2_conn_is_alive(struct Curl_cfilter *cf,
+                                    struct Curl_easy *data,
+                                    bool *input_pending)
+{
+  bool alive = TRUE;
+
+  *input_pending = FALSE;
+  if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending))
+    return FALSE;
+
+  if(*input_pending) {
+    /* This happens before we've sent off a request and the connection is
+       not in use by any other transfer, there shouldn't be any data here,
+       only "protocol frames" */
+    *input_pending = FALSE;
+    Curl_attach_connection(data, cf->conn);
+    if(cf_process_ingress(cf, data))
+      alive = FALSE;
+    else {
+      alive = TRUE;
+    }
+    Curl_detach_connection(data);
+  }
+
+  return alive;
+}
+
+struct Curl_cftype Curl_cft_http3 = {
+  "HTTP/3",
+  CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX,
+  0,
+  cf_ngtcp2_destroy,
+  cf_ngtcp2_connect,
+  cf_ngtcp2_close,
+  Curl_cf_def_get_host,
+  cf_ngtcp2_get_select_socks,
+  cf_ngtcp2_data_pending,
+  cf_ngtcp2_send,
+  cf_ngtcp2_recv,
+  cf_ngtcp2_data_event,
+  cf_ngtcp2_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  cf_ngtcp2_query,
+};
+
+CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf,
+                               struct Curl_easy *data,
+                               struct connectdata *conn,
+                               const struct Curl_addrinfo *ai)
+{
+  struct cf_ngtcp2_ctx *ctx = NULL;
+  struct Curl_cfilter *cf = NULL, *udp_cf = NULL;
+  CURLcode result;
+
+  (void)data;
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+  ctx->qlogfd = -1;
+  cf_ngtcp2_ctx_clear(ctx);
+
+  result = Curl_cf_create(&cf, &Curl_cft_http3, ctx);
+  if(result)
+    goto out;
+
+  result = Curl_cf_udp_create(&udp_cf, data, conn, ai, TRNSPRT_QUIC);
+  if(result)
+    goto out;
+
+  cf->conn = conn;
+  udp_cf->conn = cf->conn;
+  udp_cf->sockindex = cf->sockindex;
+  cf->next = udp_cf;
+
+out:
+  *pcf = (!result)? cf : NULL;
+  if(result) {
+    if(udp_cf)
+      Curl_conn_cf_discard(udp_cf, data);
+    Curl_safefree(cf);
+    Curl_safefree(ctx);
+  }
+  return result;
+}
+
+bool Curl_conn_is_ngtcp2(const struct Curl_easy *data,
+                         const struct connectdata *conn,
+                         int sockindex)
+{
+  struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL;
+
+  (void)data;
+  for(; cf; cf = cf->next) {
+    if(cf->cft == &Curl_cft_http3)
+      return TRUE;
+    if(cf->cft->flags & CF_TYPE_IP_CONNECT)
+      return FALSE;
+  }
+  return FALSE;
+}
+
+#endif
diff --git a/Utilities/cmcurl/lib/vquic/curl_ngtcp2.h b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.h
new file mode 100644
index 0000000..8813ec9
--- /dev/null
+++ b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.h
@@ -0,0 +1,61 @@
+#ifndef HEADER_CURL_VQUIC_CURL_NGTCP2_H
+#define HEADER_CURL_VQUIC_CURL_NGTCP2_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef USE_NGTCP2
+
+#ifdef HAVE_NETINET_UDP_H
+#include <netinet/udp.h>
+#endif
+
+#include <ngtcp2/ngtcp2_crypto.h>
+#include <nghttp3/nghttp3.h>
+#ifdef USE_OPENSSL
+#include <openssl/ssl.h>
+#elif defined(USE_WOLFSSL)
+#include <wolfssl/options.h>
+#include <wolfssl/ssl.h>
+#include <wolfssl/quic.h>
+#endif
+
+struct Curl_cfilter;
+
+#include "urldata.h"
+
+void Curl_ngtcp2_ver(char *p, size_t len);
+
+CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf,
+                             struct Curl_easy *data,
+                             struct connectdata *conn,
+                             const struct Curl_addrinfo *ai);
+
+bool Curl_conn_is_ngtcp2(const struct Curl_easy *data,
+                         const struct connectdata *conn,
+                         int sockindex);
+#endif
+
+#endif /* HEADER_CURL_VQUIC_CURL_NGTCP2_H */
diff --git a/Utilities/cmcurl/lib/vquic/curl_quiche.c b/Utilities/cmcurl/lib/vquic/curl_quiche.c
new file mode 100644
index 0000000..87a221c
--- /dev/null
+++ b/Utilities/cmcurl/lib/vquic/curl_quiche.c
@@ -0,0 +1,1464 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef USE_QUICHE
+#include <quiche.h>
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+#include "urldata.h"
+#include "cfilters.h"
+#include "cf-socket.h"
+#include "sendf.h"
+#include "strdup.h"
+#include "rand.h"
+#include "strcase.h"
+#include "multiif.h"
+#include "connect.h"
+#include "progress.h"
+#include "strerror.h"
+#include "vquic.h"
+#include "vquic_int.h"
+#include "curl_quiche.h"
+#include "transfer.h"
+#include "h2h3.h"
+#include "vtls/openssl.h"
+#include "vtls/keylog.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+
+#define QUIC_MAX_STREAMS (256*1024)
+#define QUIC_MAX_DATA (1*1024*1024)
+#define QUIC_IDLE_TIMEOUT (60 * 1000) /* milliseconds */
+
+/* how many UDP packets to send max in one call */
+#define MAX_PKT_BURST 10
+#define MAX_UDP_PAYLOAD_SIZE  1452
+
+/*
+ * Store quiche version info in this buffer.
+ */
+void Curl_quiche_ver(char *p, size_t len)
+{
+  (void)msnprintf(p, len, "quiche/%s", quiche_version());
+}
+
+static void keylog_callback(const SSL *ssl, const char *line)
+{
+  (void)ssl;
+  Curl_tls_keylog_write_line(line);
+}
+
+static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data)
+{
+  SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method());
+
+  SSL_CTX_set_alpn_protos(ssl_ctx,
+                          (const uint8_t *)QUICHE_H3_APPLICATION_PROTOCOL,
+                          sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1);
+
+  SSL_CTX_set_default_verify_paths(ssl_ctx);
+
+  /* Open the file if a TLS or QUIC backend has not done this before. */
+  Curl_tls_keylog_open();
+  if(Curl_tls_keylog_enabled()) {
+    SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback);
+  }
+
+  {
+    struct connectdata *conn = data->conn;
+    if(conn->ssl_config.verifypeer) {
+      const char * const ssl_cafile = conn->ssl_config.CAfile;
+      const char * const ssl_capath = conn->ssl_config.CApath;
+      if(ssl_cafile || ssl_capath) {
+        SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
+        /* tell OpenSSL where to find CA certificates that are used to verify
+           the server's certificate. */
+        if(!SSL_CTX_load_verify_locations(ssl_ctx, ssl_cafile, ssl_capath)) {
+          /* Fail if we insist on successfully verifying the server. */
+          failf(data, "error setting certificate verify locations:"
+                "  CAfile: %s CApath: %s",
+                ssl_cafile ? ssl_cafile : "none",
+                ssl_capath ? ssl_capath : "none");
+          return NULL;
+        }
+        infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
+        infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
+      }
+#ifdef CURL_CA_FALLBACK
+      else {
+        /* verifying the peer without any CA certificates won't work so
+           use openssl's built-in default as fallback */
+        SSL_CTX_set_default_verify_paths(ssl_ctx);
+      }
+#endif
+    }
+  }
+  return ssl_ctx;
+}
+
+struct quic_handshake {
+  char *buf;       /* pointer to the buffer */
+  size_t alloclen; /* size of allocation */
+  size_t len;      /* size of content in buffer */
+  size_t nread;    /* how many bytes have been read */
+};
+
+struct h3_event_node {
+  struct h3_event_node *next;
+  quiche_h3_event *ev;
+};
+
+struct cf_quiche_ctx {
+  struct cf_quic_ctx q;
+  quiche_conn *qconn;
+  quiche_config *cfg;
+  quiche_h3_conn *h3c;
+  quiche_h3_config *h3config;
+  uint8_t scid[QUICHE_MAX_CONN_ID_LEN];
+  SSL_CTX *sslctx;
+  SSL *ssl;
+  struct curltime started_at;        /* time the current attempt started */
+  struct curltime handshake_at;      /* time connect handshake finished */
+  struct curltime first_byte_at;     /* when first byte was recvd */
+  struct curltime reconnect_at;      /* time the next attempt should start */
+  BIT(goaway);                       /* got GOAWAY from server */
+  BIT(got_first_byte);               /* if first byte was received */
+};
+
+
+#ifdef DEBUG_QUICHE
+static void quiche_debug_log(const char *line, void *argp)
+{
+  (void)argp;
+  fprintf(stderr, "%s\n", line);
+}
+#endif
+
+static void h3_clear_pending(struct Curl_easy *data)
+{
+  struct HTTP *stream = data->req.p.http;
+
+  if(stream->pending) {
+    struct h3_event_node *node, *next;
+    for(node = stream->pending; node; node = next) {
+      next = node->next;
+      quiche_h3_event_free(node->ev);
+      free(node);
+    }
+    stream->pending = NULL;
+  }
+}
+
+static void cf_quiche_ctx_clear(struct cf_quiche_ctx *ctx)
+{
+  if(ctx) {
+    vquic_ctx_free(&ctx->q);
+    if(ctx->qconn)
+      quiche_conn_free(ctx->qconn);
+    if(ctx->h3config)
+      quiche_h3_config_free(ctx->h3config);
+    if(ctx->h3c)
+      quiche_h3_conn_free(ctx->h3c);
+    if(ctx->cfg)
+      quiche_config_free(ctx->cfg);
+    memset(ctx, 0, sizeof(*ctx));
+  }
+}
+
+static void notify_drain(struct Curl_cfilter *cf,
+                         struct Curl_easy *data)
+{
+  (void)cf;
+  data->state.drain = 1;
+  Curl_expire(data, 0, EXPIRE_RUN_NOW);
+}
+
+static CURLcode h3_add_event(struct Curl_cfilter *cf,
+                             struct Curl_easy *data,
+                             int64_t stream3_id, quiche_h3_event *ev)
+{
+  struct Curl_easy *mdata;
+  struct h3_event_node *node, **pnext;
+
+  DEBUGASSERT(data->multi);
+  for(mdata = data->multi->easyp; mdata; mdata = mdata->next) {
+    if(mdata->req.p.http && mdata->req.p.http->stream3_id == stream3_id) {
+      break;
+    }
+  }
+
+  if(!mdata) {
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] event discarded, easy handle "
+                  "not found", stream3_id));
+    quiche_h3_event_free(ev);
+    return CURLE_OK;
+  }
+
+  node = calloc(sizeof(*node), 1);
+  if(!node) {
+    quiche_h3_event_free(ev);
+    return CURLE_OUT_OF_MEMORY;
+  }
+  node->ev = ev;
+  /* append to process them in order of arrival */
+  pnext = &mdata->req.p.http->pending;
+  while(*pnext) {
+    pnext = &((*pnext)->next);
+  }
+  *pnext = node;
+  notify_drain(cf, mdata);
+  return CURLE_OK;
+}
+
+struct h3h1header {
+  char *dest;
+  size_t destlen; /* left to use */
+  size_t nlen; /* used */
+};
+
+static int cb_each_header(uint8_t *name, size_t name_len,
+                          uint8_t *value, size_t value_len,
+                          void *argp)
+{
+  struct h3h1header *headers = (struct h3h1header *)argp;
+  size_t olen = 0;
+
+  if((name_len == 7) && !strncmp(H2H3_PSEUDO_STATUS, (char *)name, 7)) {
+    msnprintf(headers->dest,
+              headers->destlen, "HTTP/3 %.*s \r\n",
+              (int) value_len, value);
+  }
+  else if(!headers->nlen) {
+    return CURLE_HTTP3;
+  }
+  else {
+    msnprintf(headers->dest,
+              headers->destlen, "%.*s: %.*s\r\n",
+              (int)name_len, name, (int) value_len, value);
+  }
+  olen = strlen(headers->dest);
+  headers->destlen -= olen;
+  headers->nlen += olen;
+  headers->dest += olen;
+  return 0;
+}
+
+static ssize_t cf_recv_body(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                char *buf, size_t len,
+                                CURLcode *err)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+  struct HTTP *stream = data->req.p.http;
+  ssize_t nread;
+  size_t offset = 0;
+
+  if(!stream->firstbody) {
+    /* add a header-body separator CRLF */
+    offset = 2;
+  }
+  nread = quiche_h3_recv_body(ctx->h3c, ctx->qconn, stream->stream3_id,
+                              (unsigned char *)buf + offset, len - offset);
+  if(nread >= 0) {
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"][DATA] len=%zd",
+                  stream->stream3_id, nread));
+    if(!stream->firstbody) {
+      stream->firstbody = TRUE;
+      buf[0] = '\r';
+      buf[1] = '\n';
+      nread += offset;
+    }
+  }
+  else if(nread == -1) {
+    *err = CURLE_AGAIN;
+    stream->h3_recving_data = FALSE;
+  }
+  else {
+    failf(data, "Error %zd in HTTP/3 response body for stream[%"PRId64"]",
+          nread, stream->stream3_id);
+    stream->closed = TRUE;
+    stream->reset = TRUE;
+    streamclose(cf->conn, "Reset of stream");
+    stream->h3_recving_data = FALSE;
+    nread = -1;
+    *err = stream->h3_got_header? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR;
+  }
+  return nread;
+}
+
+#ifdef DEBUGBUILD
+static const char *cf_ev_name(quiche_h3_event *ev)
+{
+  switch(quiche_h3_event_type(ev)) {
+  case QUICHE_H3_EVENT_HEADERS:
+    return "HEADERS";
+  case QUICHE_H3_EVENT_DATA:
+    return "DATA";
+  case QUICHE_H3_EVENT_RESET:
+    return "RESET";
+  case QUICHE_H3_EVENT_FINISHED:
+    return "FINISHED";
+  case QUICHE_H3_EVENT_GOAWAY:
+    return "GOAWAY";
+  default:
+    return "Unknown";
+  }
+}
+#else
+#define cf_ev_name(x)   ""
+#endif
+
+static ssize_t h3_process_event(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                char *buf, size_t len,
+                                int64_t stream3_id,
+                                quiche_h3_event *ev,
+                                CURLcode *err)
+{
+  struct HTTP *stream = data->req.p.http;
+  ssize_t recvd = 0;
+  int rc;
+  struct h3h1header headers;
+
+  DEBUGASSERT(stream3_id == stream->stream3_id);
+
+  *err = CURLE_OK;
+  switch(quiche_h3_event_type(ev)) {
+  case QUICHE_H3_EVENT_HEADERS:
+    stream->h3_got_header = TRUE;
+    headers.dest = buf;
+    headers.destlen = len;
+    headers.nlen = 0;
+    rc = quiche_h3_event_for_each_header(ev, cb_each_header, &headers);
+    if(rc) {
+      failf(data, "Error %d in HTTP/3 response header for stream[%"PRId64"]",
+            rc, stream3_id);
+      *err = CURLE_RECV_ERROR;
+      recvd = -1;
+      break;
+    }
+    recvd = headers.nlen;
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"][HEADERS] len=%zd",
+                  stream3_id, recvd));
+    break;
+
+  case QUICHE_H3_EVENT_DATA:
+    DEBUGASSERT(!stream->closed);
+    stream->h3_recving_data = TRUE;
+    recvd = cf_recv_body(cf, data, buf, len, err);
+    if(recvd < 0) {
+      if(*err != CURLE_AGAIN)
+        return -1;
+      recvd = 0;
+    }
+    break;
+
+  case QUICHE_H3_EVENT_RESET:
+      DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"][RESET]", stream3_id));
+    stream->closed = TRUE;
+    stream->reset = TRUE;
+    /* streamclose(cf->conn, "Reset of stream");*/
+    stream->h3_recving_data = FALSE;
+    break;
+
+  case QUICHE_H3_EVENT_FINISHED:
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"][FINISHED]", stream3_id));
+    stream->closed = TRUE;
+    /* streamclose(cf->conn, "End of stream");*/
+    stream->h3_recving_data = FALSE;
+    break;
+
+  case QUICHE_H3_EVENT_GOAWAY:
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"][GOAWAY]", stream3_id));
+    break;
+
+  default:
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] recv, unhandled event %d",
+                  stream3_id, quiche_h3_event_type(ev)));
+    break;
+  }
+  return recvd;
+}
+
+static ssize_t h3_process_pending(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  char *buf, size_t len,
+                                  CURLcode *err)
+{
+  struct HTTP *stream = data->req.p.http;
+  struct h3_event_node *node = stream->pending, **pnext = &stream->pending;
+  ssize_t recvd = 0, erecvd;
+
+  *err = CURLE_OK;
+  DEBUGASSERT(stream);
+  while(node && len) {
+    erecvd = h3_process_event(cf, data, buf, len,
+                              stream->stream3_id, node->ev, err);
+    quiche_h3_event_free(node->ev);
+    *pnext = node->next;
+    free(node);
+    node = *pnext;
+    if(erecvd < 0) {
+      DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] process event -> %d",
+                    stream->stream3_id, *err));
+      return erecvd;
+    }
+    recvd += erecvd;
+    *err = CURLE_OK;
+    buf += erecvd;
+    len -= erecvd;
+  }
+  return recvd;
+}
+
+static CURLcode cf_process_ingress(struct Curl_cfilter *cf,
+                                   struct Curl_easy *data)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+  int64_t stream3_id = data->req.p.http? data->req.p.http->stream3_id : -1;
+  uint8_t buf[65536];
+  int bufsize = (int)sizeof(buf);
+  struct sockaddr_storage remote_addr;
+  socklen_t remote_addrlen;
+  quiche_recv_info recv_info;
+  ssize_t recvd, nread;
+  ssize_t total = 0, pkts = 0;
+
+  DEBUGASSERT(ctx->qconn);
+
+  /* in case the timeout expired */
+  quiche_conn_on_timeout(ctx->qconn);
+
+  do {
+    remote_addrlen = sizeof(remote_addr);
+    while((recvd = recvfrom(ctx->q.sockfd, (char *)buf, bufsize, 0,
+                            (struct sockaddr *)&remote_addr,
+                            &remote_addrlen)) == -1 &&
+          SOCKERRNO == EINTR)
+      ;
+    if(recvd < 0) {
+      if((SOCKERRNO == EAGAIN) || (SOCKERRNO == EWOULDBLOCK)) {
+        break;
+      }
+      if(SOCKERRNO == ECONNREFUSED) {
+        const char *r_ip;
+        int r_port;
+        Curl_cf_socket_peek(cf->next, data, NULL, NULL,
+                            &r_ip, &r_port, NULL, NULL);
+        failf(data, "quiche: connection to %s:%u refused",
+              r_ip, r_port);
+        return CURLE_COULDNT_CONNECT;
+      }
+      failf(data, "quiche: recvfrom() unexpectedly returned %zd "
+            "(errno: %d, socket %d)", recvd, SOCKERRNO, ctx->q.sockfd);
+      return CURLE_RECV_ERROR;
+    }
+
+    total += recvd;
+    ++pkts;
+    if(recvd > 0 && !ctx->got_first_byte) {
+      ctx->first_byte_at = Curl_now();
+      ctx->got_first_byte = TRUE;
+    }
+    recv_info.from = (struct sockaddr *) &remote_addr;
+    recv_info.from_len = remote_addrlen;
+    recv_info.to = (struct sockaddr *) &ctx->q.local_addr;
+    recv_info.to_len = ctx->q.local_addrlen;
+
+    nread = quiche_conn_recv(ctx->qconn, buf, recvd, &recv_info);
+    if(nread < 0) {
+      if(QUICHE_ERR_DONE == nread) {
+        DEBUGF(LOG_CF(data, cf, "ingress, quiche is DONE"));
+        return CURLE_OK;
+      }
+      else if(QUICHE_ERR_TLS_FAIL == nread) {
+        long verify_ok = SSL_get_verify_result(ctx->ssl);
+        if(verify_ok != X509_V_OK) {
+          failf(data, "SSL certificate problem: %s",
+                X509_verify_cert_error_string(verify_ok));
+          return CURLE_PEER_FAILED_VERIFICATION;
+        }
+      }
+      else {
+        failf(data, "quiche_conn_recv() == %zd", nread);
+        return CURLE_RECV_ERROR;
+      }
+    }
+    else if(nread < recvd) {
+      DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] ingress, quiche only "
+                    "accepted %zd/%zd bytes",
+                    stream3_id, nread, recvd));
+    }
+
+  } while(pkts < 1000); /* arbitrary */
+
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] ingress, recvd %zd bytes "
+                "in %zd packets", stream3_id, total, pkts));
+  return CURLE_OK;
+}
+
+/*
+ * flush_egress drains the buffers and sends off data.
+ * Calls failf() on errors.
+ */
+static CURLcode cf_flush_egress(struct Curl_cfilter *cf,
+                                struct Curl_easy *data)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+  int64_t stream3_id = data->req.p.http? data->req.p.http->stream3_id : -1;
+  quiche_send_info send_info;
+  ssize_t outlen, total_len = 0;
+  size_t max_udp_payload_size =
+    quiche_conn_max_send_udp_payload_size(ctx->qconn);
+  size_t gsolen = max_udp_payload_size;
+  size_t sent, pktcnt = 0;
+  CURLcode result;
+  int64_t timeout_ns;
+
+  ctx->q.no_gso = TRUE;
+  if(ctx->q.num_blocked_pkt) {
+    result = vquic_send_blocked_pkt(cf, data, &ctx->q);
+    if(result) {
+      if(result == CURLE_AGAIN) {
+        DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] egress, still not "
+                      "able to send blocked packet", stream3_id));
+        Curl_expire(data, 1, EXPIRE_QUIC);
+        return CURLE_OK;
+      }
+      goto out;
+    }
+  }
+
+  for(;;) {
+    outlen = quiche_conn_send(ctx->qconn, ctx->q.pktbuf, max_udp_payload_size,
+                              &send_info);
+    if(outlen == QUICHE_ERR_DONE) {
+      result = CURLE_OK;
+      goto out;
+    }
+
+    if(outlen < 0) {
+      failf(data, "quiche_conn_send returned %zd", outlen);
+      result = CURLE_SEND_ERROR;
+      goto out;
+    }
+
+    /* send the pktbuf *before* the last addition */
+    result = vquic_send_packet(cf, data, &ctx->q, ctx->q.pktbuf,
+                               outlen, gsolen, &sent);
+    ++pktcnt;
+    total_len += outlen;
+    if(result) {
+      if(result == CURLE_AGAIN) {
+        /* blocked, add the pktbuf *before* and *at* the last addition
+         * separately to the blocked packages */
+        DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] egress, pushing blocked "
+                      "packet with %zd bytes", stream3_id, outlen));
+        vquic_push_blocked_pkt(cf, &ctx->q, ctx->q.pktbuf, outlen, gsolen);
+        Curl_expire(data, 1, EXPIRE_QUIC);
+        return CURLE_OK;
+      }
+      goto out;
+    }
+  }
+
+out:
+  timeout_ns = quiche_conn_timeout_as_nanos(ctx->qconn);
+  if(timeout_ns % 1000000)
+    timeout_ns += 1000000;
+    /* expire resolution is milliseconds */
+  Curl_expire(data, (timeout_ns / 1000000), EXPIRE_QUIC);
+  if(pktcnt)
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] egress, sent %zd packets "
+                  "with %zd bytes", stream3_id, pktcnt, total_len));
+  return result;
+}
+
+static ssize_t recv_closed_stream(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  CURLcode *err)
+{
+  struct HTTP *stream = data->req.p.http;
+  ssize_t nread = -1;
+
+  if(stream->reset) {
+    failf(data,
+          "HTTP/3 stream %" PRId64 " reset by server", stream->stream3_id);
+    *err = stream->h3_got_header? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR;
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, was reset -> %d",
+                  stream->stream3_id, *err));
+    goto out;
+  }
+
+  if(!stream->h3_got_header) {
+    failf(data,
+          "HTTP/3 stream %" PRId64 " was closed cleanly, but before getting"
+          " all response header fields, treated as error",
+          stream->stream3_id);
+    /* *err = CURLE_PARTIAL_FILE; */
+    *err = CURLE_RECV_ERROR;
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, closed incomplete"
+                  " -> %d", stream->stream3_id, *err));
+    goto out;
+  }
+  else {
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, closed ok"
+                  " -> %d", stream->stream3_id, *err));
+  }
+  *err = CURLE_OK;
+  nread = 0;
+
+out:
+  return nread;
+}
+
+static CURLcode cf_poll_events(struct Curl_cfilter *cf,
+                               struct Curl_easy *data)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+  struct HTTP *stream = data->req.p.http;
+  quiche_h3_event *ev;
+
+  /* Take in the events and distribute them to the transfers. */
+  while(1) {
+    int64_t stream3_id = quiche_h3_conn_poll(ctx->h3c, ctx->qconn, &ev);
+    if(stream3_id < 0) {
+      /* nothing more to do */
+      break;
+    }
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] recv, queue event %s "
+                  "for [h3sid=%"PRId64"]",
+                  stream? stream->stream3_id : -1, cf_ev_name(ev),
+                  stream3_id));
+    if(h3_add_event(cf, data, stream3_id, ev) != CURLE_OK) {
+      return CURLE_OUT_OF_MEMORY;
+    }
+  }
+  return CURLE_OK;
+}
+
+static ssize_t cf_recv_transfer_data(struct Curl_cfilter *cf,
+                                     struct Curl_easy *data,
+                                      char *buf, size_t len,
+                                      CURLcode *err)
+{
+  struct HTTP *stream = data->req.p.http;
+  ssize_t recvd = -1;
+  size_t offset = 0;
+
+  if(stream->h3_recving_data) {
+    /* try receiving body first */
+    recvd = cf_recv_body(cf, data, buf, len, err);
+    if(recvd < 0) {
+      if(*err != CURLE_AGAIN)
+        return -1;
+      recvd = 0;
+    }
+    if(recvd > 0) {
+      offset = recvd;
+    }
+  }
+
+  if(offset < len && stream->pending) {
+    /* process any pending events for `data` first. if there are,
+     * return so the transfer can handle those. We do not want to
+     * progress ingress while events are pending here. */
+    recvd = h3_process_pending(cf, data, buf + offset, len - offset, err);
+    if(recvd < 0) {
+      if(*err != CURLE_AGAIN)
+        return -1;
+      recvd = 0;
+    }
+    if(recvd > 0) {
+      offset += recvd;
+    }
+  }
+
+  if(offset) {
+    *err = CURLE_OK;
+    return offset;
+  }
+  *err = CURLE_AGAIN;
+  return 0;
+}
+
+static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
+                              char *buf, size_t len, CURLcode *err)
+{
+  struct HTTP *stream = data->req.p.http;
+  ssize_t recvd = -1;
+
+  *err = CURLE_AGAIN;
+
+  recvd = cf_recv_transfer_data(cf, data, buf, len, err);
+  if(recvd)
+    goto out;
+  if(stream->closed) {
+    recvd = recv_closed_stream(cf, data, err);
+    goto out;
+  }
+
+  /* we did get nothing from the quiche buffers or pending events.
+   * Take in more data from the connection, any error is fatal */
+  if(cf_process_ingress(cf, data)) {
+    DEBUGF(LOG_CF(data, cf, "h3_stream_recv returns on ingress"));
+    *err = CURLE_RECV_ERROR;
+    recvd = -1;
+    goto out;
+  }
+  /* poll quiche and distribute the events to the transfers */
+  *err = cf_poll_events(cf, data);
+  if(*err) {
+    recvd = -1;
+    goto out;
+  }
+
+  /* try to receive again for this transfer */
+  recvd = cf_recv_transfer_data(cf, data, buf, len, err);
+  if(recvd)
+    goto out;
+  if(stream->closed) {
+    recvd = recv_closed_stream(cf, data, err);
+    goto out;
+  }
+  recvd = -1;
+  *err = CURLE_AGAIN;
+  data->state.drain = 0;
+
+out:
+  if(cf_flush_egress(cf, data)) {
+    DEBUGF(LOG_CF(data, cf, "cf_recv, flush egress failed"));
+    *err = CURLE_SEND_ERROR;
+    return -1;
+  }
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] cf_recv -> %zd, err=%d",
+                stream->stream3_id, recvd, *err));
+  if(recvd > 0)
+    notify_drain(cf, data);
+  return recvd;
+}
+
+/* Index where :authority header field will appear in request header
+   field list. */
+#define AUTHORITY_DST_IDX 3
+
+static CURLcode cf_http_request(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                const void *mem,
+                                size_t len)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+  struct HTTP *stream = data->req.p.http;
+  size_t nheader;
+  int64_t stream3_id;
+  quiche_h3_header *nva = NULL;
+  CURLcode result = CURLE_OK;
+  struct h2h3req *hreq = NULL;
+
+  stream->h3req = TRUE; /* send off! */
+  stream->closed = FALSE;
+  stream->reset = FALSE;
+
+  result = Curl_pseudo_headers(data, mem, len, NULL, &hreq);
+  if(result)
+    goto fail;
+  nheader = hreq->entries;
+
+  nva = malloc(sizeof(quiche_h3_header) * nheader);
+  if(!nva) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto fail;
+  }
+  else {
+    unsigned int i;
+    for(i = 0; i < nheader; i++) {
+      nva[i].name = (unsigned char *)hreq->header[i].name;
+      nva[i].name_len = hreq->header[i].namelen;
+      nva[i].value = (unsigned char *)hreq->header[i].value;
+      nva[i].value_len = hreq->header[i].valuelen;
+    }
+  }
+
+  switch(data->state.httpreq) {
+  case HTTPREQ_POST:
+  case HTTPREQ_POST_FORM:
+  case HTTPREQ_POST_MIME:
+  case HTTPREQ_PUT:
+    if(data->state.infilesize != -1)
+      stream->upload_left = data->state.infilesize;
+    else
+      /* data sending without specifying the data amount up front */
+      stream->upload_left = -1; /* unknown, but not zero */
+
+    stream->upload_done = !stream->upload_left;
+    stream3_id = quiche_h3_send_request(ctx->h3c, ctx->qconn, nva, nheader,
+                                        stream->upload_done);
+    break;
+  default:
+    stream->upload_left = 0;
+    stream->upload_done = TRUE;
+    stream3_id = quiche_h3_send_request(ctx->h3c, ctx->qconn, nva, nheader,
+                                        TRUE);
+    break;
+  }
+
+  Curl_safefree(nva);
+
+  if(stream3_id < 0) {
+    if(QUICHE_H3_ERR_STREAM_BLOCKED == stream3_id) {
+      DEBUGF(LOG_CF(data, cf, "send_request(%s, body_len=%ld) rejected "
+                    "with H3_ERR_STREAM_BLOCKED",
+                    data->state.url, (long)stream->upload_left));
+      result = CURLE_AGAIN;
+      goto fail;
+    }
+    else {
+      DEBUGF(LOG_CF(data, cf, "send_request(%s, body_len=%ld) -> %" PRId64,
+                    data->state.url, (long)stream->upload_left, stream3_id));
+    }
+    result = CURLE_SEND_ERROR;
+    goto fail;
+  }
+
+  stream->stream3_id = stream3_id;
+  infof(data, "Using HTTP/3 Stream ID: %" PRId64 " (easy handle %p)",
+        stream3_id, (void *)data);
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] opened for %s",
+                stream3_id, data->state.url));
+
+  Curl_pseudo_free(hreq);
+  return CURLE_OK;
+
+fail:
+  free(nva);
+  Curl_pseudo_free(hreq);
+  return result;
+}
+
+static ssize_t cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data,
+                              const void *buf, size_t len, CURLcode *err)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+  struct HTTP *stream = data->req.p.http;
+  ssize_t nwritten;
+
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_send(len=%zu) start",
+                stream->h3req? stream->stream3_id : -1, len));
+  *err = cf_process_ingress(cf, data);
+  if(*err)
+    return -1;
+
+  if(!stream->h3req) {
+    CURLcode result = cf_http_request(cf, data, buf, len);
+    if(result) {
+      *err = result;
+      return -1;
+    }
+    nwritten = len;
+  }
+  else {
+    nwritten = quiche_h3_send_body(ctx->h3c, ctx->qconn, stream->stream3_id,
+                                   (uint8_t *)buf, len, FALSE);
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] send body(len=%zu) -> %zd",
+                  stream->stream3_id, len, nwritten));
+    if(nwritten == QUICHE_H3_ERR_DONE) {
+      /* no error, nothing to do (flow control?) */
+      *err = CURLE_AGAIN;
+      nwritten = -1;
+    }
+    else if(nwritten == QUICHE_H3_TRANSPORT_ERR_FINAL_SIZE) {
+      DEBUGF(LOG_CF(data, cf, "send_body(len=%zu) -> exceeds size", len));
+      *err = CURLE_SEND_ERROR;
+      nwritten = -1;
+    }
+    else if(nwritten < 0) {
+      DEBUGF(LOG_CF(data, cf, "send_body(len=%zu) -> SEND_ERROR", len));
+      *err = CURLE_SEND_ERROR;
+      nwritten = -1;
+    }
+    else {
+      *err = CURLE_OK;
+    }
+  }
+
+  if(cf_flush_egress(cf, data)) {
+    *err = CURLE_SEND_ERROR;
+    return -1;
+  }
+
+  return nwritten;
+}
+
+static bool stream_is_writeable(struct Curl_cfilter *cf,
+                                struct Curl_easy *data)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+  struct HTTP *stream = data->req.p.http;
+
+  /* surely, there must be a better way */
+  quiche_stream_iter *qiter = quiche_conn_writable(ctx->qconn);
+  if(qiter) {
+    uint64_t stream_id;
+    while(quiche_stream_iter_next(qiter, &stream_id)) {
+      if(stream_id == (uint64_t)stream->stream3_id)
+        return TRUE;
+    }
+    quiche_stream_iter_free(qiter);
+  }
+  return FALSE;
+}
+
+static int cf_quiche_get_select_socks(struct Curl_cfilter *cf,
+                                      struct Curl_easy *data,
+                                      curl_socket_t *socks)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+  struct SingleRequest *k = &data->req;
+  int rv = GETSOCK_BLANK;
+
+  socks[0] = ctx->q.sockfd;
+
+  /* in an HTTP/3 connection we can basically always get a frame so we should
+     always be ready for one */
+  rv |= GETSOCK_READSOCK(0);
+
+  /* we're still uploading or the HTTP/3 layer wants to send data */
+  if(((k->keepon & KEEP_SENDBITS) == KEEP_SEND)
+     && stream_is_writeable(cf, data))
+    rv |= GETSOCK_WRITESOCK(0);
+
+  return rv;
+}
+
+/*
+ * Called from transfer.c:data_pending to know if we should keep looping
+ * to receive more data from the connection.
+ */
+static bool cf_quiche_data_pending(struct Curl_cfilter *cf,
+                                   const struct Curl_easy *data)
+{
+  struct HTTP *stream = data->req.p.http;
+
+  if(stream->pending) {
+    DEBUGF(LOG_CF((struct Curl_easy *)data, cf,
+                   "[h3sid=%"PRId64"] has event pending", stream->stream3_id));
+    return TRUE;
+  }
+  if(stream->h3_recving_data) {
+    DEBUGF(LOG_CF((struct Curl_easy *)data, cf,
+                   "[h3sid=%"PRId64"] is receiving DATA", stream->stream3_id));
+    return TRUE;
+  }
+  if(data->state.drain) {
+    DEBUGF(LOG_CF((struct Curl_easy *)data, cf,
+                   "[h3sid=%"PRId64"] is draining", stream->stream3_id));
+    return TRUE;
+  }
+  return FALSE;
+}
+
+static CURLcode cf_quiche_data_event(struct Curl_cfilter *cf,
+                                     struct Curl_easy *data,
+                                     int event, int arg1, void *arg2)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OK;
+
+  (void)arg1;
+  (void)arg2;
+  switch(event) {
+  case CF_CTRL_DATA_DONE: {
+    struct HTTP *stream = data->req.p.http;
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] easy handle is %s",
+                  stream->stream3_id, arg1? "cancelled" : "done"));
+    h3_clear_pending(data);
+    break;
+  }
+  case CF_CTRL_DATA_DONE_SEND: {
+    struct HTTP *stream = data->req.p.http;
+    ssize_t sent;
+    stream->upload_done = TRUE;
+    sent = quiche_h3_send_body(ctx->h3c, ctx->qconn, stream->stream3_id,
+                               NULL, 0, TRUE);
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] send_body FINISHED",
+                  stream->stream3_id));
+    if(sent < 0)
+      return CURLE_SEND_ERROR;
+    break;
+  }
+  case CF_CTRL_DATA_IDLE:
+    /* anything to do? */
+    break;
+  default:
+    break;
+  }
+  return result;
+}
+
+static CURLcode cf_verify_peer(struct Curl_cfilter *cf,
+                               struct Curl_easy *data)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OK;
+
+  cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
+  cf->conn->httpversion = 30;
+  cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX;
+
+  if(cf->conn->ssl_config.verifyhost) {
+    X509 *server_cert;
+    server_cert = SSL_get_peer_certificate(ctx->ssl);
+    if(!server_cert) {
+      result = CURLE_PEER_FAILED_VERIFICATION;
+      goto out;
+    }
+    result = Curl_ossl_verifyhost(data, cf->conn, server_cert);
+    X509_free(server_cert);
+    if(result)
+      goto out;
+  }
+  else
+    DEBUGF(LOG_CF(data, cf, "Skipped certificate verification"));
+
+  ctx->h3config = quiche_h3_config_new();
+  if(!ctx->h3config) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+
+  /* Create a new HTTP/3 connection on the QUIC connection. */
+  ctx->h3c = quiche_h3_conn_new_with_transport(ctx->qconn, ctx->h3config);
+  if(!ctx->h3c) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+  if(data->set.ssl.certinfo)
+    /* asked to gather certificate info */
+    (void)Curl_ossl_certchain(data, ctx->ssl);
+
+out:
+  if(result) {
+    if(ctx->h3config) {
+      quiche_h3_config_free(ctx->h3config);
+      ctx->h3config = NULL;
+    }
+    if(ctx->h3c) {
+      quiche_h3_conn_free(ctx->h3c);
+      ctx->h3c = NULL;
+    }
+  }
+  return result;
+}
+
+static CURLcode cf_connect_start(struct Curl_cfilter *cf,
+                                 struct Curl_easy *data)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+  int rv;
+  CURLcode result;
+  const struct Curl_sockaddr_ex *sockaddr;
+
+  DEBUGASSERT(ctx->q.sockfd != CURL_SOCKET_BAD);
+
+#ifdef DEBUG_QUICHE
+  /* initialize debug log callback only once */
+  static int debug_log_init = 0;
+  if(!debug_log_init) {
+    quiche_enable_debug_logging(quiche_debug_log, NULL);
+    debug_log_init = 1;
+  }
+#endif
+
+  result = vquic_ctx_init(&ctx->q, MAX_UDP_PAYLOAD_SIZE * MAX_PKT_BURST);
+  if(result)
+    return result;
+
+  ctx->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION);
+  if(!ctx->cfg) {
+    failf(data, "can't create quiche config");
+    return CURLE_FAILED_INIT;
+  }
+  quiche_config_set_max_idle_timeout(ctx->cfg, QUIC_IDLE_TIMEOUT);
+  quiche_config_set_initial_max_data(ctx->cfg, QUIC_MAX_DATA);
+  quiche_config_set_initial_max_stream_data_bidi_local(
+    ctx->cfg, QUIC_MAX_DATA);
+  quiche_config_set_initial_max_stream_data_bidi_remote(
+    ctx->cfg, QUIC_MAX_DATA);
+  quiche_config_set_initial_max_stream_data_uni(ctx->cfg, QUIC_MAX_DATA);
+  quiche_config_set_initial_max_streams_bidi(ctx->cfg, QUIC_MAX_STREAMS);
+  quiche_config_set_initial_max_streams_uni(ctx->cfg, QUIC_MAX_STREAMS);
+  quiche_config_set_application_protos(ctx->cfg,
+                                       (uint8_t *)
+                                       QUICHE_H3_APPLICATION_PROTOCOL,
+                                       sizeof(QUICHE_H3_APPLICATION_PROTOCOL)
+                                       - 1);
+
+  DEBUGASSERT(!ctx->ssl);
+  DEBUGASSERT(!ctx->sslctx);
+  ctx->sslctx = quic_ssl_ctx(data);
+  if(!ctx->sslctx)
+    return CURLE_QUIC_CONNECT_ERROR;
+  ctx->ssl = SSL_new(ctx->sslctx);
+  if(!ctx->ssl)
+    return CURLE_QUIC_CONNECT_ERROR;
+
+  SSL_set_app_data(ctx->ssl, cf);
+  SSL_set_tlsext_host_name(ctx->ssl, cf->conn->host.name);
+
+  result = Curl_rand(data, ctx->scid, sizeof(ctx->scid));
+  if(result)
+    return result;
+
+  Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd,
+                      &sockaddr, NULL, NULL, NULL, NULL);
+  ctx->q.local_addrlen = sizeof(ctx->q.local_addr);
+  rv = getsockname(ctx->q.sockfd, (struct sockaddr *)&ctx->q.local_addr,
+                   &ctx->q.local_addrlen);
+  if(rv == -1)
+    return CURLE_QUIC_CONNECT_ERROR;
+
+  ctx->qconn = quiche_conn_new_with_tls((const uint8_t *)ctx->scid,
+                                      sizeof(ctx->scid), NULL, 0,
+                                      (struct sockaddr *)&ctx->q.local_addr,
+                                      ctx->q.local_addrlen,
+                                      &sockaddr->sa_addr, sockaddr->addrlen,
+                                      ctx->cfg, ctx->ssl, false);
+  if(!ctx->qconn) {
+    failf(data, "can't create quiche connection");
+    return CURLE_OUT_OF_MEMORY;
+  }
+
+  /* Known to not work on Windows */
+#if !defined(WIN32) && defined(HAVE_QUICHE_CONN_SET_QLOG_FD)
+  {
+    int qfd;
+    (void)Curl_qlogdir(data, ctx->scid, sizeof(ctx->scid), &qfd);
+    if(qfd != -1)
+      quiche_conn_set_qlog_fd(ctx->qconn, qfd,
+                              "qlog title", "curl qlog");
+  }
+#endif
+
+  result = cf_flush_egress(cf, data);
+  if(result)
+    return result;
+
+  {
+    unsigned char alpn_protocols[] = QUICHE_H3_APPLICATION_PROTOCOL;
+    unsigned alpn_len, offset = 0;
+
+    /* Replace each ALPN length prefix by a comma. */
+    while(offset < sizeof(alpn_protocols) - 1) {
+      alpn_len = alpn_protocols[offset];
+      alpn_protocols[offset] = ',';
+      offset += 1 + alpn_len;
+    }
+
+    DEBUGF(LOG_CF(data, cf, "Sent QUIC client Initial, ALPN: %s",
+                   alpn_protocols + 1));
+  }
+
+  return CURLE_OK;
+}
+
+static CURLcode cf_quiche_connect(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  bool blocking, bool *done)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OK;
+  struct curltime now;
+
+  if(cf->connected) {
+    *done = TRUE;
+    return CURLE_OK;
+  }
+
+  /* Connect the UDP filter first */
+  if(!cf->next->connected) {
+    result = Curl_conn_cf_connect(cf->next, data, blocking, done);
+    if(result || !*done)
+      return result;
+  }
+
+  *done = FALSE;
+  now = Curl_now();
+
+  if(ctx->reconnect_at.tv_sec && Curl_timediff(now, ctx->reconnect_at) < 0) {
+    /* Not time yet to attempt the next connect */
+    DEBUGF(LOG_CF(data, cf, "waiting for reconnect time"));
+    goto out;
+  }
+
+  if(!ctx->qconn) {
+    result = cf_connect_start(cf, data);
+    if(result)
+      goto out;
+    ctx->started_at = now;
+    result = cf_flush_egress(cf, data);
+    /* we do not expect to be able to recv anything yet */
+    goto out;
+  }
+
+  result = cf_process_ingress(cf, data);
+  if(result)
+    goto out;
+
+  result = cf_flush_egress(cf, data);
+  if(result)
+    goto out;
+
+  if(quiche_conn_is_established(ctx->qconn)) {
+    DEBUGF(LOG_CF(data, cf, "handshake complete after %dms",
+           (int)Curl_timediff(now, ctx->started_at)));
+    ctx->handshake_at = now;
+    result = cf_verify_peer(cf, data);
+    if(!result) {
+      DEBUGF(LOG_CF(data, cf, "peer verified"));
+      cf->connected = TRUE;
+      cf->conn->alpn = CURL_HTTP_VERSION_3;
+      *done = TRUE;
+      connkeep(cf->conn, "HTTP/3 default");
+    }
+  }
+  else if(quiche_conn_is_draining(ctx->qconn)) {
+    /* When a QUIC server instance is shutting down, it may send us a
+     * CONNECTION_CLOSE right away. Our connection then enters the DRAINING
+     * state.
+     * This may be a stopping of the service or it may be that the server
+     * is reloading and a new instance will start serving soon.
+     * In any case, we tear down our socket and start over with a new one.
+     * We re-open the underlying UDP cf right now, but do not start
+     * connecting until called again.
+     */
+    int reconn_delay_ms = 200;
+
+    DEBUGF(LOG_CF(data, cf, "connect, remote closed, reconnect after %dms",
+                  reconn_delay_ms));
+    Curl_conn_cf_close(cf->next, data);
+    cf_quiche_ctx_clear(ctx);
+    result = Curl_conn_cf_connect(cf->next, data, FALSE, done);
+    if(!result && *done) {
+      *done = FALSE;
+      ctx->reconnect_at = Curl_now();
+      ctx->reconnect_at.tv_usec += reconn_delay_ms * 1000;
+      Curl_expire(data, reconn_delay_ms, EXPIRE_QUIC);
+      result = CURLE_OK;
+    }
+  }
+
+out:
+#ifndef CURL_DISABLE_VERBOSE_STRINGS
+  if(result && result != CURLE_AGAIN) {
+    const char *r_ip;
+    int r_port;
+
+    Curl_cf_socket_peek(cf->next, data, NULL, NULL,
+                        &r_ip, &r_port, NULL, NULL);
+    infof(data, "connect to %s port %u failed: %s",
+          r_ip, r_port, curl_easy_strerror(result));
+  }
+#endif
+  return result;
+}
+
+static void cf_quiche_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+
+  (void)data;
+  if(ctx) {
+    if(ctx->qconn) {
+      (void)quiche_conn_close(ctx->qconn, TRUE, 0, NULL, 0);
+      /* flushing the egress is not a failsafe way to deliver all the
+         outstanding packets, but we also don't want to get stuck here... */
+      (void)cf_flush_egress(cf, data);
+    }
+    cf_quiche_ctx_clear(ctx);
+  }
+}
+
+static void cf_quiche_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+
+  (void)data;
+  cf_quiche_ctx_clear(ctx);
+  free(ctx);
+  cf->ctx = NULL;
+}
+
+static CURLcode cf_quiche_query(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                int query, int *pres1, void *pres2)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+
+  switch(query) {
+  case CF_QUERY_MAX_CONCURRENT: {
+    uint64_t max_streams = CONN_INUSE(cf->conn);
+    if(!ctx->goaway) {
+      max_streams += quiche_conn_peer_streams_left_bidi(ctx->qconn);
+    }
+    *pres1 = (max_streams > INT_MAX)? INT_MAX : (int)max_streams;
+    DEBUGF(LOG_CF(data, cf, "query: MAX_CONCURRENT -> %d", *pres1));
+    return CURLE_OK;
+  }
+  case CF_QUERY_CONNECT_REPLY_MS:
+    if(ctx->got_first_byte) {
+      timediff_t ms = Curl_timediff(ctx->first_byte_at, ctx->started_at);
+      *pres1 = (ms < INT_MAX)? (int)ms : INT_MAX;
+    }
+    else
+      *pres1 = -1;
+    return CURLE_OK;
+  case CF_QUERY_TIMER_CONNECT: {
+    struct curltime *when = pres2;
+    if(ctx->got_first_byte)
+      *when = ctx->first_byte_at;
+    return CURLE_OK;
+  }
+  case CF_QUERY_TIMER_APPCONNECT: {
+    struct curltime *when = pres2;
+    if(cf->connected)
+      *when = ctx->handshake_at;
+    return CURLE_OK;
+  }
+  default:
+    break;
+  }
+  return cf->next?
+    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
+    CURLE_UNKNOWN_OPTION;
+}
+
+static bool cf_quiche_conn_is_alive(struct Curl_cfilter *cf,
+                                    struct Curl_easy *data,
+                                    bool *input_pending)
+{
+  bool alive = TRUE;
+
+  *input_pending = FALSE;
+  if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending))
+    return FALSE;
+
+  if(*input_pending) {
+    /* This happens before we've sent off a request and the connection is
+       not in use by any other transfer, there shouldn't be any data here,
+       only "protocol frames" */
+    *input_pending = FALSE;
+    Curl_attach_connection(data, cf->conn);
+    if(cf_process_ingress(cf, data))
+      alive = FALSE;
+    else {
+      alive = TRUE;
+    }
+    Curl_detach_connection(data);
+  }
+
+  return alive;
+}
+
+struct Curl_cftype Curl_cft_http3 = {
+  "HTTP/3",
+  CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX,
+  0,
+  cf_quiche_destroy,
+  cf_quiche_connect,
+  cf_quiche_close,
+  Curl_cf_def_get_host,
+  cf_quiche_get_select_socks,
+  cf_quiche_data_pending,
+  cf_quiche_send,
+  cf_quiche_recv,
+  cf_quiche_data_event,
+  cf_quiche_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  cf_quiche_query,
+};
+
+CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf,
+                               struct Curl_easy *data,
+                               struct connectdata *conn,
+                               const struct Curl_addrinfo *ai)
+{
+  struct cf_quiche_ctx *ctx = NULL;
+  struct Curl_cfilter *cf = NULL, *udp_cf = NULL;
+  CURLcode result;
+
+  (void)data;
+  (void)conn;
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+
+  result = Curl_cf_create(&cf, &Curl_cft_http3, ctx);
+  if(result)
+    goto out;
+
+  result = Curl_cf_udp_create(&udp_cf, data, conn, ai, TRNSPRT_QUIC);
+  if(result)
+    goto out;
+
+  udp_cf->conn = cf->conn;
+  udp_cf->sockindex = cf->sockindex;
+  cf->next = udp_cf;
+
+out:
+  *pcf = (!result)? cf : NULL;
+  if(result) {
+    if(udp_cf)
+      Curl_conn_cf_discard(udp_cf, data);
+    Curl_safefree(cf);
+    Curl_safefree(ctx);
+  }
+
+  return result;
+}
+
+bool Curl_conn_is_quiche(const struct Curl_easy *data,
+                         const struct connectdata *conn,
+                         int sockindex)
+{
+  struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL;
+
+  (void)data;
+  for(; cf; cf = cf->next) {
+    if(cf->cft == &Curl_cft_http3)
+      return TRUE;
+    if(cf->cft->flags & CF_TYPE_IP_CONNECT)
+      return FALSE;
+  }
+  return FALSE;
+}
+
+#endif
diff --git a/Utilities/cmcurl/lib/vquic/curl_quiche.h b/Utilities/cmcurl/lib/vquic/curl_quiche.h
new file mode 100644
index 0000000..bce781c
--- /dev/null
+++ b/Utilities/cmcurl/lib/vquic/curl_quiche.h
@@ -0,0 +1,50 @@
+#ifndef HEADER_CURL_VQUIC_CURL_QUICHE_H
+#define HEADER_CURL_VQUIC_CURL_QUICHE_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef USE_QUICHE
+
+#include <quiche.h>
+#include <openssl/ssl.h>
+
+struct Curl_cfilter;
+struct Curl_easy;
+
+void Curl_quiche_ver(char *p, size_t len);
+
+CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf,
+                               struct Curl_easy *data,
+                               struct connectdata *conn,
+                               const struct Curl_addrinfo *ai);
+
+bool Curl_conn_is_quiche(const struct Curl_easy *data,
+                         const struct connectdata *conn,
+                         int sockindex);
+
+#endif
+
+#endif /* HEADER_CURL_VQUIC_CURL_QUICHE_H */
diff --git a/Utilities/cmcurl/lib/vquic/msh3.c b/Utilities/cmcurl/lib/vquic/msh3.c
deleted file mode 100644
index c3e58e7..0000000
--- a/Utilities/cmcurl/lib/vquic/msh3.c
+++ /dev/null
@@ -1,527 +0,0 @@
-/***************************************************************************
- *                                  _   _ ____  _
- *  Project                     ___| | | |  _ \| |
- *                             / __| | | | |_) | |
- *                            | (__| |_| |  _ <| |___
- *                             \___|\___/|_| \_\_____|
- *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- *
- * This software is licensed as described in the file COPYING, which
- * you should have received as part of this distribution. The terms
- * are also available at https://curl.se/docs/copyright.html.
- *
- * You may opt to use, copy, modify, merge, publish, distribute and/or sell
- * copies of the Software, and permit persons to whom the Software is
- * furnished to do so, under the terms of the COPYING file.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- * SPDX-License-Identifier: curl
- *
- ***************************************************************************/
-
-#include "curl_setup.h"
-
-#ifdef USE_MSH3
-
-#include "urldata.h"
-#include "timeval.h"
-#include "multiif.h"
-#include "sendf.h"
-#include "connect.h"
-#include "h2h3.h"
-#include "msh3.h"
-
-/* The last 3 #include files should be in this order */
-#include "curl_printf.h"
-#include "curl_memory.h"
-#include "memdebug.h"
-
-/* #define DEBUG_HTTP3 1 */
-#ifdef DEBUG_HTTP3
-#define H3BUGF(x) x
-#else
-#define H3BUGF(x) do { } while(0)
-#endif
-
-#define MSH3_REQ_INIT_BUF_LEN 8192
-
-static CURLcode msh3_do_it(struct Curl_easy *data, bool *done);
-static int msh3_getsock(struct Curl_easy *data,
-                        struct connectdata *conn, curl_socket_t *socks);
-static CURLcode msh3_disconnect(struct Curl_easy *data,
-                                struct connectdata *conn,
-                                bool dead_connection);
-static unsigned int msh3_conncheck(struct Curl_easy *data,
-                                   struct connectdata *conn,
-                                   unsigned int checks_to_perform);
-static Curl_recv msh3_stream_recv;
-static Curl_send msh3_stream_send;
-static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request,
-                                           void *IfContext,
-                                           const MSH3_HEADER *Header);
-static void MSH3_CALL msh3_data_received(MSH3_REQUEST *Request,
-                                        void *IfContext, uint32_t Length,
-                                        const uint8_t *Data);
-static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext,
-                                    bool Aborted, uint64_t AbortError);
-static void MSH3_CALL msh3_shutdown(MSH3_REQUEST *Request, void *IfContext);
-
-static const struct Curl_handler msh3_curl_handler_http3 = {
-  "HTTPS",                              /* scheme */
-  ZERO_NULL,                            /* setup_connection */
-  msh3_do_it,                           /* do_it */
-  Curl_http_done,                       /* done */
-  ZERO_NULL,                            /* do_more */
-  ZERO_NULL,                            /* connect_it */
-  ZERO_NULL,                            /* connecting */
-  ZERO_NULL,                            /* doing */
-  msh3_getsock,                         /* proto_getsock */
-  msh3_getsock,                         /* doing_getsock */
-  ZERO_NULL,                            /* domore_getsock */
-  msh3_getsock,                         /* perform_getsock */
-  msh3_disconnect,                      /* disconnect */
-  ZERO_NULL,                            /* readwrite */
-  msh3_conncheck,                       /* connection_check */
-  ZERO_NULL,                            /* attach connection */
-  PORT_HTTP,                            /* defport */
-  CURLPROTO_HTTPS,                      /* protocol */
-  CURLPROTO_HTTP,                       /* family */
-  PROTOPT_SSL | PROTOPT_STREAM          /* flags */
-};
-
-static const MSH3_REQUEST_IF msh3_request_if = {
-  msh3_header_received,
-  msh3_data_received,
-  msh3_complete,
-  msh3_shutdown
-};
-
-void Curl_quic_ver(char *p, size_t len)
-{
-  uint32_t v[4];
-  MsH3Version(v);
-  (void)msnprintf(p, len, "msh3/%d.%d.%d.%d", v[0], v[1], v[2], v[3]);
-}
-
-CURLcode Curl_quic_connect(struct Curl_easy *data,
-                           struct connectdata *conn,
-                           curl_socket_t sockfd,
-                           int sockindex,
-                           const struct sockaddr *addr,
-                           socklen_t addrlen)
-{
-  struct quicsocket *qs = &conn->hequic[sockindex];
-  bool insecure = !conn->ssl_config.verifypeer;
-  memset(qs, 0, sizeof(*qs));
-
-  (void)sockfd;
-  (void)addr; /* TODO - Pass address along */
-  (void)addrlen;
-
-  H3BUGF(infof(data, "creating new api/connection"));
-
-  qs->api = MsH3ApiOpen();
-  if(!qs->api) {
-    failf(data, "can't create msh3 api");
-    return CURLE_FAILED_INIT;
-  }
-
-  qs->conn = MsH3ConnectionOpen(qs->api,
-                                conn->host.name,
-                                (uint16_t)conn->remote_port,
-                                insecure);
-  if(!qs->conn) {
-    failf(data, "can't create msh3 connection");
-    if(qs->api) {
-      MsH3ApiClose(qs->api);
-    }
-    return CURLE_FAILED_INIT;
-  }
-
-  return CURLE_OK;
-}
-
-CURLcode Curl_quic_is_connected(struct Curl_easy *data,
-                                struct connectdata *conn,
-                                int sockindex,
-                                bool *connected)
-{
-  struct quicsocket *qs = &conn->hequic[sockindex];
-  MSH3_CONNECTION_STATE state;
-
-  state = MsH3ConnectionGetState(qs->conn, false);
-  if(state == MSH3_CONN_HANDSHAKE_FAILED || state == MSH3_CONN_DISCONNECTED) {
-    failf(data, "failed to connect, state=%u", (uint32_t)state);
-    return CURLE_COULDNT_CONNECT;
-  }
-
-  if(state == MSH3_CONN_CONNECTED) {
-    H3BUGF(infof(data, "connection connected"));
-    *connected = true;
-    conn->quic = qs;
-    conn->recv[sockindex] = msh3_stream_recv;
-    conn->send[sockindex] = msh3_stream_send;
-    conn->handler = &msh3_curl_handler_http3;
-    conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
-    conn->httpversion = 30;
-    conn->bundle->multiuse = BUNDLE_MULTIPLEX;
-    /* TODO - Clean up other happy-eyeballs connection(s)? */
-  }
-
-  return CURLE_OK;
-}
-
-static int msh3_getsock(struct Curl_easy *data,
-                        struct connectdata *conn, curl_socket_t *socks)
-{
-  struct HTTP *stream = data->req.p.http;
-  int bitmap = GETSOCK_BLANK;
-
-  socks[0] = conn->sock[FIRSTSOCKET];
-
-  if(stream->recv_error) {
-    bitmap |= GETSOCK_READSOCK(FIRSTSOCKET);
-    data->state.drain++;
-  }
-  else if(stream->recv_header_len || stream->recv_data_len) {
-    bitmap |= GETSOCK_READSOCK(FIRSTSOCKET);
-    data->state.drain++;
-  }
-
-  H3BUGF(infof(data, "msh3_getsock %u", (uint32_t)data->state.drain));
-
-  return bitmap;
-}
-
-static CURLcode msh3_do_it(struct Curl_easy *data, bool *done)
-{
-  struct HTTP *stream = data->req.p.http;
-  H3BUGF(infof(data, "msh3_do_it"));
-  stream->recv_buf = malloc(MSH3_REQ_INIT_BUF_LEN);
-  if(!stream->recv_buf) {
-    return CURLE_OUT_OF_MEMORY;
-  }
-  stream->req = ZERO_NULL;
-  msh3_lock_initialize(&stream->recv_lock);
-  stream->recv_buf_alloc = MSH3_REQ_INIT_BUF_LEN;
-  stream->recv_header_len = 0;
-  stream->recv_header_complete = false;
-  stream->recv_data_len = 0;
-  stream->recv_data_complete = false;
-  stream->recv_error = CURLE_OK;
-  return Curl_http(data, done);
-}
-
-static unsigned int msh3_conncheck(struct Curl_easy *data,
-                                   struct connectdata *conn,
-                                   unsigned int checks_to_perform)
-{
-  (void)data;
-  (void)conn;
-  (void)checks_to_perform;
-  H3BUGF(infof(data, "msh3_conncheck"));
-  return CONNRESULT_NONE;
-}
-
-static void disconnect(struct quicsocket *qs)
-{
-  if(qs->conn) {
-    MsH3ConnectionClose(qs->conn);
-    qs->conn = ZERO_NULL;
-  }
-  if(qs->api) {
-    MsH3ApiClose(qs->api);
-    qs->api = ZERO_NULL;
-  }
-}
-
-static CURLcode msh3_disconnect(struct Curl_easy *data,
-                                struct connectdata *conn, bool dead_connection)
-{
-  (void)data;
-  (void)dead_connection;
-  H3BUGF(infof(data, "disconnecting (msh3)"));
-  disconnect(conn->quic);
-  return CURLE_OK;
-}
-
-void Curl_quic_disconnect(struct Curl_easy *data, struct connectdata *conn,
-                          int tempindex)
-{
-  (void)data;
-  if(conn->transport == TRNSPRT_QUIC) {
-    H3BUGF(infof(data, "disconnecting QUIC index %u", tempindex));
-    disconnect(&conn->hequic[tempindex]);
-  }
-}
-
-/* Requires stream->recv_lock to be held */
-static bool msh3request_ensure_room(struct HTTP *stream, size_t len)
-{
-  uint8_t *new_recv_buf;
-  const size_t cur_recv_len = stream->recv_header_len + stream->recv_data_len;
-  if(cur_recv_len + len > stream->recv_buf_alloc) {
-    size_t new_recv_buf_alloc_len = stream->recv_buf_alloc;
-    do {
-      new_recv_buf_alloc_len <<= 1; /* TODO - handle overflow */
-    } while(cur_recv_len + len > new_recv_buf_alloc_len);
-    new_recv_buf = malloc(new_recv_buf_alloc_len);
-    if(!new_recv_buf) {
-      return false;
-    }
-    if(cur_recv_len) {
-      memcpy(new_recv_buf, stream->recv_buf, cur_recv_len);
-    }
-    stream->recv_buf_alloc = new_recv_buf_alloc_len;
-    free(stream->recv_buf);
-    stream->recv_buf = new_recv_buf;
-  }
-  return true;
-}
-
-static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request,
-                                           void *IfContext,
-                                           const MSH3_HEADER *Header)
-{
-  struct HTTP *stream = IfContext;
-  size_t total_len;
-  (void)Request;
-
-  if(stream->recv_header_complete) {
-    H3BUGF(printf("* ignoring header after data\n"));
-    return;
-  }
-
-  msh3_lock_acquire(&stream->recv_lock);
-
-  if((Header->NameLength == 7) &&
-     !strncmp(H2H3_PSEUDO_STATUS, (char *)Header->Name, 7)) {
-     total_len = 9 + Header->ValueLength;
-    if(!msh3request_ensure_room(stream, total_len)) {
-      /* TODO - handle error */
-      goto release_lock;
-    }
-    msnprintf((char *)stream->recv_buf + stream->recv_header_len,
-              stream->recv_buf_alloc - stream->recv_header_len,
-              "HTTP/3 %.*s\n", (int)Header->ValueLength, Header->Value);
-  }
-  else {
-    total_len = Header->NameLength + 4 + Header->ValueLength;
-    if(!msh3request_ensure_room(stream, total_len)) {
-      /* TODO - handle error */
-      goto release_lock;
-    }
-    msnprintf((char *)stream->recv_buf + stream->recv_header_len,
-              stream->recv_buf_alloc - stream->recv_header_len,
-              "%.*s: %.*s\n",
-              (int)Header->NameLength, Header->Name,
-              (int)Header->ValueLength, Header->Value);
-  }
-
-  stream->recv_header_len += total_len - 1; /* don't include null-terminator */
-
-release_lock:
-  msh3_lock_release(&stream->recv_lock);
-}
-
-static void MSH3_CALL msh3_data_received(MSH3_REQUEST *Request,
-                                         void *IfContext, uint32_t Length,
-                                         const uint8_t *Data)
-{
-  struct HTTP *stream = IfContext;
-  size_t cur_recv_len = stream->recv_header_len + stream->recv_data_len;
-  (void)Request;
-  H3BUGF(printf("* msh3_data_received %u. %zu buffered, %zu allocated\n",
-                Length, cur_recv_len, stream->recv_buf_alloc));
-  msh3_lock_acquire(&stream->recv_lock);
-  if(!stream->recv_header_complete) {
-    H3BUGF(printf("* Headers complete!\n"));
-    if(!msh3request_ensure_room(stream, 2)) {
-      /* TODO - handle error */
-      goto release_lock;
-    }
-    stream->recv_buf[stream->recv_header_len++] = '\r';
-    stream->recv_buf[stream->recv_header_len++] = '\n';
-    stream->recv_header_complete = true;
-    cur_recv_len += 2;
-  }
-  if(!msh3request_ensure_room(stream, Length)) {
-    /* TODO - handle error */
-    goto release_lock;
-  }
-  memcpy(stream->recv_buf + cur_recv_len, Data, Length);
-  stream->recv_data_len += (size_t)Length;
-release_lock:
-  msh3_lock_release(&stream->recv_lock);
-}
-
-static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext,
-                                    bool Aborted, uint64_t AbortError)
-{
-  struct HTTP *stream = IfContext;
-  (void)Request;
-  (void)AbortError;
-  H3BUGF(printf("* msh3_complete, aborted=%s\n", Aborted ? "true" : "false"));
-  msh3_lock_acquire(&stream->recv_lock);
-  if(Aborted) {
-    stream->recv_error = CURLE_HTTP3; /* TODO - how do we pass AbortError? */
-  }
-  stream->recv_header_complete = true;
-  stream->recv_data_complete = true;
-  msh3_lock_release(&stream->recv_lock);
-}
-
-static void MSH3_CALL msh3_shutdown(MSH3_REQUEST *Request, void *IfContext)
-{
-  struct HTTP *stream = IfContext;
-  (void)Request;
-  (void)stream;
-}
-
-static ssize_t msh3_stream_send(struct Curl_easy *data,
-                                int sockindex,
-                                const void *mem,
-                                size_t len,
-                                CURLcode *curlcode)
-{
-  struct connectdata *conn = data->conn;
-  struct HTTP *stream = data->req.p.http;
-  struct quicsocket *qs = conn->quic;
-  struct h2h3req *hreq;
-
-  (void)sockindex;
-  /* Sizes must match for cast below to work" */
-  DEBUGASSERT(sizeof(MSH3_HEADER) == sizeof(struct h2h3pseudo));
-
-  H3BUGF(infof(data, "msh3_stream_send %zu", len));
-
-  if(!stream->req) {
-    *curlcode = Curl_pseudo_headers(data, mem, len, &hreq);
-    if(*curlcode) {
-      failf(data, "Curl_pseudo_headers failed");
-      return -1;
-    }
-    H3BUGF(infof(data, "starting request with %zu headers", hreq->entries));
-    stream->req = MsH3RequestOpen(qs->conn, &msh3_request_if, stream,
-                                 (MSH3_HEADER*)hreq->header, hreq->entries);
-    Curl_pseudo_free(hreq);
-    if(!stream->req) {
-      failf(data, "request open failed");
-      *curlcode = CURLE_SEND_ERROR;
-      return -1;
-    }
-    *curlcode = CURLE_OK;
-    return len;
-  }
-  H3BUGF(infof(data, "send %zd body bytes on request %p", len,
-               (void *)stream->req));
-  *curlcode = CURLE_SEND_ERROR;
-  return -1;
-}
-
-static ssize_t msh3_stream_recv(struct Curl_easy *data,
-                                int sockindex,
-                                char *buf,
-                                size_t buffersize,
-                                CURLcode *curlcode)
-{
-  struct HTTP *stream = data->req.p.http;
-  size_t outsize = 0;
-  (void)sockindex;
-  H3BUGF(infof(data, "msh3_stream_recv %zu", buffersize));
-
-  if(stream->recv_error) {
-    failf(data, "request aborted");
-    *curlcode = stream->recv_error;
-    return -1;
-  }
-
-  msh3_lock_acquire(&stream->recv_lock);
-
-  if(stream->recv_header_len) {
-    outsize = buffersize;
-    if(stream->recv_header_len < outsize) {
-      outsize = stream->recv_header_len;
-    }
-    memcpy(buf, stream->recv_buf, outsize);
-    if(outsize < stream->recv_header_len + stream->recv_data_len) {
-      memmove(stream->recv_buf, stream->recv_buf + outsize,
-              stream->recv_header_len + stream->recv_data_len - outsize);
-    }
-    stream->recv_header_len -= outsize;
-    H3BUGF(infof(data, "returned %zu bytes of headers", outsize));
-  }
-  else if(stream->recv_data_len) {
-    outsize = buffersize;
-    if(stream->recv_data_len < outsize) {
-      outsize = stream->recv_data_len;
-    }
-    memcpy(buf, stream->recv_buf, outsize);
-    if(outsize < stream->recv_data_len) {
-      memmove(stream->recv_buf, stream->recv_buf + outsize,
-              stream->recv_data_len - outsize);
-    }
-    stream->recv_data_len -= outsize;
-    H3BUGF(infof(data, "returned %zu bytes of data", outsize));
-  }
-  else if(stream->recv_data_complete) {
-    H3BUGF(infof(data, "receive complete"));
-  }
-
-  msh3_lock_release(&stream->recv_lock);
-
-  return (ssize_t)outsize;
-}
-
-CURLcode Curl_quic_done_sending(struct Curl_easy *data)
-{
-  struct connectdata *conn = data->conn;
-  H3BUGF(infof(data, "Curl_quic_done_sending"));
-  if(conn->handler == &msh3_curl_handler_http3) {
-    struct HTTP *stream = data->req.p.http;
-    stream->upload_done = TRUE;
-  }
-
-  return CURLE_OK;
-}
-
-void Curl_quic_done(struct Curl_easy *data, bool premature)
-{
-  struct HTTP *stream = data->req.p.http;
-  (void)premature;
-  H3BUGF(infof(data, "Curl_quic_done"));
-  if(stream) {
-    if(stream->recv_buf) {
-      Curl_safefree(stream->recv_buf);
-      msh3_lock_uninitialize(&stream->recv_lock);
-    }
-    if(stream->req) {
-      MsH3RequestClose(stream->req);
-      stream->req = ZERO_NULL;
-    }
-  }
-}
-
-bool Curl_quic_data_pending(const struct Curl_easy *data)
-{
-  struct HTTP *stream = data->req.p.http;
-  H3BUGF(infof((struct Curl_easy *)data, "Curl_quic_data_pending"));
-  return stream->recv_header_len || stream->recv_data_len;
-}
-
-/*
- * Called from transfer.c:Curl_readwrite when neither HTTP level read
- * nor write is performed. It is a good place to handle timer expiry
- * for QUIC transport.
- */
-CURLcode Curl_quic_idle(struct Curl_easy *data)
-{
-  (void)data;
-  H3BUGF(infof(data, "Curl_quic_idle"));
-  return CURLE_OK;
-}
-
-#endif /* USE_MSH3 */
diff --git a/Utilities/cmcurl/lib/vquic/msh3.h b/Utilities/cmcurl/lib/vquic/msh3.h
deleted file mode 100644
index ce884d9..0000000
--- a/Utilities/cmcurl/lib/vquic/msh3.h
+++ /dev/null
@@ -1,40 +0,0 @@
-#ifndef HEADER_CURL_VQUIC_MSH3_H
-#define HEADER_CURL_VQUIC_MSH3_H
-/***************************************************************************
- *                                  _   _ ____  _
- *  Project                     ___| | | |  _ \| |
- *                             / __| | | | |_) | |
- *                            | (__| |_| |  _ <| |___
- *                             \___|\___/|_| \_\_____|
- *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- *
- * This software is licensed as described in the file COPYING, which
- * you should have received as part of this distribution. The terms
- * are also available at https://curl.se/docs/copyright.html.
- *
- * You may opt to use, copy, modify, merge, publish, distribute and/or sell
- * copies of the Software, and permit persons to whom the Software is
- * furnished to do so, under the terms of the COPYING file.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- * SPDX-License-Identifier: curl
- *
- ***************************************************************************/
-
-#include "curl_setup.h"
-
-#ifdef USE_MSH3
-
-#include <msh3.h>
-
-struct quicsocket {
-  MSH3_API* api;
-  MSH3_CONNECTION* conn;
-};
-
-#endif /* USE_MSQUIC */
-
-#endif /* HEADER_CURL_VQUIC_MSH3_H */
diff --git a/Utilities/cmcurl/lib/vquic/ngtcp2.c b/Utilities/cmcurl/lib/vquic/ngtcp2.c
deleted file mode 100644
index f16b469..0000000
--- a/Utilities/cmcurl/lib/vquic/ngtcp2.c
+++ /dev/null
@@ -1,2266 +0,0 @@
-/***************************************************************************
- *                                  _   _ ____  _
- *  Project                     ___| | | |  _ \| |
- *                             / __| | | | |_) | |
- *                            | (__| |_| |  _ <| |___
- *                             \___|\___/|_| \_\_____|
- *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- *
- * This software is licensed as described in the file COPYING, which
- * you should have received as part of this distribution. The terms
- * are also available at https://curl.se/docs/copyright.html.
- *
- * You may opt to use, copy, modify, merge, publish, distribute and/or sell
- * copies of the Software, and permit persons to whom the Software is
- * furnished to do so, under the terms of the COPYING file.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- * SPDX-License-Identifier: curl
- *
- ***************************************************************************/
-
-#include "curl_setup.h"
-
-#ifdef USE_NGTCP2
-#include <ngtcp2/ngtcp2.h>
-#include <nghttp3/nghttp3.h>
-
-#ifdef USE_OPENSSL
-#include <openssl/err.h>
-#ifdef OPENSSL_IS_BORINGSSL
-#include <ngtcp2/ngtcp2_crypto_boringssl.h>
-#else
-#include <ngtcp2/ngtcp2_crypto_openssl.h>
-#endif
-#include "vtls/openssl.h"
-#elif defined(USE_GNUTLS)
-#include <ngtcp2/ngtcp2_crypto_gnutls.h>
-#include "vtls/gtls.h"
-#elif defined(USE_WOLFSSL)
-#include <ngtcp2/ngtcp2_crypto_wolfssl.h>
-#include "vtls/wolfssl.h"
-#endif
-
-#include "urldata.h"
-#include "sendf.h"
-#include "strdup.h"
-#include "rand.h"
-#include "ngtcp2.h"
-#include "multiif.h"
-#include "strcase.h"
-#include "cfilters.h"
-#include "connect.h"
-#include "strerror.h"
-#include "dynbuf.h"
-#include "vquic.h"
-#include "h2h3.h"
-#include "vtls/keylog.h"
-#include "vtls/vtls.h"
-
-/* The last 3 #include files should be in this order */
-#include "curl_printf.h"
-#include "curl_memory.h"
-#include "memdebug.h"
-
-/* #define DEBUG_NGTCP2 */
-#ifdef CURLDEBUG
-#define DEBUG_HTTP3
-#endif
-#ifdef DEBUG_HTTP3
-#define H3BUGF(x) x
-#else
-#define H3BUGF(x) do { } while(0)
-#endif
-
-#define H3_ALPN_H3_29 "\x5h3-29"
-#define H3_ALPN_H3 "\x2h3"
-
-/*
- * This holds outgoing HTTP/3 stream data that is used by nghttp3 until acked.
- * It is used as a circular buffer. Add new bytes at the end until it reaches
- * the far end, then start over at index 0 again.
- */
-
-#define H3_SEND_SIZE (256*1024)
-struct h3out {
-  uint8_t buf[H3_SEND_SIZE];
-  size_t used;   /* number of bytes used in the buffer */
-  size_t windex; /* index in the buffer where to start writing the next
-                    data block */
-};
-
-#define QUIC_MAX_STREAMS (256*1024)
-#define QUIC_MAX_DATA (1*1024*1024)
-#define QUIC_IDLE_TIMEOUT (60*NGTCP2_SECONDS)
-
-#ifdef USE_OPENSSL
-#define QUIC_CIPHERS                                                          \
-  "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_"               \
-  "POLY1305_SHA256:TLS_AES_128_CCM_SHA256"
-#define QUIC_GROUPS "P-256:X25519:P-384:P-521"
-#elif defined(USE_GNUTLS)
-#define QUIC_PRIORITY \
-  "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:" \
-  "+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:" \
-  "+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1:" \
-  "%DISABLE_TLS13_COMPAT_MODE"
-#elif defined(USE_WOLFSSL)
-#define QUIC_CIPHERS                                                          \
-  "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_"               \
-  "POLY1305_SHA256:TLS_AES_128_CCM_SHA256"
-#define QUIC_GROUPS "P-256:P-384:P-521"
-#endif
-
-/* ngtcp2 default congestion controller does not perform pacing. Limit
-   the maximum packet burst to MAX_PKT_BURST packets. */
-#define MAX_PKT_BURST 10
-
-static CURLcode ng_process_ingress(struct Curl_easy *data,
-                                   curl_socket_t sockfd,
-                                   struct quicsocket *qs);
-static CURLcode ng_flush_egress(struct Curl_easy *data, int sockfd,
-                                struct quicsocket *qs);
-static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id,
-                                   uint64_t datalen, void *user_data,
-                                   void *stream_user_data);
-
-static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref)
-{
-  struct quicsocket *qs = conn_ref->user_data;
-  return qs->qconn;
-}
-
-static ngtcp2_tstamp timestamp(void)
-{
-  struct curltime ct = Curl_now();
-  return ct.tv_sec * NGTCP2_SECONDS + ct.tv_usec * NGTCP2_MICROSECONDS;
-}
-
-#ifdef DEBUG_NGTCP2
-static void quic_printf(void *user_data, const char *fmt, ...)
-{
-  va_list ap;
-  (void)user_data; /* TODO, use this to do infof() instead long-term */
-  va_start(ap, fmt);
-  vfprintf(stderr, fmt, ap);
-  va_end(ap);
-  fprintf(stderr, "\n");
-}
-#endif
-
-static void qlog_callback(void *user_data, uint32_t flags,
-                          const void *data, size_t datalen)
-{
-  struct quicsocket *qs = (struct quicsocket *)user_data;
-  (void)flags;
-  if(qs->qlogfd != -1) {
-    ssize_t rc = write(qs->qlogfd, data, datalen);
-    if(rc == -1) {
-      /* on write error, stop further write attempts */
-      close(qs->qlogfd);
-      qs->qlogfd = -1;
-    }
-  }
-
-}
-
-static void quic_settings(struct quicsocket *qs,
-                          uint64_t stream_buffer_size)
-{
-  ngtcp2_settings *s = &qs->settings;
-  ngtcp2_transport_params *t = &qs->transport_params;
-  ngtcp2_settings_default(s);
-  ngtcp2_transport_params_default(t);
-#ifdef DEBUG_NGTCP2
-  s->log_printf = quic_printf;
-#else
-  s->log_printf = NULL;
-#endif
-  s->initial_ts = timestamp();
-  t->initial_max_stream_data_bidi_local = stream_buffer_size;
-  t->initial_max_stream_data_bidi_remote = QUIC_MAX_STREAMS;
-  t->initial_max_stream_data_uni = QUIC_MAX_STREAMS;
-  t->initial_max_data = QUIC_MAX_DATA;
-  t->initial_max_streams_bidi = 1;
-  t->initial_max_streams_uni = 3;
-  t->max_idle_timeout = QUIC_IDLE_TIMEOUT;
-  if(qs->qlogfd != -1) {
-    s->qlog.write = qlog_callback;
-  }
-}
-
-#ifdef USE_OPENSSL
-static void keylog_callback(const SSL *ssl, const char *line)
-{
-  (void)ssl;
-  Curl_tls_keylog_write_line(line);
-}
-#elif defined(USE_GNUTLS)
-static int keylog_callback(gnutls_session_t session, const char *label,
-                    const gnutls_datum_t *secret)
-{
-  gnutls_datum_t crandom;
-  gnutls_datum_t srandom;
-
-  gnutls_session_get_random(session, &crandom, &srandom);
-  if(crandom.size != 32) {
-    return -1;
-  }
-
-  Curl_tls_keylog_write(label, crandom.data, secret->data, secret->size);
-  return 0;
-}
-#elif defined(USE_WOLFSSL)
-#if defined(HAVE_SECRET_CALLBACK)
-static void keylog_callback(const WOLFSSL *ssl, const char *line)
-{
-  (void)ssl;
-  Curl_tls_keylog_write_line(line);
-}
-#endif
-#endif
-
-static int init_ngh3_conn(struct quicsocket *qs);
-
-#ifdef USE_OPENSSL
-static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data)
-{
-  struct connectdata *conn = data->conn;
-  SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method());
-
-#ifdef OPENSSL_IS_BORINGSSL
-  if(ngtcp2_crypto_boringssl_configure_client_context(ssl_ctx) != 0) {
-    failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed");
-    return NULL;
-  }
-#else
-  if(ngtcp2_crypto_openssl_configure_client_context(ssl_ctx) != 0) {
-    failf(data, "ngtcp2_crypto_openssl_configure_client_context failed");
-    return NULL;
-  }
-#endif
-
-  SSL_CTX_set_default_verify_paths(ssl_ctx);
-
-#ifdef OPENSSL_IS_BORINGSSL
-  if(SSL_CTX_set1_curves_list(ssl_ctx, QUIC_GROUPS) != 1) {
-    failf(data, "SSL_CTX_set1_curves_list failed");
-    return NULL;
-  }
-#else
-  if(SSL_CTX_set_ciphersuites(ssl_ctx, QUIC_CIPHERS) != 1) {
-    char error_buffer[256];
-    ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer));
-    failf(data, "SSL_CTX_set_ciphersuites: %s", error_buffer);
-    return NULL;
-  }
-
-  if(SSL_CTX_set1_groups_list(ssl_ctx, QUIC_GROUPS) != 1) {
-    failf(data, "SSL_CTX_set1_groups_list failed");
-    return NULL;
-  }
-#endif
-
-  /* Open the file if a TLS or QUIC backend has not done this before. */
-  Curl_tls_keylog_open();
-  if(Curl_tls_keylog_enabled()) {
-    SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback);
-  }
-
-  if(conn->ssl_config.verifypeer) {
-    const char * const ssl_cafile = conn->ssl_config.CAfile;
-    const char * const ssl_capath = conn->ssl_config.CApath;
-
-    if(ssl_cafile || ssl_capath) {
-      SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
-      /* tell OpenSSL where to find CA certificates that are used to verify
-         the server's certificate. */
-      if(!SSL_CTX_load_verify_locations(ssl_ctx, ssl_cafile, ssl_capath)) {
-        /* Fail if we insist on successfully verifying the server. */
-        failf(data, "error setting certificate verify locations:"
-              "  CAfile: %s CApath: %s",
-              ssl_cafile ? ssl_cafile : "none",
-              ssl_capath ? ssl_capath : "none");
-        return NULL;
-      }
-      infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
-      infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
-    }
-#ifdef CURL_CA_FALLBACK
-    else {
-      /* verifying the peer without any CA certificates won't work so
-         use openssl's built-in default as fallback */
-      SSL_CTX_set_default_verify_paths(ssl_ctx);
-    }
-#endif
-  }
-  return ssl_ctx;
-}
-
-static CURLcode quic_set_client_cert(struct Curl_easy *data,
-                                     struct quicsocket *qs)
-{
-  SSL_CTX *ssl_ctx = qs->sslctx;
-  const struct ssl_config_data *ssl_config;
-
-  ssl_config = Curl_ssl_get_config(data, FIRSTSOCKET);
-  DEBUGASSERT(ssl_config);
-
-  if(ssl_config->primary.clientcert || ssl_config->primary.cert_blob
-     || ssl_config->cert_type) {
-    return Curl_ossl_set_client_cert(
-        data, ssl_ctx, ssl_config->primary.clientcert,
-        ssl_config->primary.cert_blob, ssl_config->cert_type,
-        ssl_config->key, ssl_config->key_blob,
-        ssl_config->key_type, ssl_config->key_passwd);
-  }
-
-  return CURLE_OK;
-}
-
-/** SSL callbacks ***/
-
-static CURLcode quic_init_ssl(struct quicsocket *qs,
-                              struct Curl_easy *data,
-                              struct connectdata *conn)
-{
-  const uint8_t *alpn = NULL;
-  size_t alpnlen = 0;
-  /* this will need some attention when HTTPS proxy over QUIC get fixed */
-  const char * const hostname = qs->conn->host.name;
-
-  (void)data;
-  (void)conn;
-  DEBUGASSERT(!qs->ssl);
-  qs->ssl = SSL_new(qs->sslctx);
-
-  SSL_set_app_data(qs->ssl, &qs->conn_ref);
-  SSL_set_connect_state(qs->ssl);
-  SSL_set_quic_use_legacy_codepoint(qs->ssl, 0);
-
-  alpn = (const uint8_t *)H3_ALPN_H3_29 H3_ALPN_H3;
-  alpnlen = sizeof(H3_ALPN_H3_29) - 1 + sizeof(H3_ALPN_H3) - 1;
-  if(alpn)
-    SSL_set_alpn_protos(qs->ssl, alpn, (int)alpnlen);
-
-  /* set SNI */
-  SSL_set_tlsext_host_name(qs->ssl, hostname);
-  return CURLE_OK;
-}
-#elif defined(USE_GNUTLS)
-static CURLcode quic_init_ssl(struct quicsocket *qs,
-                              struct Curl_easy *data,
-                              struct connectdata *conn)
-{
-  CURLcode result;
-  gnutls_datum_t alpn[2];
-  /* this will need some attention when HTTPS proxy over QUIC get fixed */
-  const char * const hostname = qs->conn->host.name;
-  long * const pverifyresult = &data->set.ssl.certverifyresult;
-  int rc;
-
-  DEBUGASSERT(qs->gtls == NULL);
-  qs->gtls = calloc(1, sizeof(*(qs->gtls)));
-  if(!qs->gtls)
-    return CURLE_OUT_OF_MEMORY;
-
-  result = gtls_client_init(data, &conn->ssl_config, &data->set.ssl,
-                            hostname, qs->gtls, pverifyresult);
-  if(result)
-    return result;
-
-  gnutls_session_set_ptr(qs->gtls->session, &qs->conn_ref);
-
-  if(ngtcp2_crypto_gnutls_configure_client_session(qs->gtls->session) != 0) {
-    H3BUGF(fprintf(stderr,
-                   "ngtcp2_crypto_gnutls_configure_client_session failed\n"));
-    return CURLE_QUIC_CONNECT_ERROR;
-  }
-
-  rc = gnutls_priority_set_direct(qs->gtls->session, QUIC_PRIORITY, NULL);
-  if(rc < 0) {
-    H3BUGF(fprintf(stderr, "gnutls_priority_set_direct failed: %s\n",
-                   gnutls_strerror(rc)));
-    return CURLE_QUIC_CONNECT_ERROR;
-  }
-
-  /* Open the file if a TLS or QUIC backend has not done this before. */
-  Curl_tls_keylog_open();
-  if(Curl_tls_keylog_enabled()) {
-    gnutls_session_set_keylog_function(qs->gtls->session, keylog_callback);
-  }
-
-  /* strip the first byte (the length) from NGHTTP3_ALPN_H3 */
-  alpn[0].data = (unsigned char *)H3_ALPN_H3_29 + 1;
-  alpn[0].size = sizeof(H3_ALPN_H3_29) - 2;
-  alpn[1].data = (unsigned char *)H3_ALPN_H3 + 1;
-  alpn[1].size = sizeof(H3_ALPN_H3) - 2;
-
-  gnutls_alpn_set_protocols(qs->gtls->session, alpn, 2, GNUTLS_ALPN_MANDATORY);
-
-  return CURLE_OK;
-}
-#elif defined(USE_WOLFSSL)
-
-static WOLFSSL_CTX *quic_ssl_ctx(struct Curl_easy *data)
-{
-  struct connectdata *conn = data->conn;
-  WOLFSSL_CTX *ssl_ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
-
-  if(ngtcp2_crypto_wolfssl_configure_client_context(ssl_ctx) != 0) {
-    failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed");
-    return NULL;
-  }
-
-  wolfSSL_CTX_set_default_verify_paths(ssl_ctx);
-
-  if(wolfSSL_CTX_set_cipher_list(ssl_ctx, QUIC_CIPHERS) != 1) {
-    char error_buffer[256];
-    ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer));
-    failf(data, "SSL_CTX_set_ciphersuites: %s", error_buffer);
-    return NULL;
-  }
-
-  if(wolfSSL_CTX_set1_groups_list(ssl_ctx, (char *)QUIC_GROUPS) != 1) {
-    failf(data, "SSL_CTX_set1_groups_list failed");
-    return NULL;
-  }
-
-  /* Open the file if a TLS or QUIC backend has not done this before. */
-  Curl_tls_keylog_open();
-  if(Curl_tls_keylog_enabled()) {
-#if defined(HAVE_SECRET_CALLBACK)
-    wolfSSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback);
-#else
-    failf(data, "wolfSSL was built without keylog callback");
-    return NULL;
-#endif
-  }
-
-  if(conn->ssl_config.verifypeer) {
-    const char * const ssl_cafile = conn->ssl_config.CAfile;
-    const char * const ssl_capath = conn->ssl_config.CApath;
-
-    if(ssl_cafile || ssl_capath) {
-      wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
-      /* tell wolfSSL where to find CA certificates that are used to verify
-         the server's certificate. */
-      if(!wolfSSL_CTX_load_verify_locations(ssl_ctx, ssl_cafile, ssl_capath)) {
-        /* Fail if we insist on successfully verifying the server. */
-        failf(data, "error setting certificate verify locations:"
-              "  CAfile: %s CApath: %s",
-              ssl_cafile ? ssl_cafile : "none",
-              ssl_capath ? ssl_capath : "none");
-        return NULL;
-      }
-      infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
-      infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
-    }
-#ifdef CURL_CA_FALLBACK
-    else {
-      /* verifying the peer without any CA certificates won't work so
-         use wolfssl's built-in default as fallback */
-      wolfSSL_CTX_set_default_verify_paths(ssl_ctx);
-    }
-#endif
-  }
-  else {
-    wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL);
-  }
-
-  return ssl_ctx;
-}
-
-/** SSL callbacks ***/
-
-static CURLcode quic_init_ssl(struct quicsocket *qs,
-                              struct Curl_easy *data,
-                              struct connectdata *conn)
-{
-  const uint8_t *alpn = NULL;
-  size_t alpnlen = 0;
-  /* this will need some attention when HTTPS proxy over QUIC get fixed */
-  const char * const hostname = qs->conn->host.name;
-
-  (void)data;
-  (void)conn;
-  DEBUGASSERT(!qs->ssl);
-  qs->ssl = SSL_new(qs->sslctx);
-
-  wolfSSL_set_app_data(qs->ssl, &qs->conn_ref);
-  wolfSSL_set_connect_state(qs->ssl);
-  wolfSSL_set_quic_use_legacy_codepoint(qs->ssl, 0);
-
-  alpn = (const uint8_t *)H3_ALPN_H3_29 H3_ALPN_H3;
-  alpnlen = sizeof(H3_ALPN_H3_29) - 1 + sizeof(H3_ALPN_H3) - 1;
-  if(alpn)
-    wolfSSL_set_alpn_protos(qs->ssl, alpn, (int)alpnlen);
-
-  /* set SNI */
-  wolfSSL_UseSNI(qs->ssl, WOLFSSL_SNI_HOST_NAME,
-                 hostname, (unsigned short)strlen(hostname));
-
-  return CURLE_OK;
-}
-#endif /* defined(USE_WOLFSSL) */
-
-static int cb_handshake_completed(ngtcp2_conn *tconn, void *user_data)
-{
-  (void)user_data;
-  (void)tconn;
-  return 0;
-}
-
-static void extend_stream_window(ngtcp2_conn *tconn,
-                                 struct HTTP *stream)
-{
-  size_t thismuch = stream->unacked_window;
-  ngtcp2_conn_extend_max_stream_offset(tconn, stream->stream3_id, thismuch);
-  ngtcp2_conn_extend_max_offset(tconn, thismuch);
-  stream->unacked_window = 0;
-}
-
-
-static int cb_recv_stream_data(ngtcp2_conn *tconn, uint32_t flags,
-                               int64_t stream_id, uint64_t offset,
-                               const uint8_t *buf, size_t buflen,
-                               void *user_data, void *stream_user_data)
-{
-  struct quicsocket *qs = (struct quicsocket *)user_data;
-  nghttp3_ssize nconsumed;
-  int fin = (flags & NGTCP2_STREAM_DATA_FLAG_FIN) ? 1 : 0;
-  (void)offset;
-  (void)stream_user_data;
-
-  nconsumed =
-    nghttp3_conn_read_stream(qs->h3conn, stream_id, buf, buflen, fin);
-  if(nconsumed < 0) {
-    ngtcp2_connection_close_error_set_application_error(
-        &qs->last_error, nghttp3_err_infer_quic_app_error_code((int)nconsumed),
-        NULL, 0);
-    return NGTCP2_ERR_CALLBACK_FAILURE;
-  }
-
-  /* number of bytes inside buflen which consists of framing overhead
-   * including QPACK HEADERS. In other words, it does not consume payload of
-   * DATA frame. */
-  ngtcp2_conn_extend_max_stream_offset(tconn, stream_id, nconsumed);
-  ngtcp2_conn_extend_max_offset(tconn, nconsumed);
-
-  return 0;
-}
-
-static int
-cb_acked_stream_data_offset(ngtcp2_conn *tconn, int64_t stream_id,
-                            uint64_t offset, uint64_t datalen, void *user_data,
-                            void *stream_user_data)
-{
-  struct quicsocket *qs = (struct quicsocket *)user_data;
-  int rv;
-  (void)stream_id;
-  (void)tconn;
-  (void)offset;
-  (void)datalen;
-  (void)stream_user_data;
-
-  rv = nghttp3_conn_add_ack_offset(qs->h3conn, stream_id, datalen);
-  if(rv) {
-    return NGTCP2_ERR_CALLBACK_FAILURE;
-  }
-
-  return 0;
-}
-
-static int cb_stream_close(ngtcp2_conn *tconn, uint32_t flags,
-                           int64_t stream_id, uint64_t app_error_code,
-                           void *user_data, void *stream_user_data)
-{
-  struct quicsocket *qs = (struct quicsocket *)user_data;
-  int rv;
-  (void)tconn;
-  (void)stream_user_data;
-  /* stream is closed... */
-
-  if(!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) {
-    app_error_code = NGHTTP3_H3_NO_ERROR;
-  }
-
-  rv = nghttp3_conn_close_stream(qs->h3conn, stream_id,
-                                 app_error_code);
-  if(rv) {
-    ngtcp2_connection_close_error_set_application_error(
-        &qs->last_error, nghttp3_err_infer_quic_app_error_code(rv), NULL, 0);
-    return NGTCP2_ERR_CALLBACK_FAILURE;
-  }
-
-  return 0;
-}
-
-static int cb_stream_reset(ngtcp2_conn *tconn, int64_t stream_id,
-                           uint64_t final_size, uint64_t app_error_code,
-                           void *user_data, void *stream_user_data)
-{
-  struct quicsocket *qs = (struct quicsocket *)user_data;
-  int rv;
-  (void)tconn;
-  (void)final_size;
-  (void)app_error_code;
-  (void)stream_user_data;
-
-  rv = nghttp3_conn_shutdown_stream_read(qs->h3conn, stream_id);
-  if(rv) {
-    return NGTCP2_ERR_CALLBACK_FAILURE;
-  }
-
-  return 0;
-}
-
-static int cb_stream_stop_sending(ngtcp2_conn *tconn, int64_t stream_id,
-                                  uint64_t app_error_code, void *user_data,
-                                  void *stream_user_data)
-{
-  struct quicsocket *qs = (struct quicsocket *)user_data;
-  int rv;
-  (void)tconn;
-  (void)app_error_code;
-  (void)stream_user_data;
-
-  rv = nghttp3_conn_shutdown_stream_read(qs->h3conn, stream_id);
-  if(rv) {
-    return NGTCP2_ERR_CALLBACK_FAILURE;
-  }
-
-  return 0;
-}
-
-static int cb_extend_max_local_streams_bidi(ngtcp2_conn *tconn,
-                                            uint64_t max_streams,
-                                            void *user_data)
-{
-  (void)tconn;
-  (void)max_streams;
-  (void)user_data;
-
-  return 0;
-}
-
-static int cb_extend_max_stream_data(ngtcp2_conn *tconn, int64_t stream_id,
-                                     uint64_t max_data, void *user_data,
-                                     void *stream_user_data)
-{
-  struct quicsocket *qs = (struct quicsocket *)user_data;
-  int rv;
-  (void)tconn;
-  (void)max_data;
-  (void)stream_user_data;
-
-  rv = nghttp3_conn_unblock_stream(qs->h3conn, stream_id);
-  if(rv) {
-    return NGTCP2_ERR_CALLBACK_FAILURE;
-  }
-
-  return 0;
-}
-
-static void cb_rand(uint8_t *dest, size_t destlen,
-                    const ngtcp2_rand_ctx *rand_ctx)
-{
-  CURLcode result;
-  (void)rand_ctx;
-
-  result = Curl_rand(NULL, dest, destlen);
-  if(result) {
-    /* cb_rand is only used for non-cryptographic context.  If Curl_rand
-       failed, just fill 0 and call it *random*. */
-    memset(dest, 0, destlen);
-  }
-}
-
-static int cb_get_new_connection_id(ngtcp2_conn *tconn, ngtcp2_cid *cid,
-                                    uint8_t *token, size_t cidlen,
-                                    void *user_data)
-{
-  CURLcode result;
-  (void)tconn;
-  (void)user_data;
-
-  result = Curl_rand(NULL, cid->data, cidlen);
-  if(result)
-    return NGTCP2_ERR_CALLBACK_FAILURE;
-  cid->datalen = cidlen;
-
-  result = Curl_rand(NULL, token, NGTCP2_STATELESS_RESET_TOKENLEN);
-  if(result)
-    return NGTCP2_ERR_CALLBACK_FAILURE;
-
-  return 0;
-}
-
-static int cb_recv_rx_key(ngtcp2_conn *tconn, ngtcp2_crypto_level level,
-                          void *user_data)
-{
-  struct quicsocket *qs = (struct quicsocket *)user_data;
-  (void)tconn;
-
-  if(level != NGTCP2_CRYPTO_LEVEL_APPLICATION) {
-    return 0;
-  }
-
-  if(init_ngh3_conn(qs) != CURLE_OK) {
-    return NGTCP2_ERR_CALLBACK_FAILURE;
-  }
-
-  return 0;
-}
-
-static ngtcp2_callbacks ng_callbacks = {
-  ngtcp2_crypto_client_initial_cb,
-  NULL, /* recv_client_initial */
-  ngtcp2_crypto_recv_crypto_data_cb,
-  cb_handshake_completed,
-  NULL, /* recv_version_negotiation */
-  ngtcp2_crypto_encrypt_cb,
-  ngtcp2_crypto_decrypt_cb,
-  ngtcp2_crypto_hp_mask_cb,
-  cb_recv_stream_data,
-  cb_acked_stream_data_offset,
-  NULL, /* stream_open */
-  cb_stream_close,
-  NULL, /* recv_stateless_reset */
-  ngtcp2_crypto_recv_retry_cb,
-  cb_extend_max_local_streams_bidi,
-  NULL, /* extend_max_local_streams_uni */
-  cb_rand,
-  cb_get_new_connection_id,
-  NULL, /* remove_connection_id */
-  ngtcp2_crypto_update_key_cb, /* update_key */
-  NULL, /* path_validation */
-  NULL, /* select_preferred_addr */
-  cb_stream_reset,
-  NULL, /* extend_max_remote_streams_bidi */
-  NULL, /* extend_max_remote_streams_uni */
-  cb_extend_max_stream_data,
-  NULL, /* dcid_status */
-  NULL, /* handshake_confirmed */
-  NULL, /* recv_new_token */
-  ngtcp2_crypto_delete_crypto_aead_ctx_cb,
-  ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
-  NULL, /* recv_datagram */
-  NULL, /* ack_datagram */
-  NULL, /* lost_datagram */
-  ngtcp2_crypto_get_path_challenge_data_cb,
-  cb_stream_stop_sending,
-  NULL, /* version_negotiation */
-  cb_recv_rx_key,
-  NULL, /* recv_tx_key */
-  NULL, /* early_data_rejected */
-};
-
-/*
- * Might be called twice for happy eyeballs.
- */
-CURLcode Curl_quic_connect(struct Curl_easy *data,
-                           struct connectdata *conn,
-                           curl_socket_t sockfd,
-                           int sockindex,
-                           const struct sockaddr *addr,
-                           socklen_t addrlen)
-{
-  int rc;
-  int rv;
-  CURLcode result;
-  ngtcp2_path path; /* TODO: this must be initialized properly */
-  struct quicsocket *qs = &conn->hequic[sockindex];
-  char ipbuf[40];
-  int port;
-  int qfd;
-
-  if(qs->conn)
-    Curl_quic_disconnect(data, conn, sockindex);
-  qs->conn = conn;
-
-  /* extract the used address as a string */
-  if(!Curl_addr2string((struct sockaddr*)addr, addrlen, ipbuf, &port)) {
-    char buffer[STRERROR_LEN];
-    failf(data, "ssrem inet_ntop() failed with errno %d: %s",
-          SOCKERRNO, Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
-    return CURLE_BAD_FUNCTION_ARGUMENT;
-  }
-
-  infof(data, "Connect socket %d over QUIC to %s:%d",
-        sockfd, ipbuf, port);
-
-  qs->version = NGTCP2_PROTO_VER_MAX;
-#ifdef USE_OPENSSL
-  qs->sslctx = quic_ssl_ctx(data);
-  if(!qs->sslctx)
-    return CURLE_QUIC_CONNECT_ERROR;
-
-  result = quic_set_client_cert(data, qs);
-  if(result)
-    return result;
-#elif defined(USE_WOLFSSL)
-  qs->sslctx = quic_ssl_ctx(data);
-  if(!qs->sslctx)
-    return CURLE_QUIC_CONNECT_ERROR;
-#endif
-
-  result = quic_init_ssl(qs, data, conn);
-  if(result)
-    return result;
-
-  qs->dcid.datalen = NGTCP2_MAX_CIDLEN;
-  result = Curl_rand(data, qs->dcid.data, NGTCP2_MAX_CIDLEN);
-  if(result)
-    return result;
-
-  qs->scid.datalen = NGTCP2_MAX_CIDLEN;
-  result = Curl_rand(data, qs->scid.data, NGTCP2_MAX_CIDLEN);
-  if(result)
-    return result;
-
-  (void)Curl_qlogdir(data, qs->scid.data, NGTCP2_MAX_CIDLEN, &qfd);
-  qs->qlogfd = qfd; /* -1 if failure above */
-  quic_settings(qs, data->set.buffer_size);
-
-  qs->local_addrlen = sizeof(qs->local_addr);
-  rv = getsockname(sockfd, (struct sockaddr *)&qs->local_addr,
-                   &qs->local_addrlen);
-  if(rv == -1)
-    return CURLE_QUIC_CONNECT_ERROR;
-
-  ngtcp2_addr_init(&path.local, (struct sockaddr *)&qs->local_addr,
-                   qs->local_addrlen);
-  ngtcp2_addr_init(&path.remote, addr, addrlen);
-
-  rc = ngtcp2_conn_client_new(&qs->qconn, &qs->dcid, &qs->scid, &path,
-                              NGTCP2_PROTO_VER_V1, &ng_callbacks,
-                              &qs->settings, &qs->transport_params, NULL, qs);
-  if(rc)
-    return CURLE_QUIC_CONNECT_ERROR;
-
-#ifdef USE_GNUTLS
-  ngtcp2_conn_set_tls_native_handle(qs->qconn, qs->gtls->session);
-#else
-  ngtcp2_conn_set_tls_native_handle(qs->qconn, qs->ssl);
-#endif
-
-  ngtcp2_connection_close_error_default(&qs->last_error);
-
-#if defined(__linux__) && defined(UDP_SEGMENT) && defined(HAVE_SENDMSG)
-  qs->no_gso = FALSE;
-#else
-  qs->no_gso = TRUE;
-#endif
-
-  qs->num_blocked_pkt = 0;
-  qs->num_blocked_pkt_sent = 0;
-  memset(&qs->blocked_pkt, 0, sizeof(qs->blocked_pkt));
-
-  qs->pktbuflen = NGTCP2_MAX_PMTUD_UDP_PAYLOAD_SIZE * MAX_PKT_BURST;
-  qs->pktbuf = malloc(qs->pktbuflen);
-  if(!qs->pktbuf) {
-    ngtcp2_conn_del(qs->qconn);
-    qs->qconn = NULL;
-    return CURLE_OUT_OF_MEMORY;
-  }
-
-  qs->conn_ref.get_conn = get_conn;
-  qs->conn_ref.user_data = qs;
-
-  return CURLE_OK;
-}
-
-/*
- * Store ngtcp2 version info in this buffer.
- */
-void Curl_quic_ver(char *p, size_t len)
-{
-  const ngtcp2_info *ng2 = ngtcp2_version(0);
-  const nghttp3_info *ht3 = nghttp3_version(0);
-  (void)msnprintf(p, len, "ngtcp2/%s nghttp3/%s",
-                  ng2->version_str, ht3->version_str);
-}
-
-static int ng_getsock(struct Curl_easy *data, struct connectdata *conn,
-                      curl_socket_t *socks)
-{
-  struct SingleRequest *k = &data->req;
-  int bitmap = GETSOCK_BLANK;
-  struct HTTP *stream = data->req.p.http;
-  struct quicsocket *qs = conn->quic;
-
-  socks[0] = conn->sock[FIRSTSOCKET];
-
-  /* in an HTTP/2 connection we can basically always get a frame so we should
-     always be ready for one */
-  bitmap |= GETSOCK_READSOCK(FIRSTSOCKET);
-
-  /* we're still uploading or the HTTP/2 layer wants to send data */
-  if((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND &&
-     (!stream->h3out || stream->h3out->used < H3_SEND_SIZE) &&
-     ngtcp2_conn_get_cwnd_left(qs->qconn) &&
-     ngtcp2_conn_get_max_data_left(qs->qconn) &&
-     nghttp3_conn_is_stream_writable(qs->h3conn, stream->stream3_id))
-    bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET);
-
-  return bitmap;
-}
-
-static void qs_disconnect(struct quicsocket *qs)
-{
-  char buffer[NGTCP2_MAX_UDP_PAYLOAD_SIZE];
-  ngtcp2_tstamp ts;
-  ngtcp2_ssize rc;
-
-  if(!qs->conn) /* already closed */
-    return;
-  ts = timestamp();
-  rc = ngtcp2_conn_write_connection_close(qs->qconn, NULL, /* path */
-                                          NULL, /* pkt_info */
-                                          (uint8_t *)buffer, sizeof(buffer),
-                                          &qs->last_error, ts);
-  if(rc > 0) {
-    while((send(qs->conn->sock[FIRSTSOCKET], buffer, rc, 0) == -1) &&
-          SOCKERRNO == EINTR);
-  }
-
-  qs->conn = NULL;
-  if(qs->qlogfd != -1) {
-    close(qs->qlogfd);
-    qs->qlogfd = -1;
-  }
-#ifdef USE_OPENSSL
-  if(qs->ssl)
-    SSL_free(qs->ssl);
-  qs->ssl = NULL;
-  SSL_CTX_free(qs->sslctx);
-#elif defined(USE_GNUTLS)
-  if(qs->gtls) {
-    if(qs->gtls->cred)
-      gnutls_certificate_free_credentials(qs->gtls->cred);
-    if(qs->gtls->session)
-      gnutls_deinit(qs->gtls->session);
-    free(qs->gtls);
-    qs->gtls = NULL;
-  }
-#elif defined(USE_WOLFSSL)
-  if(qs->ssl)
-    wolfSSL_free(qs->ssl);
-  qs->ssl = NULL;
-  wolfSSL_CTX_free(qs->sslctx);
-#endif
-  free(qs->pktbuf);
-  nghttp3_conn_del(qs->h3conn);
-  ngtcp2_conn_del(qs->qconn);
-}
-
-void Curl_quic_disconnect(struct Curl_easy *data,
-                          struct connectdata *conn,
-                          int tempindex)
-{
-  (void)data;
-  if(conn->transport == TRNSPRT_QUIC)
-    qs_disconnect(&conn->hequic[tempindex]);
-}
-
-static CURLcode ng_disconnect(struct Curl_easy *data,
-                              struct connectdata *conn,
-                              bool dead_connection)
-{
-  (void)dead_connection;
-  Curl_quic_disconnect(data, conn, 0);
-  Curl_quic_disconnect(data, conn, 1);
-  return CURLE_OK;
-}
-
-static unsigned int ng_conncheck(struct Curl_easy *data,
-                                 struct connectdata *conn,
-                                 unsigned int checks_to_perform)
-{
-  (void)data;
-  (void)conn;
-  (void)checks_to_perform;
-  return CONNRESULT_NONE;
-}
-
-static const struct Curl_handler Curl_handler_http3 = {
-  "HTTPS",                              /* scheme */
-  ZERO_NULL,                            /* setup_connection */
-  Curl_http,                            /* do_it */
-  Curl_http_done,                       /* done */
-  ZERO_NULL,                            /* do_more */
-  ZERO_NULL,                            /* connect_it */
-  ZERO_NULL,                            /* connecting */
-  ZERO_NULL,                            /* doing */
-  ng_getsock,                           /* proto_getsock */
-  ng_getsock,                           /* doing_getsock */
-  ZERO_NULL,                            /* domore_getsock */
-  ng_getsock,                           /* perform_getsock */
-  ng_disconnect,                        /* disconnect */
-  ZERO_NULL,                            /* readwrite */
-  ng_conncheck,                         /* connection_check */
-  ZERO_NULL,                            /* attach connection */
-  PORT_HTTP,                            /* defport */
-  CURLPROTO_HTTPS,                      /* protocol */
-  CURLPROTO_HTTP,                       /* family */
-  PROTOPT_SSL | PROTOPT_STREAM          /* flags */
-};
-
-static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id,
-                              uint64_t app_error_code, void *user_data,
-                              void *stream_user_data)
-{
-  struct Curl_easy *data = stream_user_data;
-  struct HTTP *stream = data->req.p.http;
-  (void)conn;
-  (void)stream_id;
-  (void)app_error_code;
-  (void)user_data;
-  H3BUGF(infof(data, "cb_h3_stream_close CALLED"));
-
-  stream->closed = TRUE;
-  stream->error3 = app_error_code;
-  Curl_expire(data, 0, EXPIRE_QUIC);
-  /* make sure that ngh3_stream_recv is called again to complete the transfer
-     even if there are no more packets to be received from the server. */
-  data->state.drain = 1;
-  return 0;
-}
-
-/*
- * write_data() copies data to the stream's receive buffer. If not enough
- * space is available in the receive buffer, it copies the rest to the
- * stream's overflow buffer.
- */
-static CURLcode write_data(struct HTTP *stream, const void *mem, size_t memlen)
-{
-  CURLcode result = CURLE_OK;
-  const char *buf = mem;
-  size_t ncopy = memlen;
-  /* copy as much as possible to the receive buffer */
-  if(stream->len) {
-    size_t len = CURLMIN(ncopy, stream->len);
-    memcpy(stream->mem, buf, len);
-    stream->len -= len;
-    stream->memlen += len;
-    stream->mem += len;
-    buf += len;
-    ncopy -= len;
-  }
-  /* copy the rest to the overflow buffer */
-  if(ncopy)
-    result = Curl_dyn_addn(&stream->overflow, buf, ncopy);
-  return result;
-}
-
-static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream_id,
-                           const uint8_t *buf, size_t buflen,
-                           void *user_data, void *stream_user_data)
-{
-  struct Curl_easy *data = stream_user_data;
-  struct HTTP *stream = data->req.p.http;
-  CURLcode result = CURLE_OK;
-  (void)conn;
-
-  result = write_data(stream, buf, buflen);
-  if(result) {
-    return -1;
-  }
-  stream->unacked_window += buflen;
-  (void)stream_id;
-  (void)user_data;
-  return 0;
-}
-
-static int cb_h3_deferred_consume(nghttp3_conn *conn, int64_t stream_id,
-                                  size_t consumed, void *user_data,
-                                  void *stream_user_data)
-{
-  struct quicsocket *qs = user_data;
-  (void)conn;
-  (void)stream_user_data;
-  (void)stream_id;
-
-  ngtcp2_conn_extend_max_stream_offset(qs->qconn, stream_id, consumed);
-  ngtcp2_conn_extend_max_offset(qs->qconn, consumed);
-  return 0;
-}
-
-/* Decode HTTP status code.  Returns -1 if no valid status code was
-   decoded. (duplicate from http2.c) */
-static int decode_status_code(const uint8_t *value, size_t len)
-{
-  int i;
-  int res;
-
-  if(len != 3) {
-    return -1;
-  }
-
-  res = 0;
-
-  for(i = 0; i < 3; ++i) {
-    char c = value[i];
-
-    if(c < '0' || c > '9') {
-      return -1;
-    }
-
-    res *= 10;
-    res += c - '0';
-  }
-
-  return res;
-}
-
-static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id,
-                             int fin, void *user_data, void *stream_user_data)
-{
-  struct Curl_easy *data = stream_user_data;
-  struct HTTP *stream = data->req.p.http;
-  CURLcode result = CURLE_OK;
-  (void)conn;
-  (void)stream_id;
-  (void)user_data;
-  (void)fin;
-
-  /* add a CRLF only if we've received some headers */
-  if(stream->firstheader) {
-    result = write_data(stream, "\r\n", 2);
-    if(result) {
-      return -1;
-    }
-  }
-
-  if(stream->status_code / 100 != 1) {
-    stream->bodystarted = TRUE;
-  }
-  return 0;
-}
-
-static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id,
-                             int32_t token, nghttp3_rcbuf *name,
-                             nghttp3_rcbuf *value, uint8_t flags,
-                             void *user_data, void *stream_user_data)
-{
-  nghttp3_vec h3name = nghttp3_rcbuf_get_buf(name);
-  nghttp3_vec h3val = nghttp3_rcbuf_get_buf(value);
-  struct Curl_easy *data = stream_user_data;
-  struct HTTP *stream = data->req.p.http;
-  CURLcode result = CURLE_OK;
-  (void)conn;
-  (void)stream_id;
-  (void)token;
-  (void)flags;
-  (void)user_data;
-
-  if(token == NGHTTP3_QPACK_TOKEN__STATUS) {
-    char line[14]; /* status line is always 13 characters long */
-    size_t ncopy;
-    stream->status_code = decode_status_code(h3val.base, h3val.len);
-    DEBUGASSERT(stream->status_code != -1);
-    ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n",
-                      stream->status_code);
-    result = write_data(stream, line, ncopy);
-    if(result) {
-      return -1;
-    }
-  }
-  else {
-    /* store as an HTTP1-style header */
-    result = write_data(stream, h3name.base, h3name.len);
-    if(result) {
-      return -1;
-    }
-    result = write_data(stream, ": ", 2);
-    if(result) {
-      return -1;
-    }
-    result = write_data(stream, h3val.base, h3val.len);
-    if(result) {
-      return -1;
-    }
-    result = write_data(stream, "\r\n", 2);
-    if(result) {
-      return -1;
-    }
-  }
-
-  stream->firstheader = TRUE;
-  return 0;
-}
-
-static int cb_h3_stop_sending(nghttp3_conn *conn, int64_t stream_id,
-                              uint64_t app_error_code, void *user_data,
-                              void *stream_user_data)
-{
-  struct quicsocket *qs = user_data;
-  int rv;
-  (void)conn;
-  (void)stream_user_data;
-
-  rv = ngtcp2_conn_shutdown_stream_read(qs->qconn, stream_id, app_error_code);
-  if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) {
-    return NGTCP2_ERR_CALLBACK_FAILURE;
-  }
-
-  return 0;
-}
-
-static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t stream_id,
-                              uint64_t app_error_code, void *user_data,
-                              void *stream_user_data) {
-  struct quicsocket *qs = user_data;
-  int rv;
-  (void)conn;
-  (void)stream_user_data;
-
-  rv = ngtcp2_conn_shutdown_stream_write(qs->qconn, stream_id, app_error_code);
-  if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) {
-    return NGTCP2_ERR_CALLBACK_FAILURE;
-  }
-
-  return 0;
-}
-
-static nghttp3_callbacks ngh3_callbacks = {
-  cb_h3_acked_stream_data, /* acked_stream_data */
-  cb_h3_stream_close,
-  cb_h3_recv_data,
-  cb_h3_deferred_consume,
-  NULL, /* begin_headers */
-  cb_h3_recv_header,
-  cb_h3_end_headers,
-  NULL, /* begin_trailers */
-  cb_h3_recv_header,
-  NULL, /* end_trailers */
-  cb_h3_stop_sending,
-  NULL, /* end_stream */
-  cb_h3_reset_stream,
-  NULL /* shutdown */
-};
-
-static int init_ngh3_conn(struct quicsocket *qs)
-{
-  CURLcode result;
-  int rc;
-  int64_t ctrl_stream_id, qpack_enc_stream_id, qpack_dec_stream_id;
-
-  if(ngtcp2_conn_get_max_local_streams_uni(qs->qconn) < 3) {
-    return CURLE_QUIC_CONNECT_ERROR;
-  }
-
-  nghttp3_settings_default(&qs->h3settings);
-
-  rc = nghttp3_conn_client_new(&qs->h3conn,
-                               &ngh3_callbacks,
-                               &qs->h3settings,
-                               nghttp3_mem_default(),
-                               qs);
-  if(rc) {
-    result = CURLE_OUT_OF_MEMORY;
-    goto fail;
-  }
-
-  rc = ngtcp2_conn_open_uni_stream(qs->qconn, &ctrl_stream_id, NULL);
-  if(rc) {
-    result = CURLE_QUIC_CONNECT_ERROR;
-    goto fail;
-  }
-
-  rc = nghttp3_conn_bind_control_stream(qs->h3conn, ctrl_stream_id);
-  if(rc) {
-    result = CURLE_QUIC_CONNECT_ERROR;
-    goto fail;
-  }
-
-  rc = ngtcp2_conn_open_uni_stream(qs->qconn, &qpack_enc_stream_id, NULL);
-  if(rc) {
-    result = CURLE_QUIC_CONNECT_ERROR;
-    goto fail;
-  }
-
-  rc = ngtcp2_conn_open_uni_stream(qs->qconn, &qpack_dec_stream_id, NULL);
-  if(rc) {
-    result = CURLE_QUIC_CONNECT_ERROR;
-    goto fail;
-  }
-
-  rc = nghttp3_conn_bind_qpack_streams(qs->h3conn, qpack_enc_stream_id,
-                                       qpack_dec_stream_id);
-  if(rc) {
-    result = CURLE_QUIC_CONNECT_ERROR;
-    goto fail;
-  }
-
-  return CURLE_OK;
-  fail:
-
-  return result;
-}
-
-static Curl_recv ngh3_stream_recv;
-static Curl_send ngh3_stream_send;
-
-static size_t drain_overflow_buffer(struct HTTP *stream)
-{
-  size_t overlen = Curl_dyn_len(&stream->overflow);
-  size_t ncopy = CURLMIN(overlen, stream->len);
-  if(ncopy > 0) {
-    memcpy(stream->mem, Curl_dyn_ptr(&stream->overflow), ncopy);
-    stream->len -= ncopy;
-    stream->mem += ncopy;
-    stream->memlen += ncopy;
-    if(ncopy != overlen)
-      /* make the buffer only keep the tail */
-      (void)Curl_dyn_tail(&stream->overflow, overlen - ncopy);
-    else
-      Curl_dyn_reset(&stream->overflow);
-  }
-  return ncopy;
-}
-
-/* incoming data frames on the h3 stream */
-static ssize_t ngh3_stream_recv(struct Curl_easy *data,
-                                int sockindex,
-                                char *buf,
-                                size_t buffersize,
-                                CURLcode *curlcode)
-{
-  struct connectdata *conn = data->conn;
-  curl_socket_t sockfd = conn->sock[sockindex];
-  struct HTTP *stream = data->req.p.http;
-  struct quicsocket *qs = conn->quic;
-
-  if(!stream->memlen) {
-    /* remember where to store incoming data for this stream and how big the
-       buffer is */
-    stream->mem = buf;
-    stream->len = buffersize;
-  }
-  /* else, there's data in the buffer already */
-
-  /* if there's data in the overflow buffer from a previous call, copy as much
-     as possible to the receive buffer before receiving more */
-  drain_overflow_buffer(stream);
-
-  if(ng_process_ingress(data, sockfd, qs)) {
-    *curlcode = CURLE_RECV_ERROR;
-    return -1;
-  }
-  if(ng_flush_egress(data, sockfd, qs)) {
-    *curlcode = CURLE_SEND_ERROR;
-    return -1;
-  }
-
-  if(stream->memlen) {
-    ssize_t memlen = stream->memlen;
-    /* data arrived */
-    *curlcode = CURLE_OK;
-    /* reset to allow more data to come */
-    stream->memlen = 0;
-    stream->mem = buf;
-    stream->len = buffersize;
-    /* extend the stream window with the data we're consuming and send out
-       any additional packets to tell the server that we can receive more */
-    extend_stream_window(qs->qconn, stream);
-    if(ng_flush_egress(data, sockfd, qs)) {
-      *curlcode = CURLE_SEND_ERROR;
-      return -1;
-    }
-    return memlen;
-  }
-
-  if(stream->closed) {
-    if(stream->error3 != NGHTTP3_H3_NO_ERROR) {
-      failf(data,
-            "HTTP/3 stream %" PRId64 " was not closed cleanly: (err %" PRIu64
-            ")",
-            stream->stream3_id, stream->error3);
-      *curlcode = CURLE_HTTP3;
-      return -1;
-    }
-
-    if(!stream->bodystarted) {
-      failf(data,
-            "HTTP/3 stream %" PRId64 " was closed cleanly, but before getting"
-            " all response header fields, treated as error",
-            stream->stream3_id);
-      *curlcode = CURLE_HTTP3;
-      return -1;
-    }
-
-    *curlcode = CURLE_OK;
-    return 0;
-  }
-
-  infof(data, "ngh3_stream_recv returns 0 bytes and EAGAIN");
-  *curlcode = CURLE_AGAIN;
-  return -1;
-}
-
-/* this amount of data has now been acked on this stream */
-static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id,
-                                   uint64_t datalen, void *user_data,
-                                   void *stream_user_data)
-{
-  struct Curl_easy *data = stream_user_data;
-  struct HTTP *stream = data->req.p.http;
-  (void)user_data;
-
-  if(!data->set.postfields) {
-    stream->h3out->used -= datalen;
-    H3BUGF(infof(data,
-                 "cb_h3_acked_stream_data, %zd bytes, %zd left unacked",
-                 datalen, stream->h3out->used));
-    DEBUGASSERT(stream->h3out->used < H3_SEND_SIZE);
-
-    if(stream->h3out->used == 0) {
-      int rv = nghttp3_conn_resume_stream(conn, stream_id);
-      if(rv) {
-        return NGTCP2_ERR_CALLBACK_FAILURE;
-      }
-    }
-  }
-  return 0;
-}
-
-static nghttp3_ssize cb_h3_readfunction(nghttp3_conn *conn, int64_t stream_id,
-                                        nghttp3_vec *vec, size_t veccnt,
-                                        uint32_t *pflags, void *user_data,
-                                        void *stream_user_data)
-{
-  struct Curl_easy *data = stream_user_data;
-  size_t nread;
-  struct HTTP *stream = data->req.p.http;
-  (void)conn;
-  (void)stream_id;
-  (void)user_data;
-  (void)veccnt;
-
-  if(data->set.postfields) {
-    vec[0].base = data->set.postfields;
-    vec[0].len = data->state.infilesize;
-    *pflags = NGHTTP3_DATA_FLAG_EOF;
-    return 1;
-  }
-
-  if(stream->upload_len && H3_SEND_SIZE <= stream->h3out->used) {
-    return NGHTTP3_ERR_WOULDBLOCK;
-  }
-
-  nread = CURLMIN(stream->upload_len, H3_SEND_SIZE - stream->h3out->used);
-  if(nread > 0) {
-    /* nghttp3 wants us to hold on to the data until it tells us it is okay to
-       delete it. Append the data at the end of the h3out buffer. Since we can
-       only return consecutive data, copy the amount that fits and the next
-       part comes in next invoke. */
-    struct h3out *out = stream->h3out;
-    if(nread + out->windex > H3_SEND_SIZE)
-      nread = H3_SEND_SIZE - out->windex;
-
-    memcpy(&out->buf[out->windex], stream->upload_mem, nread);
-
-    /* that's the chunk we return to nghttp3 */
-    vec[0].base = &out->buf[out->windex];
-    vec[0].len = nread;
-
-    out->windex += nread;
-    out->used += nread;
-
-    if(out->windex == H3_SEND_SIZE)
-      out->windex = 0; /* wrap */
-    stream->upload_mem += nread;
-    stream->upload_len -= nread;
-    if(data->state.infilesize != -1) {
-      stream->upload_left -= nread;
-      if(!stream->upload_left)
-        *pflags = NGHTTP3_DATA_FLAG_EOF;
-    }
-    H3BUGF(infof(data, "cb_h3_readfunction %zd bytes%s (at %zd unacked)",
-                 nread, *pflags == NGHTTP3_DATA_FLAG_EOF?" EOF":"",
-                 out->used));
-  }
-  if(stream->upload_done && !stream->upload_len &&
-     (stream->upload_left <= 0)) {
-    H3BUGF(infof(data, "cb_h3_readfunction sets EOF"));
-    *pflags = NGHTTP3_DATA_FLAG_EOF;
-    return nread ? 1 : 0;
-  }
-  else if(!nread) {
-    return NGHTTP3_ERR_WOULDBLOCK;
-  }
-  return 1;
-}
-
-/* Index where :authority header field will appear in request header
-   field list. */
-#define AUTHORITY_DST_IDX 3
-
-static CURLcode http_request(struct Curl_easy *data, const void *mem,
-                             size_t len)
-{
-  struct connectdata *conn = data->conn;
-  struct HTTP *stream = data->req.p.http;
-  size_t nheader;
-  struct quicsocket *qs = conn->quic;
-  CURLcode result = CURLE_OK;
-  nghttp3_nv *nva = NULL;
-  int64_t stream3_id;
-  int rc;
-  struct h3out *h3out = NULL;
-  struct h2h3req *hreq = NULL;
-
-  rc = ngtcp2_conn_open_bidi_stream(qs->qconn, &stream3_id, NULL);
-  if(rc) {
-    failf(data, "can get bidi streams");
-    result = CURLE_SEND_ERROR;
-    goto fail;
-  }
-
-  stream->stream3_id = stream3_id;
-  stream->h3req = TRUE; /* senf off! */
-  Curl_dyn_init(&stream->overflow, CURL_MAX_READ_SIZE);
-
-  result = Curl_pseudo_headers(data, mem, len, &hreq);
-  if(result)
-    goto fail;
-  nheader = hreq->entries;
-
-  nva = malloc(sizeof(nghttp3_nv) * nheader);
-  if(!nva) {
-    result = CURLE_OUT_OF_MEMORY;
-    goto fail;
-  }
-  else {
-    unsigned int i;
-    for(i = 0; i < nheader; i++) {
-      nva[i].name = (unsigned char *)hreq->header[i].name;
-      nva[i].namelen = hreq->header[i].namelen;
-      nva[i].value = (unsigned char *)hreq->header[i].value;
-      nva[i].valuelen = hreq->header[i].valuelen;
-      nva[i].flags = NGHTTP3_NV_FLAG_NONE;
-    }
-  }
-
-  switch(data->state.httpreq) {
-  case HTTPREQ_POST:
-  case HTTPREQ_POST_FORM:
-  case HTTPREQ_POST_MIME:
-  case HTTPREQ_PUT: {
-    nghttp3_data_reader data_reader;
-    if(data->state.infilesize != -1)
-      stream->upload_left = data->state.infilesize;
-    else
-      /* data sending without specifying the data amount up front */
-      stream->upload_left = -1; /* unknown, but not zero */
-
-    data_reader.read_data = cb_h3_readfunction;
-
-    h3out = calloc(sizeof(struct h3out), 1);
-    if(!h3out) {
-      result = CURLE_OUT_OF_MEMORY;
-      goto fail;
-    }
-    stream->h3out = h3out;
-
-    rc = nghttp3_conn_submit_request(qs->h3conn, stream->stream3_id,
-                                     nva, nheader, &data_reader, data);
-    if(rc) {
-      result = CURLE_SEND_ERROR;
-      goto fail;
-    }
-    break;
-  }
-  default:
-    stream->upload_left = 0; /* nothing left to send */
-    rc = nghttp3_conn_submit_request(qs->h3conn, stream->stream3_id,
-                                     nva, nheader, NULL, data);
-    if(rc) {
-      result = CURLE_SEND_ERROR;
-      goto fail;
-    }
-    break;
-  }
-
-  Curl_safefree(nva);
-
-  infof(data, "Using HTTP/3 Stream ID: %x (easy handle %p)",
-        stream3_id, (void *)data);
-
-  Curl_pseudo_free(hreq);
-  return CURLE_OK;
-
-fail:
-  free(nva);
-  Curl_pseudo_free(hreq);
-  return result;
-}
-static ssize_t ngh3_stream_send(struct Curl_easy *data,
-                                int sockindex,
-                                const void *mem,
-                                size_t len,
-                                CURLcode *curlcode)
-{
-  ssize_t sent = 0;
-  struct connectdata *conn = data->conn;
-  struct quicsocket *qs = conn->quic;
-  curl_socket_t sockfd = conn->sock[sockindex];
-  struct HTTP *stream = data->req.p.http;
-
-  if(stream->closed) {
-    *curlcode = CURLE_HTTP3;
-    return -1;
-  }
-
-  if(!stream->h3req) {
-    CURLcode result = http_request(data, mem, len);
-    if(result) {
-      *curlcode = CURLE_SEND_ERROR;
-      return -1;
-    }
-    /* Assume that mem of length len only includes HTTP/1.1 style
-       header fields.  In other words, it does not contain request
-       body. */
-    sent = len;
-  }
-  else {
-    H3BUGF(infof(data, "ngh3_stream_send() wants to send %zd bytes",
-                 len));
-    if(!stream->upload_len) {
-      stream->upload_mem = mem;
-      stream->upload_len = len;
-      (void)nghttp3_conn_resume_stream(qs->h3conn, stream->stream3_id);
-    }
-    else {
-      *curlcode = CURLE_AGAIN;
-      return -1;
-    }
-  }
-
-  if(ng_flush_egress(data, sockfd, qs)) {
-    *curlcode = CURLE_SEND_ERROR;
-    return -1;
-  }
-
-  /* Reset post upload buffer after resumed. */
-  if(stream->upload_mem) {
-    if(data->set.postfields) {
-      sent = len;
-    }
-    else {
-      sent = len - stream->upload_len;
-    }
-
-    stream->upload_mem = NULL;
-    stream->upload_len = 0;
-
-    if(sent == 0) {
-      *curlcode = CURLE_AGAIN;
-      return -1;
-    }
-  }
-
-  *curlcode = CURLE_OK;
-  return sent;
-}
-
-static CURLcode ng_has_connected(struct Curl_easy *data,
-                                 struct connectdata *conn, int tempindex)
-{
-  CURLcode result = CURLE_OK;
-  const char *hostname, *disp_hostname;
-  int port;
-  char *snihost;
-
-  Curl_conn_get_host(data, FIRSTSOCKET, &hostname, &disp_hostname, &port);
-  snihost = Curl_ssl_snihost(data, hostname, NULL);
-  if(!snihost)
-      return CURLE_PEER_FAILED_VERIFICATION;
-
-  conn->recv[FIRSTSOCKET] = ngh3_stream_recv;
-  conn->send[FIRSTSOCKET] = ngh3_stream_send;
-  conn->handler = &Curl_handler_http3;
-  conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
-  conn->httpversion = 30;
-  conn->bundle->multiuse = BUNDLE_MULTIPLEX;
-  conn->quic = &conn->hequic[tempindex];
-
-  if(conn->ssl_config.verifyhost) {
-#ifdef USE_OPENSSL
-    X509 *server_cert;
-    server_cert = SSL_get_peer_certificate(conn->quic->ssl);
-    if(!server_cert) {
-      return CURLE_PEER_FAILED_VERIFICATION;
-    }
-    result = Curl_ossl_verifyhost(data, conn, server_cert);
-    X509_free(server_cert);
-    if(result)
-      return result;
-#elif defined(USE_GNUTLS)
-    result = Curl_gtls_verifyserver(data, conn->quic->gtls->session,
-                                    &conn->ssl_config, &data->set.ssl,
-                                    hostname, disp_hostname,
-                                    data->set.str[STRING_SSL_PINNEDPUBLICKEY]);
-    if(result)
-      return result;
-#elif defined(USE_WOLFSSL)
-    if(wolfSSL_check_domain_name(conn->quic->ssl, snihost) == SSL_FAILURE)
-      return CURLE_PEER_FAILED_VERIFICATION;
-#endif
-    infof(data, "Verified certificate just fine");
-  }
-  else
-    infof(data, "Skipped certificate verification");
-#ifdef USE_OPENSSL
-  if(data->set.ssl.certinfo)
-    /* asked to gather certificate info */
-    (void)Curl_ossl_certchain(data, conn->quic->ssl);
-#endif
-  return result;
-}
-
-/*
- * There can be multiple connection attempts going on in parallel.
- */
-CURLcode Curl_quic_is_connected(struct Curl_easy *data,
-                                struct connectdata *conn,
-                                int sockindex,
-                                bool *done)
-{
-  CURLcode result;
-  struct quicsocket *qs = &conn->hequic[sockindex];
-  curl_socket_t sockfd = conn->tempsock[sockindex];
-
-  result = ng_process_ingress(data, sockfd, qs);
-  if(result)
-    goto error;
-
-  result = ng_flush_egress(data, sockfd, qs);
-  if(result)
-    goto error;
-
-  if(ngtcp2_conn_get_handshake_completed(qs->qconn)) {
-    result = ng_has_connected(data, conn, sockindex);
-    if(!result)
-      *done = TRUE;
-  }
-
-  return result;
-  error:
-  (void)qs_disconnect(qs);
-  return result;
-
-}
-
-static CURLcode ng_process_ingress(struct Curl_easy *data,
-                                   curl_socket_t sockfd,
-                                   struct quicsocket *qs)
-{
-  ssize_t recvd;
-  int rv;
-  uint8_t buf[65536];
-  size_t bufsize = sizeof(buf);
-  struct sockaddr_storage remote_addr;
-  socklen_t remote_addrlen;
-  ngtcp2_path path;
-  ngtcp2_tstamp ts = timestamp();
-  ngtcp2_pkt_info pi = { 0 };
-
-  for(;;) {
-    remote_addrlen = sizeof(remote_addr);
-    while((recvd = recvfrom(sockfd, (char *)buf, bufsize, 0,
-                            (struct sockaddr *)&remote_addr,
-                            &remote_addrlen)) == -1 &&
-          SOCKERRNO == EINTR)
-      ;
-    if(recvd == -1) {
-      if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK)
-        break;
-
-      failf(data, "ngtcp2: recvfrom() unexpectedly returned %zd", recvd);
-      return CURLE_RECV_ERROR;
-    }
-
-    ngtcp2_addr_init(&path.local, (struct sockaddr *)&qs->local_addr,
-                     qs->local_addrlen);
-    ngtcp2_addr_init(&path.remote, (struct sockaddr *)&remote_addr,
-                     remote_addrlen);
-
-    rv = ngtcp2_conn_read_pkt(qs->qconn, &path, &pi, buf, recvd, ts);
-    if(rv) {
-      if(!qs->last_error.error_code) {
-        if(rv == NGTCP2_ERR_CRYPTO) {
-          ngtcp2_connection_close_error_set_transport_error_tls_alert(
-              &qs->last_error, ngtcp2_conn_get_tls_alert(qs->qconn), NULL, 0);
-        }
-        else {
-          ngtcp2_connection_close_error_set_transport_error_liberr(
-              &qs->last_error, rv, NULL, 0);
-        }
-      }
-
-      if(rv == NGTCP2_ERR_CRYPTO)
-        /* this is a "TLS problem", but a failed certificate verification
-           is a common reason for this */
-        return CURLE_PEER_FAILED_VERIFICATION;
-      return CURLE_RECV_ERROR;
-    }
-  }
-
-  return CURLE_OK;
-}
-
-static CURLcode do_sendmsg(size_t *sent, struct Curl_easy *data, int sockfd,
-                           struct quicsocket *qs, const uint8_t *pkt,
-                           size_t pktlen, size_t gsolen);
-
-static CURLcode send_packet_no_gso(size_t *psent, struct Curl_easy *data,
-                                   int sockfd, struct quicsocket *qs,
-                                   const uint8_t *pkt, size_t pktlen,
-                                   size_t gsolen)
-{
-  const uint8_t *p, *end = pkt + pktlen;
-  size_t sent;
-
-  *psent = 0;
-
-  for(p = pkt; p < end; p += gsolen) {
-    size_t len = CURLMIN(gsolen, (size_t)(end - p));
-    CURLcode curlcode = do_sendmsg(&sent, data, sockfd, qs, p, len, len);
-    if(curlcode != CURLE_OK) {
-      return curlcode;
-    }
-    *psent += sent;
-  }
-
-  return CURLE_OK;
-}
-
-static CURLcode do_sendmsg(size_t *psent, struct Curl_easy *data, int sockfd,
-                           struct quicsocket *qs, const uint8_t *pkt,
-                           size_t pktlen, size_t gsolen)
-{
-#ifdef HAVE_SENDMSG
-  struct iovec msg_iov;
-  struct msghdr msg = {0};
-  ssize_t sent;
-#if defined(__linux__) && defined(UDP_SEGMENT)
-  uint8_t msg_ctrl[32];
-  struct cmsghdr *cm;
-#endif
-
-  *psent = 0;
-  msg_iov.iov_base = (uint8_t *)pkt;
-  msg_iov.iov_len = pktlen;
-  msg.msg_iov = &msg_iov;
-  msg.msg_iovlen = 1;
-
-#if defined(__linux__) && defined(UDP_SEGMENT)
-  if(pktlen > gsolen) {
-    /* Only set this, when we need it. macOS, for example,
-     * does not seem to like a msg_control of length 0. */
-    msg.msg_control = msg_ctrl;
-    assert(sizeof(msg_ctrl) >= CMSG_SPACE(sizeof(uint16_t)));
-    msg.msg_controllen = CMSG_SPACE(sizeof(uint16_t));
-    cm = CMSG_FIRSTHDR(&msg);
-    cm->cmsg_level = SOL_UDP;
-    cm->cmsg_type = UDP_SEGMENT;
-    cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
-    *(uint16_t *)(void *)CMSG_DATA(cm) = gsolen & 0xffff;
-  }
-#endif
-
-
-  while((sent = sendmsg(sockfd, &msg, 0)) == -1 && SOCKERRNO == EINTR)
-    ;
-
-  if(sent == -1) {
-    switch(SOCKERRNO) {
-    case EAGAIN:
-#if EAGAIN != EWOULDBLOCK
-    case EWOULDBLOCK:
-#endif
-      return CURLE_AGAIN;
-    case EMSGSIZE:
-      /* UDP datagram is too large; caused by PMTUD. Just let it be lost. */
-      break;
-    case EIO:
-      if(pktlen > gsolen) {
-        /* GSO failure */
-        failf(data, "sendmsg() returned %zd (errno %d); disable GSO", sent,
-              SOCKERRNO);
-        qs->no_gso = TRUE;
-        return send_packet_no_gso(psent, data, sockfd, qs, pkt, pktlen,
-                                  gsolen);
-      }
-      /* FALLTHROUGH */
-    default:
-      failf(data, "sendmsg() returned %zd (errno %d)", sent, SOCKERRNO);
-      return CURLE_SEND_ERROR;
-    }
-  }
-  else {
-    assert(pktlen == (size_t)sent);
-  }
-#else
-  ssize_t sent;
-  (void)qs;
-  (void)gsolen;
-
-  *psent = 0;
-
-  while((sent = send(sockfd, (const char *)pkt, pktlen, 0)) == -1 &&
-        SOCKERRNO == EINTR)
-    ;
-
-  if(sent == -1) {
-    if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) {
-      return CURLE_AGAIN;
-    }
-    else {
-      failf(data, "send() returned %zd (errno %d)", sent, SOCKERRNO);
-      if(SOCKERRNO != EMSGSIZE) {
-        return CURLE_SEND_ERROR;
-      }
-      /* UDP datagram is too large; caused by PMTUD. Just let it be
-         lost. */
-    }
-  }
-#endif
-
-  *psent = pktlen;
-
-  return CURLE_OK;
-}
-
-static CURLcode send_packet(size_t *psent, struct Curl_easy *data, int sockfd,
-                            struct quicsocket *qs, const uint8_t *pkt,
-                            size_t pktlen, size_t gsolen)
-{
-  if(qs->no_gso && pktlen > gsolen) {
-    return send_packet_no_gso(psent, data, sockfd, qs, pkt, pktlen, gsolen);
-  }
-
-  return do_sendmsg(psent, data, sockfd, qs, pkt, pktlen, gsolen);
-}
-
-static void push_blocked_pkt(struct quicsocket *qs, const uint8_t *pkt,
-                             size_t pktlen, size_t gsolen)
-{
-  struct blocked_pkt *blkpkt;
-
-  assert(qs->num_blocked_pkt <
-         sizeof(qs->blocked_pkt) / sizeof(qs->blocked_pkt[0]));
-
-  blkpkt = &qs->blocked_pkt[qs->num_blocked_pkt++];
-
-  blkpkt->pkt = pkt;
-  blkpkt->pktlen = pktlen;
-  blkpkt->gsolen = gsolen;
-}
-
-static CURLcode send_blocked_pkt(struct Curl_easy *data, int sockfd,
-                                 struct quicsocket *qs)
-{
-  size_t sent;
-  CURLcode curlcode;
-  struct blocked_pkt *blkpkt;
-
-  for(; qs->num_blocked_pkt_sent < qs->num_blocked_pkt;
-      ++qs->num_blocked_pkt_sent) {
-    blkpkt = &qs->blocked_pkt[qs->num_blocked_pkt_sent];
-    curlcode = send_packet(&sent, data, sockfd, qs, blkpkt->pkt,
-                           blkpkt->pktlen, blkpkt->gsolen);
-
-    if(curlcode) {
-      if(curlcode == CURLE_AGAIN) {
-        blkpkt->pkt += sent;
-        blkpkt->pktlen -= sent;
-      }
-      return curlcode;
-    }
-  }
-
-  qs->num_blocked_pkt = 0;
-  qs->num_blocked_pkt_sent = 0;
-
-  return CURLE_OK;
-}
-
-static CURLcode ng_flush_egress(struct Curl_easy *data,
-                                int sockfd,
-                                struct quicsocket *qs)
-{
-  int rv;
-  size_t sent;
-  ngtcp2_ssize outlen;
-  uint8_t *outpos = qs->pktbuf;
-  size_t max_udp_payload_size =
-      ngtcp2_conn_get_max_tx_udp_payload_size(qs->qconn);
-  size_t path_max_udp_payload_size =
-      ngtcp2_conn_get_path_max_tx_udp_payload_size(qs->qconn);
-  size_t max_pktcnt =
-      CURLMIN(MAX_PKT_BURST, qs->pktbuflen / max_udp_payload_size);
-  size_t pktcnt = 0;
-  size_t gsolen;
-  ngtcp2_path_storage ps;
-  ngtcp2_tstamp ts = timestamp();
-  ngtcp2_tstamp expiry;
-  ngtcp2_duration timeout;
-  int64_t stream_id;
-  nghttp3_ssize veccnt;
-  int fin;
-  nghttp3_vec vec[16];
-  ngtcp2_ssize ndatalen;
-  uint32_t flags;
-  CURLcode curlcode;
-
-  rv = ngtcp2_conn_handle_expiry(qs->qconn, ts);
-  if(rv) {
-    failf(data, "ngtcp2_conn_handle_expiry returned error: %s",
-          ngtcp2_strerror(rv));
-    ngtcp2_connection_close_error_set_transport_error_liberr(&qs->last_error,
-                                                             rv, NULL, 0);
-    return CURLE_SEND_ERROR;
-  }
-
-  if(qs->num_blocked_pkt) {
-    curlcode = send_blocked_pkt(data, sockfd, qs);
-    if(curlcode) {
-      if(curlcode == CURLE_AGAIN) {
-        Curl_expire(data, 1, EXPIRE_QUIC);
-        return CURLE_OK;
-      }
-      return curlcode;
-    }
-  }
-
-  ngtcp2_path_storage_zero(&ps);
-
-  for(;;) {
-    veccnt = 0;
-    stream_id = -1;
-    fin = 0;
-
-    if(qs->h3conn && ngtcp2_conn_get_max_data_left(qs->qconn)) {
-      veccnt = nghttp3_conn_writev_stream(qs->h3conn, &stream_id, &fin, vec,
-                                          sizeof(vec) / sizeof(vec[0]));
-      if(veccnt < 0) {
-        failf(data, "nghttp3_conn_writev_stream returned error: %s",
-              nghttp3_strerror((int)veccnt));
-        ngtcp2_connection_close_error_set_application_error(
-            &qs->last_error,
-            nghttp3_err_infer_quic_app_error_code((int)veccnt), NULL, 0);
-        return CURLE_SEND_ERROR;
-      }
-    }
-
-    flags = NGTCP2_WRITE_STREAM_FLAG_MORE |
-            (fin ? NGTCP2_WRITE_STREAM_FLAG_FIN : 0);
-    outlen = ngtcp2_conn_writev_stream(qs->qconn, &ps.path, NULL, outpos,
-                                       max_udp_payload_size,
-                                       &ndatalen, flags, stream_id,
-                                       (const ngtcp2_vec *)vec, veccnt, ts);
-    if(outlen == 0) {
-      if(outpos != qs->pktbuf) {
-        curlcode = send_packet(&sent, data, sockfd, qs, qs->pktbuf,
-                               outpos - qs->pktbuf, gsolen);
-        if(curlcode) {
-          if(curlcode == CURLE_AGAIN) {
-            push_blocked_pkt(qs, qs->pktbuf + sent, outpos - qs->pktbuf - sent,
-                             gsolen);
-            Curl_expire(data, 1, EXPIRE_QUIC);
-            return CURLE_OK;
-          }
-          return curlcode;
-        }
-      }
-
-      break;
-    }
-    if(outlen < 0) {
-      switch(outlen) {
-      case NGTCP2_ERR_STREAM_DATA_BLOCKED:
-        assert(ndatalen == -1);
-        nghttp3_conn_block_stream(qs->h3conn, stream_id);
-        continue;
-      case NGTCP2_ERR_STREAM_SHUT_WR:
-        assert(ndatalen == -1);
-        nghttp3_conn_shutdown_stream_write(qs->h3conn, stream_id);
-        continue;
-      case NGTCP2_ERR_WRITE_MORE:
-        assert(ndatalen >= 0);
-        rv = nghttp3_conn_add_write_offset(qs->h3conn, stream_id, ndatalen);
-        if(rv) {
-          failf(data, "nghttp3_conn_add_write_offset returned error: %s\n",
-                nghttp3_strerror(rv));
-          return CURLE_SEND_ERROR;
-        }
-        continue;
-      default:
-        assert(ndatalen == -1);
-        failf(data, "ngtcp2_conn_writev_stream returned error: %s",
-              ngtcp2_strerror((int)outlen));
-        ngtcp2_connection_close_error_set_transport_error_liberr(
-            &qs->last_error, (int)outlen, NULL, 0);
-        return CURLE_SEND_ERROR;
-      }
-    }
-    else if(ndatalen >= 0) {
-      rv = nghttp3_conn_add_write_offset(qs->h3conn, stream_id, ndatalen);
-      if(rv) {
-        failf(data, "nghttp3_conn_add_write_offset returned error: %s\n",
-              nghttp3_strerror(rv));
-        return CURLE_SEND_ERROR;
-      }
-    }
-
-    outpos += outlen;
-
-    if(pktcnt == 0) {
-      gsolen = outlen;
-    }
-    else if((size_t)outlen > gsolen ||
-            (gsolen > path_max_udp_payload_size &&
-             (size_t)outlen != gsolen)) {
-      /* Packet larger than path_max_udp_payload_size is PMTUD probe
-         packet and it might not be sent because of EMSGSIZE. Send
-         them separately to minimize the loss. */
-      curlcode = send_packet(&sent, data, sockfd, qs, qs->pktbuf,
-                             outpos - outlen - qs->pktbuf, gsolen);
-      if(curlcode) {
-        if(curlcode == CURLE_AGAIN) {
-          push_blocked_pkt(qs, qs->pktbuf + sent,
-                           outpos - outlen - qs->pktbuf - sent, gsolen);
-          push_blocked_pkt(qs, outpos - outlen, outlen, outlen);
-          Curl_expire(data, 1, EXPIRE_QUIC);
-          return CURLE_OK;
-        }
-        return curlcode;
-      }
-      curlcode = send_packet(&sent, data, sockfd, qs, outpos - outlen, outlen,
-                             outlen);
-      if(curlcode) {
-        if(curlcode == CURLE_AGAIN) {
-          assert(0 == sent);
-          push_blocked_pkt(qs, outpos - outlen, outlen, outlen);
-          Curl_expire(data, 1, EXPIRE_QUIC);
-          return CURLE_OK;
-        }
-        return curlcode;
-      }
-
-      pktcnt = 0;
-      outpos = qs->pktbuf;
-      continue;
-    }
-
-    if(++pktcnt >= max_pktcnt || (size_t)outlen < gsolen) {
-      curlcode = send_packet(&sent, data, sockfd, qs, qs->pktbuf,
-                             outpos - qs->pktbuf, gsolen);
-      if(curlcode) {
-        if(curlcode == CURLE_AGAIN) {
-          push_blocked_pkt(qs, qs->pktbuf + sent, outpos - qs->pktbuf - sent,
-                           gsolen);
-          Curl_expire(data, 1, EXPIRE_QUIC);
-          return CURLE_OK;
-        }
-        return curlcode;
-      }
-
-      pktcnt = 0;
-      outpos = qs->pktbuf;
-    }
-  }
-
-  expiry = ngtcp2_conn_get_expiry(qs->qconn);
-  if(expiry != UINT64_MAX) {
-    if(expiry <= ts) {
-      timeout = 0;
-    }
-    else {
-      timeout = expiry - ts;
-      if(timeout % NGTCP2_MILLISECONDS) {
-        timeout += NGTCP2_MILLISECONDS;
-      }
-    }
-    Curl_expire(data, timeout / NGTCP2_MILLISECONDS, EXPIRE_QUIC);
-  }
-
-  return CURLE_OK;
-}
-
-/*
- * Called from transfer.c:done_sending when we stop HTTP/3 uploading.
- */
-CURLcode Curl_quic_done_sending(struct Curl_easy *data)
-{
-  struct connectdata *conn = data->conn;
-  DEBUGASSERT(conn);
-  if(conn->handler == &Curl_handler_http3) {
-    /* only for HTTP/3 transfers */
-    struct HTTP *stream = data->req.p.http;
-    struct quicsocket *qs = conn->quic;
-    stream->upload_done = TRUE;
-    (void)nghttp3_conn_resume_stream(qs->h3conn, stream->stream3_id);
-  }
-
-  return CURLE_OK;
-}
-
-/*
- * Called from http.c:Curl_http_done when a request completes.
- */
-void Curl_quic_done(struct Curl_easy *data, bool premature)
-{
-  (void)premature;
-  if(data->conn->handler == &Curl_handler_http3) {
-    /* only for HTTP/3 transfers */
-    struct HTTP *stream = data->req.p.http;
-    Curl_dyn_free(&stream->overflow);
-    free(stream->h3out);
-  }
-}
-
-/*
- * Called from transfer.c:data_pending to know if we should keep looping
- * to receive more data from the connection.
- */
-bool Curl_quic_data_pending(const struct Curl_easy *data)
-{
-  /* We may have received more data than we're able to hold in the receive
-     buffer and allocated an overflow buffer. Since it's possible that
-     there's no more data coming on the socket, we need to keep reading
-     until the overflow buffer is empty. */
-  const struct HTTP *stream = data->req.p.http;
-  return Curl_dyn_len(&stream->overflow) > 0;
-}
-
-/*
- * Called from transfer.c:Curl_readwrite when neither HTTP level read
- * nor write is performed. It is a good place to handle timer expiry
- * for QUIC transport.
- */
-CURLcode Curl_quic_idle(struct Curl_easy *data)
-{
-  struct connectdata *conn = data->conn;
-  curl_socket_t sockfd = conn->sock[FIRSTSOCKET];
-  struct quicsocket *qs = conn->quic;
-
-  if(ngtcp2_conn_get_expiry(qs->qconn) > timestamp()) {
-    return CURLE_OK;
-  }
-
-  if(ng_flush_egress(data, sockfd, qs)) {
-    return CURLE_SEND_ERROR;
-  }
-
-  return CURLE_OK;
-}
-
-#endif
diff --git a/Utilities/cmcurl/lib/vquic/ngtcp2.h b/Utilities/cmcurl/lib/vquic/ngtcp2.h
deleted file mode 100644
index 2265999..0000000
--- a/Utilities/cmcurl/lib/vquic/ngtcp2.h
+++ /dev/null
@@ -1,93 +0,0 @@
-#ifndef HEADER_CURL_VQUIC_NGTCP2_H
-#define HEADER_CURL_VQUIC_NGTCP2_H
-/***************************************************************************
- *                                  _   _ ____  _
- *  Project                     ___| | | |  _ \| |
- *                             / __| | | | |_) | |
- *                            | (__| |_| |  _ <| |___
- *                             \___|\___/|_| \_\_____|
- *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- *
- * This software is licensed as described in the file COPYING, which
- * you should have received as part of this distribution. The terms
- * are also available at https://curl.se/docs/copyright.html.
- *
- * You may opt to use, copy, modify, merge, publish, distribute and/or sell
- * copies of the Software, and permit persons to whom the Software is
- * furnished to do so, under the terms of the COPYING file.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- * SPDX-License-Identifier: curl
- *
- ***************************************************************************/
-
-#include "curl_setup.h"
-
-#ifdef USE_NGTCP2
-
-#ifdef HAVE_NETINET_UDP_H
-#include <netinet/udp.h>
-#endif
-
-#include <ngtcp2/ngtcp2_crypto.h>
-#include <nghttp3/nghttp3.h>
-#ifdef USE_OPENSSL
-#include <openssl/ssl.h>
-#elif defined(USE_WOLFSSL)
-#include <wolfssl/options.h>
-#include <wolfssl/ssl.h>
-#include <wolfssl/quic.h>
-#endif
-
-struct gtls_instance;
-
-struct blocked_pkt {
-  const uint8_t *pkt;
-  size_t pktlen;
-  size_t gsolen;
-};
-
-struct quicsocket {
-  struct connectdata *conn; /* point back to the connection */
-  ngtcp2_conn *qconn;
-  ngtcp2_cid dcid;
-  ngtcp2_cid scid;
-  uint32_t version;
-  ngtcp2_settings settings;
-  ngtcp2_transport_params transport_params;
-  ngtcp2_connection_close_error last_error;
-  ngtcp2_crypto_conn_ref conn_ref;
-#ifdef USE_OPENSSL
-  SSL_CTX *sslctx;
-  SSL *ssl;
-#elif defined(USE_GNUTLS)
-  struct gtls_instance *gtls;
-#elif defined(USE_WOLFSSL)
-  WOLFSSL_CTX *sslctx;
-  WOLFSSL *ssl;
-#endif
-  struct sockaddr_storage local_addr;
-  socklen_t local_addrlen;
-  bool no_gso;
-  uint8_t *pktbuf;
-  size_t pktbuflen;
-  /* the number of entries in blocked_pkt */
-  size_t num_blocked_pkt;
-  /* the number of processed entries in blocked_pkt */
-  size_t num_blocked_pkt_sent;
-  /* the packets blocked by sendmsg (EAGAIN or EWOULDBLOCK) */
-  struct blocked_pkt blocked_pkt[2];
-
-  nghttp3_conn *h3conn;
-  nghttp3_settings h3settings;
-  int qlogfd;
-};
-
-#include "urldata.h"
-
-#endif
-
-#endif /* HEADER_CURL_VQUIC_NGTCP2_H */
diff --git a/Utilities/cmcurl/lib/vquic/quiche.c b/Utilities/cmcurl/lib/vquic/quiche.c
deleted file mode 100644
index 2b9a041..0000000
--- a/Utilities/cmcurl/lib/vquic/quiche.c
+++ /dev/null
@@ -1,892 +0,0 @@
-/***************************************************************************
- *                                  _   _ ____  _
- *  Project                     ___| | | |  _ \| |
- *                             / __| | | | |_) | |
- *                            | (__| |_| |  _ <| |___
- *                             \___|\___/|_| \_\_____|
- *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- *
- * This software is licensed as described in the file COPYING, which
- * you should have received as part of this distribution. The terms
- * are also available at https://curl.se/docs/copyright.html.
- *
- * You may opt to use, copy, modify, merge, publish, distribute and/or sell
- * copies of the Software, and permit persons to whom the Software is
- * furnished to do so, under the terms of the COPYING file.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- * SPDX-License-Identifier: curl
- *
- ***************************************************************************/
-
-#include "curl_setup.h"
-
-#ifdef USE_QUICHE
-#include <quiche.h>
-#include <openssl/err.h>
-#include <openssl/ssl.h>
-#include "urldata.h"
-#include "sendf.h"
-#include "strdup.h"
-#include "rand.h"
-#include "quic.h"
-#include "strcase.h"
-#include "multiif.h"
-#include "connect.h"
-#include "strerror.h"
-#include "vquic.h"
-#include "transfer.h"
-#include "h2h3.h"
-#include "vtls/openssl.h"
-#include "vtls/keylog.h"
-
-/* The last 3 #include files should be in this order */
-#include "curl_printf.h"
-#include "curl_memory.h"
-#include "memdebug.h"
-
-#define DEBUG_HTTP3
-/* #define DEBUG_QUICHE */
-#ifdef DEBUG_HTTP3
-#define H3BUGF(x) x
-#else
-#define H3BUGF(x) do { } while(0)
-#endif
-
-#define QUIC_MAX_STREAMS (256*1024)
-#define QUIC_MAX_DATA (1*1024*1024)
-#define QUIC_IDLE_TIMEOUT (60 * 1000) /* milliseconds */
-
-static CURLcode process_ingress(struct Curl_easy *data,
-                                curl_socket_t sockfd,
-                                struct quicsocket *qs);
-
-static CURLcode flush_egress(struct Curl_easy *data, curl_socket_t sockfd,
-                             struct quicsocket *qs);
-
-static CURLcode http_request(struct Curl_easy *data, const void *mem,
-                             size_t len);
-static Curl_recv h3_stream_recv;
-static Curl_send h3_stream_send;
-
-static int quiche_getsock(struct Curl_easy *data,
-                          struct connectdata *conn, curl_socket_t *socks)
-{
-  struct SingleRequest *k = &data->req;
-  int bitmap = GETSOCK_BLANK;
-
-  socks[0] = conn->sock[FIRSTSOCKET];
-
-  /* in an HTTP/2 connection we can basically always get a frame so we should
-     always be ready for one */
-  bitmap |= GETSOCK_READSOCK(FIRSTSOCKET);
-
-  /* we're still uploading or the HTTP/2 layer wants to send data */
-  if((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND)
-    bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET);
-
-  return bitmap;
-}
-
-static CURLcode qs_disconnect(struct Curl_easy *data,
-                              struct quicsocket *qs)
-{
-  DEBUGASSERT(qs);
-  if(qs->conn) {
-    (void)quiche_conn_close(qs->conn, TRUE, 0, NULL, 0);
-    /* flushing the egress is not a failsafe way to deliver all the
-       outstanding packets, but we also don't want to get stuck here... */
-    (void)flush_egress(data, qs->sockfd, qs);
-    quiche_conn_free(qs->conn);
-    qs->conn = NULL;
-  }
-  if(qs->h3config)
-    quiche_h3_config_free(qs->h3config);
-  if(qs->h3c)
-    quiche_h3_conn_free(qs->h3c);
-  if(qs->cfg) {
-    quiche_config_free(qs->cfg);
-    qs->cfg = NULL;
-  }
-  return CURLE_OK;
-}
-
-static CURLcode quiche_disconnect(struct Curl_easy *data,
-                                  struct connectdata *conn,
-                                  bool dead_connection)
-{
-  struct quicsocket *qs = conn->quic;
-  (void)dead_connection;
-  return qs_disconnect(data, qs);
-}
-
-void Curl_quic_disconnect(struct Curl_easy *data,
-                          struct connectdata *conn,
-                          int tempindex)
-{
-  if(conn->transport == TRNSPRT_QUIC)
-    qs_disconnect(data, &conn->hequic[tempindex]);
-}
-
-static unsigned int quiche_conncheck(struct Curl_easy *data,
-                                     struct connectdata *conn,
-                                     unsigned int checks_to_perform)
-{
-  (void)data;
-  (void)conn;
-  (void)checks_to_perform;
-  return CONNRESULT_NONE;
-}
-
-static CURLcode quiche_do(struct Curl_easy *data, bool *done)
-{
-  struct HTTP *stream = data->req.p.http;
-  stream->h3req = FALSE; /* not sent */
-  return Curl_http(data, done);
-}
-
-static const struct Curl_handler Curl_handler_http3 = {
-  "HTTPS",                              /* scheme */
-  ZERO_NULL,                            /* setup_connection */
-  quiche_do,                            /* do_it */
-  Curl_http_done,                       /* done */
-  ZERO_NULL,                            /* do_more */
-  ZERO_NULL,                            /* connect_it */
-  ZERO_NULL,                            /* connecting */
-  ZERO_NULL,                            /* doing */
-  quiche_getsock,                       /* proto_getsock */
-  quiche_getsock,                       /* doing_getsock */
-  ZERO_NULL,                            /* domore_getsock */
-  quiche_getsock,                       /* perform_getsock */
-  quiche_disconnect,                    /* disconnect */
-  ZERO_NULL,                            /* readwrite */
-  quiche_conncheck,                     /* connection_check */
-  ZERO_NULL,                            /* attach connection */
-  PORT_HTTP,                            /* defport */
-  CURLPROTO_HTTPS,                      /* protocol */
-  CURLPROTO_HTTP,                       /* family */
-  PROTOPT_SSL | PROTOPT_STREAM          /* flags */
-};
-
-#ifdef DEBUG_QUICHE
-static void quiche_debug_log(const char *line, void *argp)
-{
-  (void)argp;
-  fprintf(stderr, "%s\n", line);
-}
-#endif
-
-static void keylog_callback(const SSL *ssl, const char *line)
-{
-  (void)ssl;
-  Curl_tls_keylog_write_line(line);
-}
-
-static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data)
-{
-  SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method());
-
-  SSL_CTX_set_alpn_protos(ssl_ctx,
-                          (const uint8_t *)QUICHE_H3_APPLICATION_PROTOCOL,
-                          sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1);
-
-  SSL_CTX_set_default_verify_paths(ssl_ctx);
-
-  /* Open the file if a TLS or QUIC backend has not done this before. */
-  Curl_tls_keylog_open();
-  if(Curl_tls_keylog_enabled()) {
-    SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback);
-  }
-
-  {
-    struct connectdata *conn = data->conn;
-    if(conn->ssl_config.verifypeer) {
-      const char * const ssl_cafile = conn->ssl_config.CAfile;
-      const char * const ssl_capath = conn->ssl_config.CApath;
-      if(ssl_cafile || ssl_capath) {
-        SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
-        /* tell OpenSSL where to find CA certificates that are used to verify
-           the server's certificate. */
-        if(!SSL_CTX_load_verify_locations(ssl_ctx, ssl_cafile, ssl_capath)) {
-          /* Fail if we insist on successfully verifying the server. */
-          failf(data, "error setting certificate verify locations:"
-                "  CAfile: %s CApath: %s",
-                ssl_cafile ? ssl_cafile : "none",
-                ssl_capath ? ssl_capath : "none");
-          return NULL;
-        }
-        infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
-        infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
-      }
-#ifdef CURL_CA_FALLBACK
-      else {
-        /* verifying the peer without any CA certificates won't work so
-           use openssl's built-in default as fallback */
-        SSL_CTX_set_default_verify_paths(ssl_ctx);
-      }
-#endif
-    }
-  }
-  return ssl_ctx;
-}
-
-static int quic_init_ssl(struct quicsocket *qs, struct connectdata *conn)
-{
-  /* this will need some attention when HTTPS proxy over QUIC get fixed */
-  const char * const hostname = conn->host.name;
-
-  DEBUGASSERT(!qs->ssl);
-  qs->ssl = SSL_new(qs->sslctx);
-
-  SSL_set_app_data(qs->ssl, qs);
-
-  /* set SNI */
-  SSL_set_tlsext_host_name(qs->ssl, hostname);
-  return 0;
-}
-
-
-CURLcode Curl_quic_connect(struct Curl_easy *data,
-                           struct connectdata *conn, curl_socket_t sockfd,
-                           int sockindex,
-                           const struct sockaddr *addr, socklen_t addrlen)
-{
-  CURLcode result;
-  struct quicsocket *qs = &conn->hequic[sockindex];
-  char ipbuf[40];
-  int port;
-  int rv;
-
-#ifdef DEBUG_QUICHE
-  /* initialize debug log callback only once */
-  static int debug_log_init = 0;
-  if(!debug_log_init) {
-    quiche_enable_debug_logging(quiche_debug_log, NULL);
-    debug_log_init = 1;
-  }
-#endif
-
-  (void)addr;
-  (void)addrlen;
-
-  qs->sockfd = sockfd;
-  qs->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION);
-  if(!qs->cfg) {
-    failf(data, "can't create quiche config");
-    return CURLE_FAILED_INIT;
-  }
-
-  quiche_config_set_max_idle_timeout(qs->cfg, QUIC_IDLE_TIMEOUT);
-  quiche_config_set_initial_max_data(qs->cfg, QUIC_MAX_DATA);
-  quiche_config_set_initial_max_stream_data_bidi_local(qs->cfg, QUIC_MAX_DATA);
-  quiche_config_set_initial_max_stream_data_bidi_remote(qs->cfg,
-                                                        QUIC_MAX_DATA);
-  quiche_config_set_initial_max_stream_data_uni(qs->cfg, QUIC_MAX_DATA);
-  quiche_config_set_initial_max_streams_bidi(qs->cfg, QUIC_MAX_STREAMS);
-  quiche_config_set_initial_max_streams_uni(qs->cfg, QUIC_MAX_STREAMS);
-  quiche_config_set_application_protos(qs->cfg,
-                                       (uint8_t *)
-                                       QUICHE_H3_APPLICATION_PROTOCOL,
-                                       sizeof(QUICHE_H3_APPLICATION_PROTOCOL)
-                                       - 1);
-
-  qs->sslctx = quic_ssl_ctx(data);
-  if(!qs->sslctx)
-    return CURLE_QUIC_CONNECT_ERROR;
-
-  if(quic_init_ssl(qs, conn))
-    return CURLE_QUIC_CONNECT_ERROR;
-
-  result = Curl_rand(data, qs->scid, sizeof(qs->scid));
-  if(result)
-    return result;
-
-  qs->local_addrlen = sizeof(qs->local_addr);
-  rv = getsockname(sockfd, (struct sockaddr *)&qs->local_addr,
-                   &qs->local_addrlen);
-  if(rv == -1)
-    return CURLE_QUIC_CONNECT_ERROR;
-
-  qs->conn = quiche_conn_new_with_tls((const uint8_t *) qs->scid,
-                                      sizeof(qs->scid), NULL, 0,
-                                      (struct sockaddr *)&qs->local_addr,
-                                      qs->local_addrlen, addr, addrlen,
-                                      qs->cfg, qs->ssl, false);
-  if(!qs->conn) {
-    failf(data, "can't create quiche connection");
-    return CURLE_OUT_OF_MEMORY;
-  }
-
-  /* Known to not work on Windows */
-#if !defined(WIN32) && defined(HAVE_QUICHE_CONN_SET_QLOG_FD)
-  {
-    int qfd;
-    (void)Curl_qlogdir(data, qs->scid, sizeof(qs->scid), &qfd);
-    if(qfd != -1)
-      quiche_conn_set_qlog_fd(qs->conn, qfd,
-                              "qlog title", "curl qlog");
-  }
-#endif
-
-  result = flush_egress(data, sockfd, qs);
-  if(result)
-    return result;
-
-  /* extract the used address as a string */
-  if(!Curl_addr2string((struct sockaddr*)addr, addrlen, ipbuf, &port)) {
-    char buffer[STRERROR_LEN];
-    failf(data, "ssrem inet_ntop() failed with errno %d: %s",
-          SOCKERRNO, Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
-    return CURLE_BAD_FUNCTION_ARGUMENT;
-  }
-
-  infof(data, "Connect socket %d over QUIC to %s:%ld",
-        sockfd, ipbuf, port);
-
-  Curl_persistconninfo(data, conn, NULL, -1);
-
-  {
-    unsigned char alpn_protocols[] = QUICHE_H3_APPLICATION_PROTOCOL;
-    unsigned alpn_len, offset = 0;
-
-    /* Replace each ALPN length prefix by a comma. */
-    while(offset < sizeof(alpn_protocols) - 1) {
-      alpn_len = alpn_protocols[offset];
-      alpn_protocols[offset] = ',';
-      offset += 1 + alpn_len;
-    }
-
-    infof(data, "Sent QUIC client Initial, ALPN: %s",
-          alpn_protocols + 1);
-  }
-
-  return CURLE_OK;
-}
-
-static CURLcode quiche_has_connected(struct Curl_easy *data,
-                                     struct connectdata *conn,
-                                     int sockindex,
-                                     int tempindex)
-{
-  CURLcode result;
-  struct quicsocket *qs = conn->quic = &conn->hequic[tempindex];
-
-  conn->recv[sockindex] = h3_stream_recv;
-  conn->send[sockindex] = h3_stream_send;
-  conn->handler = &Curl_handler_http3;
-  conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
-  conn->httpversion = 30;
-  conn->bundle->multiuse = BUNDLE_MULTIPLEX;
-
-  if(conn->ssl_config.verifyhost) {
-    X509 *server_cert;
-    server_cert = SSL_get_peer_certificate(qs->ssl);
-    if(!server_cert) {
-      return CURLE_PEER_FAILED_VERIFICATION;
-    }
-    result = Curl_ossl_verifyhost(data, conn, server_cert);
-    X509_free(server_cert);
-    if(result)
-      return result;
-    infof(data, "Verified certificate just fine");
-  }
-  else
-    infof(data, "Skipped certificate verification");
-
-  qs->h3config = quiche_h3_config_new();
-  if(!qs->h3config)
-    return CURLE_OUT_OF_MEMORY;
-
-  /* Create a new HTTP/3 connection on the QUIC connection. */
-  qs->h3c = quiche_h3_conn_new_with_transport(qs->conn, qs->h3config);
-  if(!qs->h3c) {
-    result = CURLE_OUT_OF_MEMORY;
-    goto fail;
-  }
-  if(conn->hequic[1-tempindex].cfg) {
-    qs = &conn->hequic[1-tempindex];
-    quiche_config_free(qs->cfg);
-    quiche_conn_free(qs->conn);
-    qs->cfg = NULL;
-    qs->conn = NULL;
-  }
-  if(data->set.ssl.certinfo)
-    /* asked to gather certificate info */
-    (void)Curl_ossl_certchain(data, qs->ssl);
-
-  return CURLE_OK;
-  fail:
-  quiche_h3_config_free(qs->h3config);
-  quiche_h3_conn_free(qs->h3c);
-  return result;
-}
-
-/*
- * This function gets polled to check if this QUIC connection has connected.
- */
-CURLcode Curl_quic_is_connected(struct Curl_easy *data,
-                                struct connectdata *conn,
-                                int sockindex,
-                                bool *done)
-{
-  CURLcode result;
-  struct quicsocket *qs = &conn->hequic[sockindex];
-  curl_socket_t sockfd = conn->tempsock[sockindex];
-
-  result = process_ingress(data, sockfd, qs);
-  if(result)
-    goto error;
-
-  result = flush_egress(data, sockfd, qs);
-  if(result)
-    goto error;
-
-  if(quiche_conn_is_established(qs->conn)) {
-    *done = TRUE;
-    result = quiche_has_connected(data, conn, 0, sockindex);
-    DEBUGF(infof(data, "quiche established connection"));
-  }
-
-  return result;
-  error:
-  qs_disconnect(data, qs);
-  return result;
-}
-
-static CURLcode process_ingress(struct Curl_easy *data, int sockfd,
-                                struct quicsocket *qs)
-{
-  ssize_t recvd;
-  uint8_t *buf = (uint8_t *)data->state.buffer;
-  size_t bufsize = data->set.buffer_size;
-  struct sockaddr_storage from;
-  socklen_t from_len;
-  quiche_recv_info recv_info;
-
-  DEBUGASSERT(qs->conn);
-
-  /* in case the timeout expired */
-  quiche_conn_on_timeout(qs->conn);
-
-  do {
-    from_len = sizeof(from);
-
-    recvd = recvfrom(sockfd, buf, bufsize, 0,
-                     (struct sockaddr *)&from, &from_len);
-
-    if((recvd < 0) && ((SOCKERRNO == EAGAIN) || (SOCKERRNO == EWOULDBLOCK)))
-      break;
-
-    if(recvd < 0) {
-      failf(data, "quiche: recvfrom() unexpectedly returned %zd "
-            "(errno: %d, socket %d)", recvd, SOCKERRNO, sockfd);
-      return CURLE_RECV_ERROR;
-    }
-
-    recv_info.from = (struct sockaddr *) &from;
-    recv_info.from_len = from_len;
-    recv_info.to = (struct sockaddr *) &qs->local_addr;
-    recv_info.to_len = qs->local_addrlen;
-
-    recvd = quiche_conn_recv(qs->conn, buf, recvd, &recv_info);
-    if(recvd == QUICHE_ERR_DONE)
-      break;
-
-    if(recvd < 0) {
-      if(QUICHE_ERR_TLS_FAIL == recvd) {
-        long verify_ok = SSL_get_verify_result(qs->ssl);
-        if(verify_ok != X509_V_OK) {
-          failf(data, "SSL certificate problem: %s",
-                X509_verify_cert_error_string(verify_ok));
-
-          return CURLE_PEER_FAILED_VERIFICATION;
-        }
-      }
-
-      failf(data, "quiche_conn_recv() == %zd", recvd);
-
-      return CURLE_RECV_ERROR;
-    }
-  } while(1);
-
-  return CURLE_OK;
-}
-
-/*
- * flush_egress drains the buffers and sends off data.
- * Calls failf() on errors.
- */
-static CURLcode flush_egress(struct Curl_easy *data, int sockfd,
-                             struct quicsocket *qs)
-{
-  ssize_t sent;
-  uint8_t out[1200];
-  int64_t timeout_ns;
-  quiche_send_info send_info;
-
-  do {
-    sent = quiche_conn_send(qs->conn, out, sizeof(out), &send_info);
-    if(sent == QUICHE_ERR_DONE)
-      break;
-
-    if(sent < 0) {
-      failf(data, "quiche_conn_send returned %zd", sent);
-      return CURLE_SEND_ERROR;
-    }
-
-    sent = send(sockfd, out, sent, 0);
-    if(sent < 0) {
-      failf(data, "send() returned %zd", sent);
-      return CURLE_SEND_ERROR;
-    }
-  } while(1);
-
-  /* time until the next timeout event, as nanoseconds. */
-  timeout_ns = quiche_conn_timeout_as_nanos(qs->conn);
-  if(timeout_ns)
-    /* expire uses milliseconds */
-    Curl_expire(data, (timeout_ns + 999999) / 1000000, EXPIRE_QUIC);
-
-  return CURLE_OK;
-}
-
-struct h3h1header {
-  char *dest;
-  size_t destlen; /* left to use */
-  size_t nlen; /* used */
-};
-
-static int cb_each_header(uint8_t *name, size_t name_len,
-                          uint8_t *value, size_t value_len,
-                          void *argp)
-{
-  struct h3h1header *headers = (struct h3h1header *)argp;
-  size_t olen = 0;
-
-  if((name_len == 7) && !strncmp(H2H3_PSEUDO_STATUS, (char *)name, 7)) {
-    msnprintf(headers->dest,
-              headers->destlen, "HTTP/3 %.*s\n",
-              (int) value_len, value);
-  }
-  else if(!headers->nlen) {
-    return CURLE_HTTP3;
-  }
-  else {
-    msnprintf(headers->dest,
-              headers->destlen, "%.*s: %.*s\n",
-              (int)name_len, name, (int) value_len, value);
-  }
-  olen = strlen(headers->dest);
-  headers->destlen -= olen;
-  headers->nlen += olen;
-  headers->dest += olen;
-  return 0;
-}
-
-static ssize_t h3_stream_recv(struct Curl_easy *data,
-                              int sockindex,
-                              char *buf,
-                              size_t buffersize,
-                              CURLcode *curlcode)
-{
-  ssize_t recvd = -1;
-  ssize_t rcode;
-  struct connectdata *conn = data->conn;
-  struct quicsocket *qs = conn->quic;
-  curl_socket_t sockfd = conn->sock[sockindex];
-  quiche_h3_event *ev;
-  int rc;
-  struct h3h1header headers;
-  struct HTTP *stream = data->req.p.http;
-  headers.dest = buf;
-  headers.destlen = buffersize;
-  headers.nlen = 0;
-
-  if(process_ingress(data, sockfd, qs)) {
-    infof(data, "h3_stream_recv returns on ingress");
-    *curlcode = CURLE_RECV_ERROR;
-    return -1;
-  }
-
-  if(qs->h3_recving) {
-    /* body receiving state */
-    rcode = quiche_h3_recv_body(qs->h3c, qs->conn, stream->stream3_id,
-                                (unsigned char *)buf, buffersize);
-    if(rcode <= 0) {
-      recvd = -1;
-      qs->h3_recving = FALSE;
-      /* fall through into the while loop below */
-    }
-    else
-      recvd = rcode;
-  }
-
-  while(recvd < 0) {
-    int64_t s = quiche_h3_conn_poll(qs->h3c, qs->conn, &ev);
-    if(s < 0)
-      /* nothing more to do */
-      break;
-
-    if(s != stream->stream3_id) {
-      /* another transfer, ignore for now */
-      infof(data, "Got h3 for stream %u, expects %u",
-            s, stream->stream3_id);
-      continue;
-    }
-
-    switch(quiche_h3_event_type(ev)) {
-    case QUICHE_H3_EVENT_HEADERS:
-      rc = quiche_h3_event_for_each_header(ev, cb_each_header, &headers);
-      if(rc) {
-        *curlcode = rc;
-        failf(data, "Error in HTTP/3 response header");
-        break;
-      }
-      recvd = headers.nlen;
-      break;
-    case QUICHE_H3_EVENT_DATA:
-      if(!stream->firstbody) {
-        /* add a header-body separator CRLF */
-        buf[0] = '\r';
-        buf[1] = '\n';
-        buf += 2;
-        buffersize -= 2;
-        stream->firstbody = TRUE;
-        recvd = 2; /* two bytes already */
-      }
-      else
-        recvd = 0;
-      rcode = quiche_h3_recv_body(qs->h3c, qs->conn, s, (unsigned char *)buf,
-                                  buffersize);
-      if(rcode <= 0) {
-        recvd = -1;
-        break;
-      }
-      qs->h3_recving = TRUE;
-      recvd += rcode;
-      break;
-
-    case QUICHE_H3_EVENT_RESET:
-      streamclose(conn, "Stream reset");
-      *curlcode = CURLE_PARTIAL_FILE;
-      return -1;
-
-    case QUICHE_H3_EVENT_FINISHED:
-      streamclose(conn, "End of stream");
-      recvd = 0; /* end of stream */
-      break;
-    default:
-      break;
-    }
-
-    quiche_h3_event_free(ev);
-  }
-  if(flush_egress(data, sockfd, qs)) {
-    *curlcode = CURLE_SEND_ERROR;
-    return -1;
-  }
-
-  *curlcode = (-1 == recvd)? CURLE_AGAIN : CURLE_OK;
-  if(recvd >= 0)
-    /* Get this called again to drain the event queue */
-    Curl_expire(data, 0, EXPIRE_QUIC);
-
-  data->state.drain = (recvd >= 0) ? 1 : 0;
-  return recvd;
-}
-
-static ssize_t h3_stream_send(struct Curl_easy *data,
-                              int sockindex,
-                              const void *mem,
-                              size_t len,
-                              CURLcode *curlcode)
-{
-  ssize_t sent;
-  struct connectdata *conn = data->conn;
-  struct quicsocket *qs = conn->quic;
-  curl_socket_t sockfd = conn->sock[sockindex];
-  struct HTTP *stream = data->req.p.http;
-
-  if(!stream->h3req) {
-    CURLcode result = http_request(data, mem, len);
-    if(result) {
-      *curlcode = CURLE_SEND_ERROR;
-      return -1;
-    }
-    sent = len;
-  }
-  else {
-    sent = quiche_h3_send_body(qs->h3c, qs->conn, stream->stream3_id,
-                               (uint8_t *)mem, len, FALSE);
-    if(sent == QUICHE_H3_ERR_DONE) {
-      sent = 0;
-    }
-    else if(sent < 0) {
-      *curlcode = CURLE_SEND_ERROR;
-      return -1;
-    }
-  }
-
-  if(flush_egress(data, sockfd, qs)) {
-    *curlcode = CURLE_SEND_ERROR;
-    return -1;
-  }
-
-  *curlcode = CURLE_OK;
-  return sent;
-}
-
-/*
- * Store quiche version info in this buffer.
- */
-void Curl_quic_ver(char *p, size_t len)
-{
-  (void)msnprintf(p, len, "quiche/%s", quiche_version());
-}
-
-/* Index where :authority header field will appear in request header
-   field list. */
-#define AUTHORITY_DST_IDX 3
-
-static CURLcode http_request(struct Curl_easy *data, const void *mem,
-                             size_t len)
-{
-  struct connectdata *conn = data->conn;
-  struct HTTP *stream = data->req.p.http;
-  size_t nheader;
-  int64_t stream3_id;
-  quiche_h3_header *nva = NULL;
-  struct quicsocket *qs = conn->quic;
-  CURLcode result = CURLE_OK;
-  struct h2h3req *hreq = NULL;
-
-  stream->h3req = TRUE; /* senf off! */
-
-  result = Curl_pseudo_headers(data, mem, len, &hreq);
-  if(result)
-    goto fail;
-  nheader = hreq->entries;
-
-  nva = malloc(sizeof(quiche_h3_header) * nheader);
-  if(!nva) {
-    result = CURLE_OUT_OF_MEMORY;
-    goto fail;
-  }
-  else {
-    unsigned int i;
-    for(i = 0; i < nheader; i++) {
-      nva[i].name = (unsigned char *)hreq->header[i].name;
-      nva[i].name_len = hreq->header[i].namelen;
-      nva[i].value = (unsigned char *)hreq->header[i].value;
-      nva[i].value_len = hreq->header[i].valuelen;
-    }
-  }
-
-  switch(data->state.httpreq) {
-  case HTTPREQ_POST:
-  case HTTPREQ_POST_FORM:
-  case HTTPREQ_POST_MIME:
-  case HTTPREQ_PUT:
-    if(data->state.infilesize != -1)
-      stream->upload_left = data->state.infilesize;
-    else
-      /* data sending without specifying the data amount up front */
-      stream->upload_left = -1; /* unknown, but not zero */
-
-    stream3_id = quiche_h3_send_request(qs->h3c, qs->conn, nva, nheader,
-                                        stream->upload_left ? FALSE: TRUE);
-    if((stream3_id >= 0) && data->set.postfields) {
-      ssize_t sent = quiche_h3_send_body(qs->h3c, qs->conn, stream3_id,
-                                         (uint8_t *)data->set.postfields,
-                                         stream->upload_left, TRUE);
-      if(sent <= 0) {
-        failf(data, "quiche_h3_send_body failed");
-        result = CURLE_SEND_ERROR;
-      }
-      stream->upload_left = 0; /* nothing left to send */
-    }
-    break;
-  default:
-    stream3_id = quiche_h3_send_request(qs->h3c, qs->conn, nva, nheader,
-                                        TRUE);
-    break;
-  }
-
-  Curl_safefree(nva);
-
-  if(stream3_id < 0) {
-    H3BUGF(infof(data, "quiche_h3_send_request returned %d",
-                 stream3_id));
-    result = CURLE_SEND_ERROR;
-    goto fail;
-  }
-
-  infof(data, "Using HTTP/3 Stream ID: %x (easy handle %p)",
-        stream3_id, (void *)data);
-  stream->stream3_id = stream3_id;
-
-  Curl_pseudo_free(hreq);
-  return CURLE_OK;
-
-fail:
-  free(nva);
-  Curl_pseudo_free(hreq);
-  return result;
-}
-
-/*
- * Called from transfer.c:done_sending when we stop HTTP/3 uploading.
- */
-CURLcode Curl_quic_done_sending(struct Curl_easy *data)
-{
-  struct connectdata *conn = data->conn;
-  DEBUGASSERT(conn);
-  if(conn->handler == &Curl_handler_http3) {
-    /* only for HTTP/3 transfers */
-    ssize_t sent;
-    struct HTTP *stream = data->req.p.http;
-    struct quicsocket *qs = conn->quic;
-    stream->upload_done = TRUE;
-    sent = quiche_h3_send_body(qs->h3c, qs->conn, stream->stream3_id,
-                               NULL, 0, TRUE);
-    if(sent < 0)
-      return CURLE_SEND_ERROR;
-  }
-
-  return CURLE_OK;
-}
-
-/*
- * Called from http.c:Curl_http_done when a request completes.
- */
-void Curl_quic_done(struct Curl_easy *data, bool premature)
-{
-  (void)data;
-  (void)premature;
-}
-
-/*
- * Called from transfer.c:data_pending to know if we should keep looping
- * to receive more data from the connection.
- */
-bool Curl_quic_data_pending(const struct Curl_easy *data)
-{
-  (void)data;
-  return FALSE;
-}
-
-/*
- * Called from transfer.c:Curl_readwrite when neither HTTP level read
- * nor write is performed. It is a good place to handle timer expiry
- * for QUIC transport.
- */
-CURLcode Curl_quic_idle(struct Curl_easy *data)
-{
-  (void)data;
-  return CURLE_OK;
-}
-
-#endif
diff --git a/Utilities/cmcurl/lib/vquic/quiche.h b/Utilities/cmcurl/lib/vquic/quiche.h
deleted file mode 100644
index 2da65f5..0000000
--- a/Utilities/cmcurl/lib/vquic/quiche.h
+++ /dev/null
@@ -1,58 +0,0 @@
-#ifndef HEADER_CURL_VQUIC_QUICHE_H
-#define HEADER_CURL_VQUIC_QUICHE_H
-/***************************************************************************
- *                                  _   _ ____  _
- *  Project                     ___| | | |  _ \| |
- *                             / __| | | | |_) | |
- *                            | (__| |_| |  _ <| |___
- *                             \___|\___/|_| \_\_____|
- *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- *
- * This software is licensed as described in the file COPYING, which
- * you should have received as part of this distribution. The terms
- * are also available at https://curl.se/docs/copyright.html.
- *
- * You may opt to use, copy, modify, merge, publish, distribute and/or sell
- * copies of the Software, and permit persons to whom the Software is
- * furnished to do so, under the terms of the COPYING file.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- * SPDX-License-Identifier: curl
- *
- ***************************************************************************/
-
-#include "curl_setup.h"
-
-#ifdef USE_QUICHE
-
-#include <quiche.h>
-#include <openssl/ssl.h>
-
-struct quic_handshake {
-  char *buf;       /* pointer to the buffer */
-  size_t alloclen; /* size of allocation */
-  size_t len;      /* size of content in buffer */
-  size_t nread;    /* how many bytes have been read */
-};
-
-struct quicsocket {
-  quiche_config *cfg;
-  quiche_conn *conn;
-  quiche_h3_conn *h3c;
-  quiche_h3_config *h3config;
-  uint8_t scid[QUICHE_MAX_CONN_ID_LEN];
-  curl_socket_t sockfd;
-  uint32_t version;
-  SSL_CTX *sslctx;
-  SSL *ssl;
-  bool h3_recving; /* TRUE when in h3-body-reading state */
-  struct sockaddr_storage local_addr;
-  socklen_t local_addrlen;
-};
-
-#endif
-
-#endif /* HEADER_CURL_VQUIC_QUICHE_H */
diff --git a/Utilities/cmcurl/lib/vquic/vquic.c b/Utilities/cmcurl/lib/vquic/vquic.c
index e52a4f3..bbdeabd 100644
--- a/Utilities/cmcurl/lib/vquic/vquic.c
+++ b/Utilities/cmcurl/lib/vquic/vquic.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -24,15 +24,26 @@
 
 #include "curl_setup.h"
 
-#ifdef ENABLE_QUIC
-
 #ifdef HAVE_FCNTL_H
 #include <fcntl.h>
 #endif
 #include "urldata.h"
 #include "dynbuf.h"
-#include "curl_printf.h"
+#include "cfilters.h"
+#include "curl_log.h"
+#include "curl_msh3.h"
+#include "curl_ngtcp2.h"
+#include "curl_quiche.h"
 #include "vquic.h"
+#include "vquic_int.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+
+#ifdef ENABLE_QUIC
 
 #ifdef O_BINARY
 #define QLOGMODE O_WRONLY|O_CREAT|O_BINARY
@@ -40,6 +51,232 @@
 #define QLOGMODE O_WRONLY|O_CREAT
 #endif
 
+void Curl_quic_ver(char *p, size_t len)
+{
+#ifdef USE_NGTCP2
+  Curl_ngtcp2_ver(p, len);
+#elif defined(USE_QUICHE)
+  Curl_quiche_ver(p, len);
+#elif defined(USE_MSH3)
+  Curl_msh3_ver(p, len);
+#endif
+}
+
+CURLcode vquic_ctx_init(struct cf_quic_ctx *qctx, size_t pktbuflen)
+{
+  qctx->num_blocked_pkt = 0;
+  qctx->num_blocked_pkt_sent = 0;
+  memset(&qctx->blocked_pkt, 0, sizeof(qctx->blocked_pkt));
+
+  qctx->pktbuflen = pktbuflen;
+  qctx->pktbuf = malloc(qctx->pktbuflen);
+  if(!qctx->pktbuf)
+    return CURLE_OUT_OF_MEMORY;
+
+#if defined(__linux__) && defined(UDP_SEGMENT) && defined(HAVE_SENDMSG)
+  qctx->no_gso = FALSE;
+#else
+  qctx->no_gso = TRUE;
+#endif
+
+  return CURLE_OK;
+}
+
+void vquic_ctx_free(struct cf_quic_ctx *qctx)
+{
+  free(qctx->pktbuf);
+  qctx->pktbuf = NULL;
+}
+
+static CURLcode send_packet_no_gso(struct Curl_cfilter *cf,
+                                   struct Curl_easy *data,
+                                   struct cf_quic_ctx *qctx,
+                                   const uint8_t *pkt, size_t pktlen,
+                                   size_t gsolen, size_t *psent);
+
+static CURLcode do_sendmsg(struct Curl_cfilter *cf,
+                           struct Curl_easy *data,
+                           struct cf_quic_ctx *qctx,
+                           const uint8_t *pkt, size_t pktlen, size_t gsolen,
+                           size_t *psent)
+{
+#ifdef HAVE_SENDMSG
+  struct iovec msg_iov;
+  struct msghdr msg = {0};
+  ssize_t sent;
+#if defined(__linux__) && defined(UDP_SEGMENT)
+  uint8_t msg_ctrl[32];
+  struct cmsghdr *cm;
+#endif
+
+  *psent = 0;
+  msg_iov.iov_base = (uint8_t *)pkt;
+  msg_iov.iov_len = pktlen;
+  msg.msg_iov = &msg_iov;
+  msg.msg_iovlen = 1;
+
+#if defined(__linux__) && defined(UDP_SEGMENT)
+  if(pktlen > gsolen) {
+    /* Only set this, when we need it. macOS, for example,
+     * does not seem to like a msg_control of length 0. */
+    msg.msg_control = msg_ctrl;
+    assert(sizeof(msg_ctrl) >= CMSG_SPACE(sizeof(uint16_t)));
+    msg.msg_controllen = CMSG_SPACE(sizeof(uint16_t));
+    cm = CMSG_FIRSTHDR(&msg);
+    cm->cmsg_level = SOL_UDP;
+    cm->cmsg_type = UDP_SEGMENT;
+    cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
+    *(uint16_t *)(void *)CMSG_DATA(cm) = gsolen & 0xffff;
+  }
+#endif
+
+
+  while((sent = sendmsg(qctx->sockfd, &msg, 0)) == -1 && SOCKERRNO == EINTR)
+    ;
+
+  if(sent == -1) {
+    switch(SOCKERRNO) {
+    case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+    case EWOULDBLOCK:
+#endif
+      return CURLE_AGAIN;
+    case EMSGSIZE:
+      /* UDP datagram is too large; caused by PMTUD. Just let it be lost. */
+      break;
+    case EIO:
+      if(pktlen > gsolen) {
+        /* GSO failure */
+        failf(data, "sendmsg() returned %zd (errno %d); disable GSO", sent,
+              SOCKERRNO);
+        qctx->no_gso = TRUE;
+        return send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent);
+      }
+      /* FALLTHROUGH */
+    default:
+      failf(data, "sendmsg() returned %zd (errno %d)", sent, SOCKERRNO);
+      return CURLE_SEND_ERROR;
+    }
+  }
+  else {
+    assert(pktlen == (size_t)sent);
+  }
+#else
+  ssize_t sent;
+  (void)gsolen;
+
+  *psent = 0;
+
+  while((sent = send(qctx->sockfd,
+                     (const char *)pkt, (SEND_TYPE_ARG3)pktlen, 0)) == -1 &&
+        SOCKERRNO == EINTR)
+    ;
+
+  if(sent == -1) {
+    if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) {
+      return CURLE_AGAIN;
+    }
+    else {
+      failf(data, "send() returned %zd (errno %d)", sent, SOCKERRNO);
+      if(SOCKERRNO != EMSGSIZE) {
+        return CURLE_SEND_ERROR;
+      }
+      /* UDP datagram is too large; caused by PMTUD. Just let it be
+         lost. */
+    }
+  }
+#endif
+  (void)cf;
+  *psent = pktlen;
+
+  return CURLE_OK;
+}
+
+static CURLcode send_packet_no_gso(struct Curl_cfilter *cf,
+                                   struct Curl_easy *data,
+                                   struct cf_quic_ctx *qctx,
+                                   const uint8_t *pkt, size_t pktlen,
+                                   size_t gsolen, size_t *psent)
+{
+  const uint8_t *p, *end = pkt + pktlen;
+  size_t sent;
+
+  *psent = 0;
+
+  for(p = pkt; p < end; p += gsolen) {
+    size_t len = CURLMIN(gsolen, (size_t)(end - p));
+    CURLcode curlcode = do_sendmsg(cf, data, qctx, p, len, len, &sent);
+    if(curlcode != CURLE_OK) {
+      return curlcode;
+    }
+    *psent += sent;
+  }
+
+  return CURLE_OK;
+}
+
+CURLcode vquic_send_packet(struct Curl_cfilter *cf,
+                           struct Curl_easy *data,
+                           struct cf_quic_ctx *qctx,
+                           const uint8_t *pkt, size_t pktlen, size_t gsolen,
+                           size_t *psent)
+{
+  if(qctx->no_gso && pktlen > gsolen) {
+    return send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent);
+  }
+
+  return do_sendmsg(cf, data, qctx, pkt, pktlen, gsolen, psent);
+}
+
+
+
+void vquic_push_blocked_pkt(struct Curl_cfilter *cf,
+                            struct cf_quic_ctx *qctx,
+                            const uint8_t *pkt, size_t pktlen, size_t gsolen)
+{
+  struct vquic_blocked_pkt *blkpkt;
+
+  (void)cf;
+  assert(qctx->num_blocked_pkt <
+         sizeof(qctx->blocked_pkt) / sizeof(qctx->blocked_pkt[0]));
+
+  blkpkt = &qctx->blocked_pkt[qctx->num_blocked_pkt++];
+
+  blkpkt->pkt = pkt;
+  blkpkt->pktlen = pktlen;
+  blkpkt->gsolen = gsolen;
+}
+
+CURLcode vquic_send_blocked_pkt(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                struct cf_quic_ctx *qctx)
+{
+  size_t sent;
+  CURLcode curlcode;
+  struct vquic_blocked_pkt *blkpkt;
+
+  (void)cf;
+  for(; qctx->num_blocked_pkt_sent < qctx->num_blocked_pkt;
+      ++qctx->num_blocked_pkt_sent) {
+    blkpkt = &qctx->blocked_pkt[qctx->num_blocked_pkt_sent];
+    curlcode = vquic_send_packet(cf, data, qctx, blkpkt->pkt,
+                                 blkpkt->pktlen, blkpkt->gsolen, &sent);
+
+    if(curlcode) {
+      if(curlcode == CURLE_AGAIN) {
+        blkpkt->pkt += sent;
+        blkpkt->pktlen -= sent;
+      }
+      return curlcode;
+    }
+  }
+
+  qctx->num_blocked_pkt = 0;
+  qctx->num_blocked_pkt_sent = 0;
+
+  return CURLE_OK;
+}
+
 /*
  * If the QLOGDIR environment variable is set, open and return a file
  * descriptor to write the log to.
@@ -84,4 +321,80 @@
 
   return CURLE_OK;
 }
+
+CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf,
+                             struct Curl_easy *data,
+                             struct connectdata *conn,
+                             const struct Curl_addrinfo *ai,
+                             int transport)
+{
+  (void)transport;
+  DEBUGASSERT(transport == TRNSPRT_QUIC);
+#ifdef USE_NGTCP2
+  return Curl_cf_ngtcp2_create(pcf, data, conn, ai);
+#elif defined(USE_QUICHE)
+  return Curl_cf_quiche_create(pcf, data, conn, ai);
+#elif defined(USE_MSH3)
+  return Curl_cf_msh3_create(pcf, data, conn, ai);
+#else
+  *pcf = NULL;
+  (void)data;
+  (void)conn;
+  (void)ai;
+  return CURLE_NOT_BUILT_IN;
 #endif
+}
+
+bool Curl_conn_is_http3(const struct Curl_easy *data,
+                        const struct connectdata *conn,
+                        int sockindex)
+{
+#ifdef USE_NGTCP2
+  return Curl_conn_is_ngtcp2(data, conn, sockindex);
+#elif defined(USE_QUICHE)
+  return Curl_conn_is_quiche(data, conn, sockindex);
+#elif defined(USE_MSH3)
+  return Curl_conn_is_msh3(data, conn, sockindex);
+#else
+  return ((conn->handler->protocol & PROTO_FAMILY_HTTP) &&
+          (conn->httpversion == 30));
+#endif
+}
+
+CURLcode Curl_conn_may_http3(struct Curl_easy *data,
+                             const struct connectdata *conn)
+{
+  if(conn->transport == TRNSPRT_UNIX) {
+    /* cannot do QUIC over a unix domain socket */
+    return CURLE_QUIC_CONNECT_ERROR;
+  }
+  if(!(conn->handler->flags & PROTOPT_SSL)) {
+    failf(data, "HTTP/3 requested for non-HTTPS URL");
+    return CURLE_URL_MALFORMAT;
+  }
+#ifndef CURL_DISABLE_PROXY
+  if(conn->bits.socksproxy) {
+    failf(data, "HTTP/3 is not supported over a SOCKS proxy");
+    return CURLE_URL_MALFORMAT;
+  }
+  if(conn->bits.httpproxy && conn->bits.tunnel_proxy) {
+    failf(data, "HTTP/3 is not supported over a HTTP proxy");
+    return CURLE_URL_MALFORMAT;
+  }
+#endif
+
+  return CURLE_OK;
+}
+
+#else /* ENABLE_QUIC */
+
+CURLcode Curl_conn_may_http3(struct Curl_easy *data,
+                             const struct connectdata *conn)
+{
+  (void)conn;
+  (void)data;
+  DEBUGF(infof(data, "QUIC is not supported in this build"));
+  return CURLE_NOT_BUILT_IN;
+}
+
+#endif /* !ENABLE_QUIC */
diff --git a/Utilities/cmcurl/lib/vquic/vquic.h b/Utilities/cmcurl/lib/vquic/vquic.h
index 8f599a8..dc73957 100644
--- a/Utilities/cmcurl/lib/vquic/vquic.h
+++ b/Utilities/cmcurl/lib/vquic/vquic.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -27,10 +27,38 @@
 #include "curl_setup.h"
 
 #ifdef ENABLE_QUIC
+struct Curl_cfilter;
+struct Curl_easy;
+struct connectdata;
+struct Curl_addrinfo;
+
+void Curl_quic_ver(char *p, size_t len);
+
 CURLcode Curl_qlogdir(struct Curl_easy *data,
                       unsigned char *scid,
                       size_t scidlen,
                       int *qlogfdp);
-#endif
+
+
+CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf,
+                             struct Curl_easy *data,
+                             struct connectdata *conn,
+                             const struct Curl_addrinfo *ai,
+                             int transport);
+
+bool Curl_conn_is_http3(const struct Curl_easy *data,
+                        const struct connectdata *conn,
+                        int sockindex);
+
+extern struct Curl_cftype Curl_cft_http3;
+
+#else /* ENABLE_QUIC */
+
+#define Curl_conn_is_http3(a,b,c)   FALSE
+
+#endif /* !ENABLE_QUIC */
+
+CURLcode Curl_conn_may_http3(struct Curl_easy *data,
+                             const struct connectdata *conn);
 
 #endif /* HEADER_CURL_VQUIC_QUIC_H */
diff --git a/Utilities/cmcurl/lib/vquic/vquic_int.h b/Utilities/cmcurl/lib/vquic/vquic_int.h
new file mode 100644
index 0000000..42aba39
--- /dev/null
+++ b/Utilities/cmcurl/lib/vquic/vquic_int.h
@@ -0,0 +1,72 @@
+#ifndef HEADER_CURL_VQUIC_QUIC_INT_H
+#define HEADER_CURL_VQUIC_QUIC_INT_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef ENABLE_QUIC
+
+struct vquic_blocked_pkt {
+  const uint8_t *pkt;
+  size_t pktlen;
+  size_t gsolen;
+};
+
+struct cf_quic_ctx {
+  curl_socket_t sockfd;
+  struct sockaddr_storage local_addr;
+  socklen_t local_addrlen;
+  struct vquic_blocked_pkt blocked_pkt[2];
+  uint8_t *pktbuf;
+  /* the number of entries in blocked_pkt */
+  size_t num_blocked_pkt;
+  size_t num_blocked_pkt_sent;
+  /* the packets blocked by sendmsg (EAGAIN or EWOULDBLOCK) */
+  size_t pktbuflen;
+  /* the number of processed entries in blocked_pkt */
+  bool no_gso;
+};
+
+CURLcode vquic_ctx_init(struct cf_quic_ctx *qctx, size_t pktbuflen);
+void vquic_ctx_free(struct cf_quic_ctx *qctx);
+
+CURLcode vquic_send_packet(struct Curl_cfilter *cf,
+                           struct Curl_easy *data,
+                           struct cf_quic_ctx *qctx,
+                           const uint8_t *pkt, size_t pktlen, size_t gsolen,
+                           size_t *psent);
+
+void vquic_push_blocked_pkt(struct Curl_cfilter *cf,
+                            struct cf_quic_ctx *qctx,
+                            const uint8_t *pkt, size_t pktlen, size_t gsolen);
+
+CURLcode vquic_send_blocked_pkt(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                struct cf_quic_ctx *qctx);
+
+
+#endif /* !ENABLE_QUIC */
+
+#endif /* HEADER_CURL_VQUIC_QUIC_INT_H */
diff --git a/Utilities/cmcurl/lib/vssh/libssh.c b/Utilities/cmcurl/lib/vssh/libssh.c
index d9fa58a..b31f741 100644
--- a/Utilities/cmcurl/lib/vssh/libssh.c
+++ b/Utilities/cmcurl/lib/vssh/libssh.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2017 - 2022 Red Hat, Inc.
+ * Copyright (C) Red Hat, Inc.
  *
  * Authors: Nikos Mavrogiannopoulos, Tomas Mraz, Stanislav Zidek,
  *          Robert Kolcun, Andreas Schneider
@@ -685,7 +685,6 @@
   struct ssh_conn *sshc = &conn->proto.sshc;
   curl_socket_t sock = conn->sock[FIRSTSOCKET];
   int rc = SSH_NO_ERROR, err;
-  char *new_readdir_line;
   int seekerr = CURL_SEEKFUNC_OK;
   const char *err_msg;
   *block = 0;                   /* we're not blocking by default */
@@ -1432,7 +1431,7 @@
       break;
 
     case SSH_SFTP_READDIR:
-
+      Curl_dyn_reset(&sshc->readdir_buf);
       if(sshc->readdir_attrs)
         sftp_attributes_free(sshc->readdir_attrs);
 
@@ -1468,17 +1467,12 @@
                      sshc->readdir_len);
         }
         else {
-          sshc->readdir_currLen = strlen(sshc->readdir_longentry);
-          sshc->readdir_totalLen = 80 + sshc->readdir_currLen;
-          sshc->readdir_line = calloc(sshc->readdir_totalLen, 1);
-          if(!sshc->readdir_line) {
-            state(data, SSH_SFTP_CLOSE);
+          if(Curl_dyn_add(&sshc->readdir_buf, sshc->readdir_longentry)) {
             sshc->actualcode = CURLE_OUT_OF_MEMORY;
+            state(data, SSH_STOP);
             break;
           }
 
-          memcpy(sshc->readdir_line, sshc->readdir_longentry,
-                 sshc->readdir_currLen);
           if((sshc->readdir_attrs->flags & SSH_FILEXFER_ATTR_PERMISSIONS) &&
              ((sshc->readdir_attrs->permissions & SSH_S_IFMT) ==
               SSH_S_IFLNK)) {
@@ -1541,24 +1535,11 @@
 
       Curl_safefree(sshc->readdir_linkPath);
 
-      /* get room for the filename and extra output */
-      sshc->readdir_totalLen += 4 + sshc->readdir_len;
-      new_readdir_line = Curl_saferealloc(sshc->readdir_line,
-                                          sshc->readdir_totalLen);
-      if(!new_readdir_line) {
-        sshc->readdir_line = NULL;
-        state(data, SSH_SFTP_CLOSE);
+      if(Curl_dyn_addf(&sshc->readdir_buf, " -> %s",
+                       sshc->readdir_filename)) {
         sshc->actualcode = CURLE_OUT_OF_MEMORY;
         break;
       }
-      sshc->readdir_line = new_readdir_line;
-
-      sshc->readdir_currLen += msnprintf(sshc->readdir_line +
-                                         sshc->readdir_currLen,
-                                         sshc->readdir_totalLen -
-                                         sshc->readdir_currLen,
-                                         " -> %s",
-                                         sshc->readdir_filename);
 
       sftp_attributes_free(sshc->readdir_link_attrs);
       sshc->readdir_link_attrs = NULL;
@@ -1568,21 +1549,19 @@
       state(data, SSH_SFTP_READDIR_BOTTOM);
       /* FALLTHROUGH */
     case SSH_SFTP_READDIR_BOTTOM:
-      sshc->readdir_currLen += msnprintf(sshc->readdir_line +
-                                         sshc->readdir_currLen,
-                                         sshc->readdir_totalLen -
-                                         sshc->readdir_currLen, "\n");
-      result = Curl_client_write(data, CLIENTWRITE_BODY,
-                                 sshc->readdir_line,
-                                 sshc->readdir_currLen);
+      if(Curl_dyn_addn(&sshc->readdir_buf, "\n", 1))
+        result = CURLE_OUT_OF_MEMORY;
+      else
+        result = Curl_client_write(data, CLIENTWRITE_BODY,
+                                   Curl_dyn_ptr(&sshc->readdir_buf),
+                                   Curl_dyn_len(&sshc->readdir_buf));
 
       if(!result) {
         /* output debug output if that is requested */
-        Curl_debug(data, CURLINFO_DATA_OUT, sshc->readdir_line,
-                   sshc->readdir_currLen);
-        data->req.bytecount += sshc->readdir_currLen;
+        Curl_debug(data, CURLINFO_DATA_OUT, Curl_dyn_ptr(&sshc->readdir_buf),
+                   Curl_dyn_len(&sshc->readdir_buf));
+        data->req.bytecount += Curl_dyn_len(&sshc->readdir_buf);
       }
-      Curl_safefree(sshc->readdir_line);
       ssh_string_free_char(sshc->readdir_tmp);
       sshc->readdir_tmp = NULL;
 
@@ -2021,7 +2000,7 @@
       Curl_safefree(sshc->rsa);
       Curl_safefree(sshc->quote_path1);
       Curl_safefree(sshc->quote_path2);
-      Curl_safefree(sshc->readdir_line);
+      Curl_dyn_free(&sshc->readdir_buf);
       Curl_safefree(sshc->readdir_linkPath);
       SSH_STRING_FREE_CHAR(sshc->homedir);
 
@@ -2166,11 +2145,12 @@
                                        struct connectdata *conn)
 {
   struct SSHPROTO *ssh;
-  (void)conn;
+  struct ssh_conn *sshc = &conn->proto.sshc;
 
   data->req.p.ssh = ssh = calloc(1, sizeof(struct SSHPROTO));
   if(!ssh)
     return CURLE_OUT_OF_MEMORY;
+  Curl_dyn_init(&sshc->readdir_buf, PATH_MAX * 2);
 
   return CURLE_OK;
 }
diff --git a/Utilities/cmcurl/lib/vssh/libssh2.c b/Utilities/cmcurl/lib/vssh/libssh2.c
index ce9229f..f1154dc 100644
--- a/Utilities/cmcurl/lib/vssh/libssh2.c
+++ b/Utilities/cmcurl/lib/vssh/libssh2.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -100,10 +100,11 @@
 
 /* Local functions: */
 static const char *sftp_libssh2_strerror(unsigned long err);
+#ifdef CURL_LIBSSH2_DEBUG
 static LIBSSH2_ALLOC_FUNC(my_libssh2_malloc);
 static LIBSSH2_REALLOC_FUNC(my_libssh2_realloc);
 static LIBSSH2_FREE_FUNC(my_libssh2_free);
-
+#endif
 static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data);
 static CURLcode ssh_connect(struct Curl_easy *data, bool *done);
 static CURLcode ssh_multi_statemach(struct Curl_easy *data, bool *done);
@@ -144,7 +145,7 @@
   scp_disconnect,                       /* disconnect */
   ZERO_NULL,                            /* readwrite */
   ZERO_NULL,                            /* connection_check */
-  ssh_attach,
+  ssh_attach,                           /* attach */
   PORT_SSH,                             /* defport */
   CURLPROTO_SCP,                        /* protocol */
   CURLPROTO_SCP,                        /* family */
@@ -173,7 +174,7 @@
   sftp_disconnect,                      /* disconnect */
   ZERO_NULL,                            /* readwrite */
   ZERO_NULL,                            /* connection_check */
-  ssh_attach,
+  ssh_attach,                           /* attach */
   PORT_SSH,                             /* defport */
   CURLPROTO_SFTP,                       /* protocol */
   CURLPROTO_SFTP,                       /* family */
@@ -283,6 +284,8 @@
   return CURLE_SSH;
 }
 
+#ifdef CURL_LIBSSH2_DEBUG
+
 static LIBSSH2_ALLOC_FUNC(my_libssh2_malloc)
 {
   (void)abstract; /* arg not used */
@@ -302,6 +305,8 @@
     free(ptr);
 }
 
+#endif
+
 /*
  * SSH State machine related code
  */
@@ -840,6 +845,8 @@
 #endif
   static const char * const hostkey_method_ssh_rsa
     = "ssh-rsa";
+  static const char * const hostkey_method_ssh_rsa_all
+    = "rsa-sha2-256,rsa-sha2-512,ssh-rsa";
   static const char * const hostkey_method_ssh_dss
     = "ssh-dss";
 
@@ -914,7 +921,16 @@
         break;
 #endif
       case LIBSSH2_KNOWNHOST_KEY_SSHRSA:
-        hostkey_method = hostkey_method_ssh_rsa;
+#ifdef HAVE_LIBSSH2_VERSION
+        if(libssh2_version(0x010900))
+          /* since 1.9.0 libssh2_session_method_pref() works as expected */
+          hostkey_method = hostkey_method_ssh_rsa_all;
+        else
+#endif
+          /* old libssh2 which cannot correctly remove unsupported methods due
+           * to bug in src/kex.c or does not support the new methods anyways.
+           */
+          hostkey_method = hostkey_method_ssh_rsa;
         break;
       case LIBSSH2_KNOWNHOST_KEY_SSHDSS:
         hostkey_method = hostkey_method_ssh_dss;
@@ -2389,7 +2405,6 @@
       result = Curl_dyn_addf(&sshp->readdir, " -> %s", sshp->readdir_filename);
 
       if(result) {
-        sshc->readdir_line = NULL;
         Curl_safefree(sshp->readdir_filename);
         Curl_safefree(sshp->readdir_longentry);
         state(data, SSH_SFTP_CLOSE);
@@ -2993,12 +3008,9 @@
 
       Curl_safefree(sshc->rsa_pub);
       Curl_safefree(sshc->rsa);
-
       Curl_safefree(sshc->quote_path1);
       Curl_safefree(sshc->quote_path2);
-
       Curl_safefree(sshc->homedir);
-      Curl_safefree(sshc->readdir_line);
 
       /* the code we are about to return */
       result = sshc->actualcode;
@@ -3257,9 +3269,13 @@
   sock = conn->sock[FIRSTSOCKET];
 #endif /* CURL_LIBSSH2_DEBUG */
 
+#ifdef CURL_LIBSSH2_DEBUG
   sshc->ssh_session = libssh2_session_init_ex(my_libssh2_malloc,
                                               my_libssh2_free,
                                               my_libssh2_realloc, data);
+#else
+  sshc->ssh_session = libssh2_session_init();
+#endif
   if(!sshc->ssh_session) {
     failf(data, "Failure initialising ssh session");
     return CURLE_FAILED_INIT;
diff --git a/Utilities/cmcurl/lib/vssh/ssh.h b/Utilities/cmcurl/lib/vssh/ssh.h
index 13bb8aa..1e1b137 100644
--- a/Utilities/cmcurl/lib/vssh/ssh.h
+++ b/Utilities/cmcurl/lib/vssh/ssh.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -147,7 +147,6 @@
 
   char *homedir;              /* when doing SFTP we figure out home dir in the
                                  connect phase */
-  char *readdir_line;
   /* end of READDIR stuff */
 
   int secondCreateDirs;         /* counter use by the code to see if the
@@ -158,7 +157,8 @@
 
 #if defined(USE_LIBSSH)
   char *readdir_linkPath;
-  size_t readdir_len, readdir_totalLen, readdir_currLen;
+  size_t readdir_len;
+  struct dynbuf readdir_buf;
 /* our variables */
   unsigned kbd_state; /* 0 or 1 */
   ssh_key privkey;
diff --git a/Utilities/cmcurl/lib/vssh/wolfssh.c b/Utilities/cmcurl/lib/vssh/wolfssh.c
index 6a8fb56..17d59ec 100644
--- a/Utilities/cmcurl/lib/vssh/wolfssh.c
+++ b/Utilities/cmcurl/lib/vssh/wolfssh.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2019 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vtls/bearssl.c b/Utilities/cmcurl/lib/vtls/bearssl.c
index d9c0ce0..7e3eb79 100644
--- a/Utilities/cmcurl/lib/vtls/bearssl.c
+++ b/Utilities/cmcurl/lib/vtls/bearssl.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2019 - 2022, Michael Forney, <mforney@mforney.org>
+ * Copyright (C) Michael Forney, <mforney@mforney.org>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -58,7 +58,7 @@
   unsigned char buf[BR_SSL_BUFSIZE_BIDI];
   br_x509_trust_anchor *anchors;
   size_t anchors_len;
-  const char *protocols[2];
+  const char *protocols[ALPN_ENTRIES_MAX];
   /* SSL client context is active */
   bool active;
   /* size of pending write, yet to be flushed */
@@ -691,29 +691,17 @@
     Curl_ssl_sessionid_unlock(data);
   }
 
-  if(cf->conn->bits.tls_enable_alpn) {
-    int cur = 0;
+  if(connssl->alpn) {
+    struct alpn_proto_buf proto;
+    size_t i;
 
-    /* NOTE: when adding more protocols here, increase the size of the
-     * protocols array in `struct ssl_backend_data`.
-     */
-
-#ifdef USE_HTTP2
-    if(data->state.httpwant >= CURL_HTTP_VERSION_2
-#ifndef CURL_DISABLE_PROXY
-      && (!Curl_ssl_cf_is_proxy(cf) || !cf->conn->bits.tunnel_proxy)
-#endif
-      ) {
-      backend->protocols[cur++] = ALPN_H2;
-      infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
+    for(i = 0; i < connssl->alpn->count; ++i) {
+      backend->protocols[i] = connssl->alpn->entries[i];
     }
-#endif
-
-    backend->protocols[cur++] = ALPN_HTTP_1_1;
-    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
-
-    br_ssl_engine_set_protocol_names(&backend->ctx.eng,
-                                     backend->protocols, cur);
+    br_ssl_engine_set_protocol_names(&backend->ctx.eng, backend->protocols,
+                                     connssl->alpn->count);
+    Curl_alpn_to_proto_str(&proto, connssl->alpn);
+    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data);
   }
 
   if((1 == Curl_inet_pton(AF_INET, hostname, &addr))
@@ -862,26 +850,11 @@
   DEBUGASSERT(backend);
 
   if(cf->conn->bits.tls_enable_alpn) {
-    const char *protocol;
+    const char *proto;
 
-    protocol = br_ssl_engine_get_selected_protocol(&backend->ctx.eng);
-    if(protocol) {
-      infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, protocol);
-
-#ifdef USE_HTTP2
-      if(!strcmp(protocol, ALPN_H2))
-        cf->conn->alpn = CURL_HTTP_VERSION_2;
-      else
-#endif
-      if(!strcmp(protocol, ALPN_HTTP_1_1))
-        cf->conn->alpn = CURL_HTTP_VERSION_1_1;
-      else
-        infof(data, "ALPN, unrecognized protocol %s", protocol);
-      Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
-                          BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
-    }
-    else
-      infof(data, VTLS_INFOF_NO_ALPN);
+    proto = br_ssl_engine_get_selected_protocol(&backend->ctx.eng);
+    Curl_alpn_set_negotiated(cf, data, (const unsigned char *)proto,
+                             proto? strlen(proto) : 0);
   }
 
   if(ssl_config->primary.sessionid) {
@@ -977,7 +950,7 @@
 {
   CURLcode ret;
   struct ssl_connect_data *connssl = cf->ctx;
-  curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+  curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data);
   timediff_t timeout_ms;
   int what;
 
diff --git a/Utilities/cmcurl/lib/vtls/bearssl.h b/Utilities/cmcurl/lib/vtls/bearssl.h
index 5125359..b3651b0 100644
--- a/Utilities/cmcurl/lib/vtls/bearssl.h
+++ b/Utilities/cmcurl/lib/vtls/bearssl.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2019 - 2022, Michael Forney, <mforney@mforney.org>
+ * Copyright (C) Michael Forney, <mforney@mforney.org>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vtls/gskit.c b/Utilities/cmcurl/lib/vtls/gskit.c
index 2074dca..59fd27c 100644
--- a/Utilities/cmcurl/lib/vtls/gskit.c
+++ b/Utilities/cmcurl/lib/vtls/gskit.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -499,7 +499,7 @@
   (void)data;
   DEBUGASSERT(BACKEND);
 
-  if(QsoCancelOperation(cf->conn->sock[cf->sockindex], 0) > 0)
+  if(QsoCancelOperation(Curl_conn_cf_get_socket(cf, data), 0) > 0)
     QsoWaitForIOCompletion(BACKEND->iocport, &cstat, (struct timeval *) NULL);
 }
 
@@ -532,7 +532,7 @@
   DEBUGASSERT(connssl_next->backend);
   n = 1;
   fds[0].fd = BACKEND->remotefd;
-  fds[1].fd = cf->conn->sock[cf->sockindex];
+  fds[1].fd = Curl_conn_cf_get_socket(cf, data);
 
   if(directions & SOS_READ) {
     fds[0].events |= POLLOUT;
@@ -847,7 +847,7 @@
     result = set_numeric(data, BACKEND->handle, GSK_OS400_READ_TIMEOUT, 1);
   if(!result)
     result = set_numeric(data, BACKEND->handle, GSK_FD, BACKEND->localfd >= 0?
-                         BACKEND->localfd: cf->conn->sock[cf->sockindex]);
+                         BACKEND->localfd: Curl_conn_cf_get_socket(cf, data));
   if(!result)
     result = set_ciphers(cf, data, BACKEND->handle, &protoflags);
   if(!protoflags) {
@@ -1208,7 +1208,7 @@
 
   close_one(cf, data);
   rc = 0;
-  what = SOCKET_READABLE(cf->conn->sock[cf->sockindex],
+  what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data),
                          SSL_SHUTDOWN_TIMEOUT);
 
   while(loop--) {
@@ -1230,7 +1230,7 @@
        notify alert from the server. No way to gsk_secure_soc_read() now, so
        use read(). */
 
-    nread = read(cf->conn->sock[cf->sockindex], buf, sizeof(buf));
+    nread = read(Curl_conn_cf_get_socket(cf, data), buf, sizeof(buf));
 
     if(nread < 0) {
       char buffer[STRERROR_LEN];
@@ -1241,7 +1241,7 @@
     if(nread <= 0)
       break;
 
-    what = SOCKET_READABLE(cf->conn->sock[cf->sockindex], 0);
+    what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), 0);
   }
 
   return rc;
diff --git a/Utilities/cmcurl/lib/vtls/gskit.h b/Utilities/cmcurl/lib/vtls/gskit.h
index cf923f6..c71e6a0 100644
--- a/Utilities/cmcurl/lib/vtls/gskit.h
+++ b/Utilities/cmcurl/lib/vtls/gskit.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vtls/gtls.c b/Utilities/cmcurl/lib/vtls/gtls.c
index 104dce6..07dfaa4 100644
--- a/Utilities/cmcurl/lib/vtls/gtls.c
+++ b/Utilities/cmcurl/lib/vtls/gtls.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -84,7 +84,7 @@
 {
   struct Curl_cfilter *cf = s;
   struct ssl_connect_data *connssl = cf->ctx;
-  struct Curl_easy *data = connssl->call_data;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
   ssize_t nwritten;
   CURLcode result;
 
@@ -102,7 +102,7 @@
 {
   struct Curl_cfilter *cf = s;
   struct ssl_connect_data *connssl = cf->ctx;
-  struct Curl_easy *data = connssl->call_data;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
   ssize_t nread;
   CURLcode result;
 
@@ -214,7 +214,7 @@
   struct ssl_connect_data *connssl = cf->ctx;
   struct ssl_backend_data *backend = connssl->backend;
   gnutls_session_t session;
-  curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+  curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data);
 
   DEBUGASSERT(backend);
   session = backend->gtls.session;
@@ -434,12 +434,10 @@
   }
 
 #ifdef USE_GNUTLS_SRP
-  if((config->authtype == CURL_TLSAUTH_SRP) &&
-     Curl_auth_allowed_to_host(data)) {
+  if(config->username && Curl_auth_allowed_to_host(data)) {
     infof(data, "Using TLS-SRP username: %s", config->username);
 
-    rc = gnutls_srp_allocate_client_credentials(
-           &gtls->srp_client_cred);
+    rc = gnutls_srp_allocate_client_credentials(&gtls->srp_client_cred);
     if(rc != GNUTLS_E_SUCCESS) {
       failf(data, "gnutls_srp_allocate_client_cred() failed: %s",
             gnutls_strerror(rc));
@@ -581,7 +579,7 @@
 #ifdef USE_GNUTLS_SRP
   /* Only add SRP to the cipher list if SRP is requested. Otherwise
    * GnuTLS will disable TLS 1.3 support. */
-  if(config->authtype == CURL_TLSAUTH_SRP) {
+  if(config->username) {
     size_t len = strlen(prioritylist);
 
     char *prioritysrp = malloc(len + sizeof(GNUTLS_SRP) + 1);
@@ -646,7 +644,7 @@
 
 #ifdef USE_GNUTLS_SRP
   /* put the credentials to the current session */
-  if(config->authtype == CURL_TLSAUTH_SRP) {
+  if(config->username) {
     rc = gnutls_credentials_set(gtls->session, GNUTLS_CRD_SRP,
                                 gtls->srp_client_cred);
     if(rc != GNUTLS_E_SUCCESS) {
@@ -700,32 +698,22 @@
   if(result)
     return result;
 
-  if(cf->conn->bits.tls_enable_alpn) {
-    int cur = 0;
-    gnutls_datum_t protocols[2];
+  if(connssl->alpn) {
+    struct alpn_proto_buf proto;
+    gnutls_datum_t alpn[ALPN_ENTRIES_MAX];
+    size_t i;
 
-#ifdef USE_HTTP2
-    if(data->state.httpwant >= CURL_HTTP_VERSION_2
-#ifndef CURL_DISABLE_PROXY
-       && (!Curl_ssl_cf_is_proxy(cf) || !cf->conn->bits.tunnel_proxy)
-#endif
-       ) {
-      protocols[cur].data = (unsigned char *)ALPN_H2;
-      protocols[cur].size = ALPN_H2_LENGTH;
-      cur++;
-      infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
+    for(i = 0; i < connssl->alpn->count; ++i) {
+      alpn[i].data = (unsigned char *)connssl->alpn->entries[i];
+      alpn[i].size = (unsigned)strlen(connssl->alpn->entries[i]);
     }
-#endif
-
-    protocols[cur].data = (unsigned char *)ALPN_HTTP_1_1;
-    protocols[cur].size = ALPN_HTTP_1_1_LENGTH;
-    cur++;
-    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
-
-    if(gnutls_alpn_set_protocols(backend->gtls.session, protocols, cur, 0)) {
+    if(gnutls_alpn_set_protocols(backend->gtls.session, alpn,
+                                 (unsigned)connssl->alpn->count, 0)) {
       failf(data, "failed setting ALPN");
       return CURLE_SSL_CONNECT_ERROR;
     }
+    Curl_alpn_to_proto_str(&proto, connssl->alpn);
+    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data);
   }
 
   /* This might be a reconnect, so we check for a session ID in the cache
@@ -860,10 +848,8 @@
        config->verifyhost ||
        config->issuercert) {
 #ifdef USE_GNUTLS_SRP
-      if(ssl_config->primary.authtype == CURL_TLSAUTH_SRP
-         && ssl_config->primary.username
-         && !config->verifypeer
-         && gnutls_cipher_get(session)) {
+      if(ssl_config->primary.username && !config->verifypeer &&
+         gnutls_cipher_get(session)) {
         /* no peer cert, but auth is ok if we have SRP user and cipher and no
            peer verify */
       }
@@ -1271,28 +1257,10 @@
     int rc;
 
     rc = gnutls_alpn_get_selected_protocol(session, &proto);
-    if(rc == 0) {
-      infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, proto.size,
-            proto.data);
-
-#ifdef USE_HTTP2
-      if(proto.size == ALPN_H2_LENGTH &&
-         !memcmp(ALPN_H2, proto.data,
-                 ALPN_H2_LENGTH)) {
-        cf->conn->alpn = CURL_HTTP_VERSION_2;
-      }
-      else
-#endif
-      if(proto.size == ALPN_HTTP_1_1_LENGTH &&
-         !memcmp(ALPN_HTTP_1_1, proto.data, ALPN_HTTP_1_1_LENGTH)) {
-        cf->conn->alpn = CURL_HTTP_VERSION_1_1;
-      }
-    }
+    if(rc == 0)
+      Curl_alpn_set_negotiated(cf, data, proto.data, proto.size);
     else
-      infof(data, VTLS_INFOF_NO_ALPN);
-
-    Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
-                        BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
+      Curl_alpn_set_negotiated(cf, data, NULL, 0);
   }
 
   if(ssl_config->primary.sessionid) {
@@ -1516,7 +1484,7 @@
     char buf[120];
 
     while(!done) {
-      int what = SOCKET_READABLE(cf->conn->sock[cf->sockindex],
+      int what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data),
                                  SSL_SHUTDOWN_TIMEOUT);
       if(what > 0) {
         /* Something to read, let's do it and hope that it is the close
@@ -1556,8 +1524,7 @@
   gnutls_certificate_free_credentials(backend->gtls.cred);
 
 #ifdef USE_GNUTLS_SRP
-  if(ssl_config->primary.authtype == CURL_TLSAUTH_SRP
-     && ssl_config->primary.username != NULL)
+  if(ssl_config->primary.username)
     gnutls_srp_free_client_credentials(backend->gtls.srp_client_cred);
 #endif
 
diff --git a/Utilities/cmcurl/lib/vtls/gtls.h b/Utilities/cmcurl/lib/vtls/gtls.h
index 49c1c47..ac141e1 100644
--- a/Utilities/cmcurl/lib/vtls/gtls.h
+++ b/Utilities/cmcurl/lib/vtls/gtls.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vtls/hostcheck.c b/Utilities/cmcurl/lib/vtls/hostcheck.c
index 2a648f2..e827dc5 100644
--- a/Utilities/cmcurl/lib/vtls/hostcheck.c
+++ b/Utilities/cmcurl/lib/vtls/hostcheck.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vtls/hostcheck.h b/Utilities/cmcurl/lib/vtls/hostcheck.h
index d3c4eab..22a1ac2 100644
--- a/Utilities/cmcurl/lib/vtls/hostcheck.h
+++ b/Utilities/cmcurl/lib/vtls/hostcheck.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vtls/keylog.c b/Utilities/cmcurl/lib/vtls/keylog.c
index 1952a69..d37bb18 100644
--- a/Utilities/cmcurl/lib/vtls/keylog.c
+++ b/Utilities/cmcurl/lib/vtls/keylog.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vtls/keylog.h b/Utilities/cmcurl/lib/vtls/keylog.h
index 5d3c675..eff5bf3 100644
--- a/Utilities/cmcurl/lib/vtls/keylog.h
+++ b/Utilities/cmcurl/lib/vtls/keylog.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vtls/mbedtls.c b/Utilities/cmcurl/lib/vtls/mbedtls.c
index 0b81662..7f0f4e3 100644
--- a/Utilities/cmcurl/lib/vtls/mbedtls.c
+++ b/Utilities/cmcurl/lib/vtls/mbedtls.c
@@ -5,8 +5,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2010 - 2011, Hoi-Ho Chan, <hoiho.chan@gmail.com>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Hoi-Ho Chan, <hoiho.chan@gmail.com>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -159,15 +159,14 @@
 static int bio_cf_write(void *bio, const unsigned char *buf, size_t blen)
 {
   struct Curl_cfilter *cf = bio;
-  struct ssl_connect_data *connssl = cf->ctx;
-  struct Curl_easy *data = connssl->call_data;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
   ssize_t nwritten;
   CURLcode result;
 
   DEBUGASSERT(data);
   nwritten = Curl_conn_cf_send(cf->next, data, (char *)buf, blen, &result);
-  /* DEBUGF(infof(data, CFMSG(cf, "bio_cf_out_write(len=%d) -> %d, err=%d"),
-         blen, (int)nwritten, result)); */
+  DEBUGF(LOG_CF(data, cf, "bio_cf_out_write(len=%zu) -> %zd, err=%d",
+                blen, nwritten, result));
   if(nwritten < 0 && CURLE_AGAIN == result) {
     nwritten = MBEDTLS_ERR_SSL_WANT_WRITE;
   }
@@ -177,8 +176,7 @@
 static int bio_cf_read(void *bio, unsigned char *buf, size_t blen)
 {
   struct Curl_cfilter *cf = bio;
-  struct ssl_connect_data *connssl = cf->ctx;
-  struct Curl_easy *data = connssl->call_data;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
   ssize_t nread;
   CURLcode result;
 
@@ -188,8 +186,8 @@
     return 0;
 
   nread = Curl_conn_cf_recv(cf->next, data, (char *)buf, blen, &result);
-  /* DEBUGF(infof(data, CFMSG(cf, "bio_cf_in_read(len=%d) -> %d, err=%d"),
-         blen, (int)nread, result)); */
+  DEBUGF(LOG_CF(data, cf, "bio_cf_in_read(len=%zu) -> %zd, err=%d",
+                blen, nread, result));
   if(nread < 0 && CURLE_AGAIN == result) {
     nread = MBEDTLS_ERR_SSL_WANT_READ;
   }
@@ -648,14 +646,13 @@
   }
 
 #ifdef HAS_ALPN
-  if(cf->conn->bits.tls_enable_alpn) {
-    const char **p = &backend->protocols[0];
-#ifdef USE_HTTP2
-    if(data->state.httpwant >= CURL_HTTP_VERSION_2)
-      *p++ = ALPN_H2;
-#endif
-    *p++ = ALPN_HTTP_1_1;
-    *p = NULL;
+  if(connssl->alpn) {
+    struct alpn_proto_buf proto;
+    size_t i;
+
+    for(i = 0; i < connssl->alpn->count; ++i) {
+      backend->protocols[i] = connssl->alpn->entries[i];
+    }
     /* this function doesn't clone the protocols array, which is why we need
        to keep it around */
     if(mbedtls_ssl_conf_alpn_protocols(&backend->config,
@@ -663,8 +660,8 @@
       failf(data, "Failed setting ALPN protocols");
       return CURLE_SSL_CONNECT_ERROR;
     }
-    for(p = &backend->protocols[0]; *p; ++p)
-      infof(data, VTLS_INFOF_ALPN_OFFER_1STR, *p);
+    Curl_alpn_to_proto_str(&proto, connssl->alpn);
+    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data);
   }
 #endif
 
@@ -844,28 +841,11 @@
   }
 
 #ifdef HAS_ALPN
-  if(cf->conn->bits.tls_enable_alpn) {
-    const char *next_protocol = mbedtls_ssl_get_alpn_protocol(&backend->ssl);
+  if(connssl->alpn) {
+    const char *proto = mbedtls_ssl_get_alpn_protocol(&backend->ssl);
 
-    if(next_protocol) {
-      infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, next_protocol);
-#ifdef USE_HTTP2
-      if(!strncmp(next_protocol, ALPN_H2, ALPN_H2_LENGTH) &&
-         !next_protocol[ALPN_H2_LENGTH]) {
-        cf->conn->alpn = CURL_HTTP_VERSION_2;
-      }
-      else
-#endif
-        if(!strncmp(next_protocol, ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH) &&
-           !next_protocol[ALPN_HTTP_1_1_LENGTH]) {
-          cf->conn->alpn = CURL_HTTP_VERSION_1_1;
-        }
-    }
-    else {
-      infof(data, VTLS_INFOF_NO_ALPN);
-    }
-    Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
-                        BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
+    Curl_alpn_set_negotiated(cf, data, (const unsigned char *)proto,
+                             proto? strlen(proto) : 0);
   }
 #endif
 
@@ -1081,7 +1061,7 @@
 {
   CURLcode retcode;
   struct ssl_connect_data *connssl = cf->ctx;
-  curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+  curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data);
   timediff_t timeout_ms;
   int what;
 
diff --git a/Utilities/cmcurl/lib/vtls/mbedtls.h b/Utilities/cmcurl/lib/vtls/mbedtls.h
index ec3b43b..d8a0a06 100644
--- a/Utilities/cmcurl/lib/vtls/mbedtls.h
+++ b/Utilities/cmcurl/lib/vtls/mbedtls.h
@@ -7,8 +7,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2010, Hoi-Ho Chan, <hoiho.chan@gmail.com>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Hoi-Ho Chan, <hoiho.chan@gmail.com>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.c b/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.c
index 7d019ed..bcb7106 100644
--- a/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.c
+++ b/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.c
@@ -5,8 +5,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2013 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2010, 2011, Hoi-Ho Chan, <hoiho.chan@gmail.com>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Hoi-Ho Chan, <hoiho.chan@gmail.com>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.h b/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.h
index 22e8725..2b0bd41 100644
--- a/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.h
+++ b/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.h
@@ -7,8 +7,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2013 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2010, Hoi-Ho Chan, <hoiho.chan@gmail.com>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Hoi-Ho Chan, <hoiho.chan@gmail.com>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vtls/nss.c b/Utilities/cmcurl/lib/vtls/nss.c
index 03694d2..12c0390 100644
--- a/Utilities/cmcurl/lib/vtls/nss.c
+++ b/Utilities/cmcurl/lib/vtls/nss.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -873,11 +873,11 @@
 #endif
     case SSL_NEXT_PROTO_NO_SUPPORT:
     case SSL_NEXT_PROTO_NO_OVERLAP:
-      infof(data, VTLS_INFOF_NO_ALPN);
+      Curl_alpn_set_negotiated(cf, data, NULL, 0);
       return;
 #ifdef SSL_ENABLE_ALPN
     case SSL_NEXT_PROTO_SELECTED:
-      infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, buflen, buf);
+      Curl_alpn_set_negotiated(cf, data, buf, buflen);
       break;
 #endif
     default:
@@ -885,25 +885,6 @@
       break;
     }
 
-#ifdef USE_HTTP2
-    if(buflen == ALPN_H2_LENGTH &&
-       !memcmp(ALPN_H2, buf, ALPN_H2_LENGTH)) {
-      cf->conn->alpn = CURL_HTTP_VERSION_2;
-    }
-    else
-#endif
-    if(buflen == ALPN_HTTP_1_1_LENGTH &&
-       !memcmp(ALPN_HTTP_1_1, buf, ALPN_HTTP_1_1_LENGTH)) {
-      cf->conn->alpn = CURL_HTTP_VERSION_1_1;
-    }
-
-    /* This callback might get called when PR_Recv() is used within
-     * close_one() during a connection shutdown. At that point there might not
-     * be any "bundle" associated with the connection anymore.
-     */
-    if(conn->bundle)
-      Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
-                          BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
   }
 }
 
@@ -1555,36 +1536,6 @@
   initialized = 0;
 }
 
-/*
- * This function uses SSL_peek to determine connection status.
- *
- * Return codes:
- *     1 means the connection is still in place
- *     0 means the connection has been closed
- *    -1 means the connection status is unknown
- */
-static int nss_check_cxn(struct Curl_cfilter *cf, struct Curl_easy *data)
-{
-  struct ssl_connect_data *connssl = cf->ctx;
-  struct ssl_backend_data *backend = connssl->backend;
-  int rc;
-  char buf;
-
-  (void)data;
-  DEBUGASSERT(backend);
-
-  rc =
-    PR_Recv(backend->handle, (void *)&buf, 1, PR_MSG_PEEK,
-            PR_SecondsToInterval(1));
-  if(rc > 0)
-    return 1; /* connection still in place */
-
-  if(rc == 0)
-    return 0; /* connection has been closed */
-
-  return -1;  /* connection status unknown */
-}
-
 static void close_one(struct ssl_connect_data *connssl)
 {
   /* before the cleanup, check whether we are using a client certificate */
@@ -1897,7 +1848,7 @@
   PRFileDesc *nspr_io_stub = NULL;
   PRBool ssl_no_cache;
   PRBool ssl_cbc_random_iv;
-  curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+  curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data);
   struct ssl_connect_data *connssl = cf->ctx;
   struct ssl_backend_data *backend = connssl->backend;
   struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
@@ -2163,27 +2114,17 @@
 #endif
 
 #if defined(SSL_ENABLE_ALPN)
-  if(cf->conn->bits.tls_enable_alpn) {
-    int cur = 0;
-    unsigned char protocols[128];
+  if(connssl->alpn) {
+    struct alpn_proto_buf proto;
 
-#ifdef USE_HTTP2
-    if(data->state.httpwant >= CURL_HTTP_VERSION_2
-#ifndef CURL_DISABLE_PROXY
-       && (!Curl_ssl_cf_is_proxy(cf) || !cf->conn->bits.tunnel_proxy)
-#endif
-      ) {
-      protocols[cur++] = ALPN_H2_LENGTH;
-      memcpy(&protocols[cur], ALPN_H2, ALPN_H2_LENGTH);
-      cur += ALPN_H2_LENGTH;
-    }
-#endif
-    protocols[cur++] = ALPN_HTTP_1_1_LENGTH;
-    memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH);
-    cur += ALPN_HTTP_1_1_LENGTH;
-
-    if(SSL_SetNextProtoNego(backend->handle, protocols, cur) != SECSuccess)
+    result = Curl_alpn_to_proto_buf(&proto, connssl->alpn);
+    if(result || SSL_SetNextProtoNego(backend->handle, proto.data, proto.len)
+                   != SECSuccess) {
+      failf(data, "Error setting ALPN");
       goto error;
+    }
+    Curl_alpn_to_proto_str(&proto, connssl->alpn);
+    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data);
   }
 #endif
 
@@ -2393,6 +2334,19 @@
   return rc; /* number of bytes */
 }
 
+static bool
+nss_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data)
+{
+  struct ssl_connect_data *connssl = cf->ctx;
+  PRFileDesc *fd = connssl->backend->handle->lower;
+  char buf;
+
+  (void) data;
+
+  /* Returns true in case of error to force reading. */
+  return PR_Recv(fd, (void *) &buf, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT) != 0;
+}
+
 static ssize_t nss_recv(struct Curl_cfilter *cf,
                         struct Curl_easy *data,    /* transfer */
                         char *buf,             /* store read data here */
@@ -2540,10 +2494,10 @@
   nss_init,                     /* init */
   nss_cleanup,                  /* cleanup */
   nss_version,                  /* version */
-  nss_check_cxn,                /* check_cxn */
+  Curl_none_check_cxn,          /* check_cxn */
   /* NSS has no shutdown function provided and thus always fail */
   Curl_none_shutdown,           /* shutdown */
-  Curl_none_data_pending,       /* data_pending */
+  nss_data_pending,             /* data_pending */
   nss_random,                   /* random */
   nss_cert_status_request,      /* cert_status_request */
   nss_connect,                  /* connect */
diff --git a/Utilities/cmcurl/lib/vtls/nssg.h b/Utilities/cmcurl/lib/vtls/nssg.h
index 454a38f..ad7eef5 100644
--- a/Utilities/cmcurl/lib/vtls/nssg.h
+++ b/Utilities/cmcurl/lib/vtls/nssg.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vtls/openssl.c b/Utilities/cmcurl/lib/vtls/openssl.c
index c5085be..2ba53d9 100644
--- a/Utilities/cmcurl/lib/vtls/openssl.c
+++ b/Utilities/cmcurl/lib/vtls/openssl.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -96,6 +96,7 @@
 #include "curl_memory.h"
 #include "memdebug.h"
 
+
 /* Uncomment the ALLOW_RENEG line to a real #define if you want to allow TLS
    renegotiations when built with BoringSSL. Renegotiating is non-compliant
    with HTTP/2 and "an extremely dangerous protocol feature". Beware.
@@ -262,6 +263,12 @@
 #define HAVE_OPENSSL_VERSION
 #endif
 
+#ifdef OPENSSL_IS_BORINGSSL
+typedef uint32_t sslerr_t;
+#else
+typedef unsigned long sslerr_t;
+#endif
+
 /*
  * Whether the OpenSSL version has the API needed to support sharing an
  * X509_STORE between connections. The API is:
@@ -285,17 +292,17 @@
 #endif /* !LIBRESSL_VERSION_NUMBER */
 
 struct ssl_backend_data {
-  struct Curl_easy *logger; /* transfer handle to pass trace logs to, only
-                               using sockindex 0 */
   /* these ones requires specific SSL-types */
   SSL_CTX* ctx;
   SSL*     handle;
   X509*    server_cert;
+  BIO_METHOD *bio_method;
   CURLcode io_result;       /* result of last BIO cfilter operation */
 #ifndef HAVE_KEYLOG_CALLBACK
   /* Set to true once a valid keylog entry has been created to avoid dupes. */
   bool     keylog_done;
 #endif
+  bool x509_store_setup;            /* x509 store has been set up */
 };
 
 #if defined(HAVE_SSL_X509_STORE_SHARE)
@@ -710,14 +717,14 @@
 {
   struct Curl_cfilter *cf = BIO_get_data(bio);
   struct ssl_connect_data *connssl = cf->ctx;
-  struct Curl_easy *data = connssl->call_data;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
   ssize_t nwritten;
   CURLcode result = CURLE_SEND_ERROR;
 
   DEBUGASSERT(data);
   nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, &result);
-  /* DEBUGF(infof(data, CFMSG(cf, "bio_cf_out_write(len=%d) -> %d, err=%d"),
-         blen, (int)nwritten, result)); */
+  DEBUGF(LOG_CF(data, cf, "bio_cf_out_write(len=%d) -> %d, err=%d",
+                blen, (int)nwritten, result));
   BIO_clear_retry_flags(bio);
   connssl->backend->io_result = result;
   if(nwritten < 0) {
@@ -731,7 +738,7 @@
 {
   struct Curl_cfilter *cf = BIO_get_data(bio);
   struct ssl_connect_data *connssl = cf->ctx;
-  struct Curl_easy *data = connssl->call_data;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
   ssize_t nread;
   CURLcode result = CURLE_RECV_ERROR;
 
@@ -741,64 +748,75 @@
     return 0;
 
   nread = Curl_conn_cf_recv(cf->next, data, buf, blen, &result);
-  /* DEBUGF(infof(data, CFMSG(cf, "bio_cf_in_read(len=%d) -> %d, err=%d"),
-         blen, (int)nread, result)); */
+  DEBUGF(LOG_CF(data, cf, "bio_cf_in_read(len=%d) -> %d, err=%d",
+                blen, (int)nread, result));
   BIO_clear_retry_flags(bio);
   connssl->backend->io_result = result;
   if(nread < 0) {
     if(CURLE_AGAIN == result)
       BIO_set_retry_read(bio);
   }
+
+  /* Before returning server replies to the SSL instance, we need
+   * to have setup the x509 store or verification will fail. */
+  if(!connssl->backend->x509_store_setup) {
+    result = Curl_ssl_setup_x509_store(cf, data, connssl->backend->ctx);
+    if(result) {
+      connssl->backend->io_result = result;
+      return -1;
+    }
+    connssl->backend->x509_store_setup = TRUE;
+  }
+
   return (int)nread;
 }
 
-static BIO_METHOD *bio_cf_method = NULL;
-
 #if USE_PRE_1_1_API
 
 static BIO_METHOD bio_cf_meth_1_0 = {
-    BIO_TYPE_MEM,
-    "OpenSSL CF BIO",
-    bio_cf_out_write,
-    bio_cf_in_read,
-    NULL,                    /* puts is never called */
-    NULL,                    /* gets is never called */
-    bio_cf_ctrl,
-    bio_cf_create,
-    bio_cf_destroy,
-    NULL
+  BIO_TYPE_MEM,
+  "OpenSSL CF BIO",
+  bio_cf_out_write,
+  bio_cf_in_read,
+  NULL,                    /* puts is never called */
+  NULL,                    /* gets is never called */
+  bio_cf_ctrl,
+  bio_cf_create,
+  bio_cf_destroy,
+  NULL
 };
 
-static void bio_cf_init_methods(void)
+static BIO_METHOD *bio_cf_method_create(void)
 {
-  bio_cf_method = &bio_cf_meth_1_0;
+  return &bio_cf_meth_1_0;
 }
 
-#define bio_cf_free_methods() Curl_nop_stmt
+#define bio_cf_method_free(m) Curl_nop_stmt
 
 #else
 
-static void bio_cf_init_methods(void)
+static BIO_METHOD *bio_cf_method_create(void)
 {
-    bio_cf_method = BIO_meth_new(BIO_TYPE_MEM, "OpenSSL CF BIO");
-    BIO_meth_set_write(bio_cf_method, &bio_cf_out_write);
-    BIO_meth_set_read(bio_cf_method, &bio_cf_in_read);
-    BIO_meth_set_ctrl(bio_cf_method, &bio_cf_ctrl);
-    BIO_meth_set_create(bio_cf_method, &bio_cf_create);
-    BIO_meth_set_destroy(bio_cf_method, &bio_cf_destroy);
+  BIO_METHOD *m = BIO_meth_new(BIO_TYPE_MEM, "OpenSSL CF BIO");
+  if(m) {
+    BIO_meth_set_write(m, &bio_cf_out_write);
+    BIO_meth_set_read(m, &bio_cf_in_read);
+    BIO_meth_set_ctrl(m, &bio_cf_ctrl);
+    BIO_meth_set_create(m, &bio_cf_create);
+    BIO_meth_set_destroy(m, &bio_cf_destroy);
+  }
+  return m;
 }
 
-static void bio_cf_free_methods(void)
+static void bio_cf_method_free(BIO_METHOD *m)
 {
-    BIO_meth_free(bio_cf_method);
+  if(m)
+    BIO_meth_free(m);
 }
 
 #endif
 
 
-static bool ossl_attach_data(struct Curl_cfilter *cf,
-                             struct Curl_easy *data);
-
 /*
  * Number of bytes to read from the random number seed file. This must be
  * a finite value (because some entropy "files" like /dev/urandom have
@@ -930,54 +948,6 @@
   return buf;
 }
 
-/* Return an extra data index for the transfer data.
- * This index can be used with SSL_get_ex_data() and SSL_set_ex_data().
- */
-static int ossl_get_ssl_data_index(void)
-{
-  static int ssl_ex_data_data_index = -1;
-  if(ssl_ex_data_data_index < 0) {
-    ssl_ex_data_data_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
-  }
-  return ssl_ex_data_data_index;
-}
-
-/* Return an extra data index for the associated Curl_cfilter instance.
- * This index can be used with SSL_get_ex_data() and SSL_set_ex_data().
- */
-static int ossl_get_ssl_cf_index(void)
-{
-  static int ssl_ex_data_cf_index = -1;
-  if(ssl_ex_data_cf_index < 0) {
-    ssl_ex_data_cf_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
-  }
-  return ssl_ex_data_cf_index;
-}
-
-/* Return an extra data index for the sockindex.
- * This index can be used with SSL_get_ex_data() and SSL_set_ex_data().
- */
-static int ossl_get_ssl_sockindex_index(void)
-{
-  static int sockindex_index = -1;
-  if(sockindex_index < 0) {
-    sockindex_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
-  }
-  return sockindex_index;
-}
-
-/* Return an extra data index for proxy boolean.
- * This index can be used with SSL_get_ex_data() and SSL_set_ex_data().
- */
-static int ossl_get_proxy_index(void)
-{
-  static int proxy_index = -1;
-  if(proxy_index < 0) {
-    proxy_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
-  }
-  return proxy_index;
-}
-
 static int passwd_callback(char *buf, int num, int encrypting,
                            void *global_passwd)
 {
@@ -1254,7 +1224,7 @@
 
   if(ret) {
     X509 *ca;
-    unsigned long err;
+    sslerr_t err;
 
     if(!SSL_CTX_clear_chain_certs(ctx)) {
       ret = 0;
@@ -1776,14 +1746,8 @@
   OpenSSL_add_all_algorithms();
 #endif
 
-  bio_cf_init_methods();
   Curl_tls_keylog_open();
 
-  /* Initialize the extra data indexes */
-  if(ossl_get_ssl_data_index() < 0 || ossl_get_ssl_cf_index() < 0 ||
-     ossl_get_ssl_sockindex_index() < 0 || ossl_get_proxy_index() < 0)
-    return 0;
-
   return 1;
 }
 
@@ -1822,61 +1786,6 @@
 #endif
 
   Curl_tls_keylog_close();
-  bio_cf_free_methods();
-}
-
-/*
- * This function is used to determine connection status.
- *
- * Return codes:
- *     1 means the connection is still in place
- *     0 means the connection has been closed
- *    -1 means the connection status is unknown
- */
-static int ossl_check_cxn(struct Curl_cfilter *cf, struct Curl_easy *data)
-{
-  /* SSL_peek takes data out of the raw recv buffer without peeking so we use
-     recv MSG_PEEK instead. Bug #795 */
-#ifdef MSG_PEEK
-  char buf;
-  ssize_t nread;
-  nread = recv((RECV_TYPE_ARG1)cf->conn->sock[cf->sockindex],
-               (RECV_TYPE_ARG2)&buf, (RECV_TYPE_ARG3)1,
-               (RECV_TYPE_ARG4)MSG_PEEK);
-  if(nread == 0)
-    return 0; /* connection has been closed */
-  if(nread == 1)
-    return 1; /* connection still in place */
-  else if(nread == -1) {
-      int err = SOCKERRNO;
-      if(err == EINPROGRESS ||
-#if defined(EAGAIN) && (EAGAIN != EWOULDBLOCK)
-         err == EAGAIN ||
-#endif
-         err == EWOULDBLOCK)
-        return 1; /* connection still in place */
-      if(err == ECONNRESET ||
-#ifdef ECONNABORTED
-         err == ECONNABORTED ||
-#endif
-#ifdef ENETDOWN
-         err == ENETDOWN ||
-#endif
-#ifdef ENETRESET
-         err == ENETRESET ||
-#endif
-#ifdef ESHUTDOWN
-         err == ESHUTDOWN ||
-#endif
-#ifdef ETIMEDOUT
-         err == ETIMEDOUT ||
-#endif
-         err == ENOTCONN)
-        return 0; /* connection has been closed */
-  }
-#endif
-  (void)data;
-  return -1; /* connection status unknown */
 }
 
 /* Selects an OpenSSL crypto engine
@@ -1968,19 +1877,15 @@
   return list;
 }
 
-#define set_logger(connssl, data)                  \
-  connssl->backend->logger = data
-
 static void ossl_close(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
   struct ssl_connect_data *connssl = cf->ctx;
   struct ssl_backend_data *backend = connssl->backend;
 
+  (void)data;
   DEBUGASSERT(backend);
 
   if(backend->handle) {
-    set_logger(connssl, data);
-
     if(cf->next && cf->next->connected) {
       char buf[32];
       /* Maybe the server has already sent a close notify alert.
@@ -1997,6 +1902,11 @@
   if(backend->ctx) {
     SSL_CTX_free(backend->ctx);
     backend->ctx = NULL;
+    backend->x509_store_setup = FALSE;
+  }
+  if(backend->bio_method) {
+    bio_cf_method_free(backend->bio_method);
+    backend->bio_method = NULL;
   }
 }
 
@@ -2034,7 +1944,7 @@
   if(backend->handle) {
     buffsize = (int)sizeof(buf);
     while(!done && loop--) {
-      int what = SOCKET_READABLE(cf->conn->sock[cf->sockindex],
+      int what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data),
                                  SSL_SHUTDOWN_TIMEOUT);
       if(what > 0) {
         ERR_clear_error();
@@ -2163,6 +2073,22 @@
   return FALSE;
 }
 
+static CURLcode
+ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn,
+                X509 *server_cert, const char *hostname,
+                const char *dispname);
+
+CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn,
+                              X509 *server_cert)
+{
+  const char *hostname, *dispname;
+  int port;
+
+  (void)conn;
+  Curl_conn_get_host(data, FIRSTSOCKET, &hostname, &dispname, &port);
+  return ossl_verifyhost(data, conn, server_cert, hostname, dispname);
+}
+
 /* Quote from RFC2818 section 3.1 "Server Identity"
 
    If a subjectAltName extension of type dNSName is present, that MUST
@@ -2185,8 +2111,10 @@
 
    This function is now used from ngtcp2 (QUIC) as well.
 */
-CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn,
-                              X509 *server_cert)
+static CURLcode
+ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn,
+                X509 *server_cert, const char *hostname,
+                const char *dispname)
 {
   bool matched = FALSE;
   int target = GEN_DNS; /* target type, GEN_DNS or GEN_IPADD */
@@ -2200,12 +2128,9 @@
   CURLcode result = CURLE_OK;
   bool dNSName = FALSE; /* if a dNSName field exists in the cert */
   bool iPAddress = FALSE; /* if a iPAddress field exists in the cert */
-  const char *hostname, *dispname;
-  int port;
   size_t hostlen;
 
   (void)conn;
-  Curl_conn_get_host(data, FIRSTSOCKET, &hostname, &dispname, &port);
   hostlen = strlen(hostname);
 
 #ifndef ENABLE_IPV6
@@ -2668,24 +2593,15 @@
                        const void *buf, size_t len, SSL *ssl,
                        void *userp)
 {
-  char unknown[32];
-  const char *verstr = NULL;
-  struct connectdata *conn = userp;
-  int cf_idx = ossl_get_ssl_cf_index();
-  struct ssl_connect_data *connssl;
+  const char *verstr = "???";
+  struct Curl_cfilter *cf = userp;
   struct Curl_easy *data = NULL;
-  struct Curl_cfilter *cf;
+  char unknown[32];
 
-  DEBUGASSERT(cf_idx >= 0);
-  cf = (struct Curl_cfilter*) SSL_get_ex_data(ssl, cf_idx);
-  DEBUGASSERT(cf);
-  connssl = cf->ctx;
-  DEBUGASSERT(connssl);
-  DEBUGASSERT(connssl->backend);
-  data = connssl->backend->logger;
-
-  if(!conn || !data || !data->set.fdebug ||
-     (direction != 0 && direction != 1))
+  if(!cf)
+    return;
+  data = CF_DATA_CURRENT(cf);
+  if(!data || !data->set.fdebug || (direction && direction != 1))
     return;
 
  switch(ssl_ver) {
@@ -2730,6 +2646,9 @@
    * For TLS 1.3, skip notification of the decrypted inner Content-Type.
    */
   if(ssl_ver
+#ifdef SSL3_RT_HEADER
+     && content_type != SSL3_RT_HEADER
+#endif
 #ifdef SSL3_RT_INNER_CONTENT_TYPE
      && content_type != SSL3_RT_INNER_CONTENT_TYPE
 #endif
@@ -2765,7 +2684,7 @@
     }
 
     txt_len = msnprintf(ssl_buf, sizeof(ssl_buf),
-                        CFMSG(cf, "%s (%s), %s, %s (%d):\n"),
+                        "%s (%s), %s, %s (%d):\n",
                         verstr, direction?"OUT":"IN",
                         tls_rt_name, msg_name, msg_type);
     if(0 <= txt_len && (unsigned)txt_len < sizeof(ssl_buf)) {
@@ -2983,21 +2902,14 @@
   struct Curl_easy *data;
   struct Curl_cfilter *cf;
   const struct ssl_config_data *config;
-  curl_socket_t *sockindex_ptr;
-  int data_idx = ossl_get_ssl_data_index();
-  int cf_idx = ossl_get_ssl_cf_index();
-  int sockindex_idx = ossl_get_ssl_sockindex_index();
-  int proxy_idx = ossl_get_proxy_index();
+  struct ssl_connect_data *connssl;
   bool isproxy;
 
-  if(data_idx < 0 || cf_idx < 0 || sockindex_idx < 0 || proxy_idx < 0)
-    return 0;
-
-  cf = (struct Curl_cfilter*) SSL_get_ex_data(ssl, cf_idx);
-  data = (struct Curl_easy *) SSL_get_ex_data(ssl, data_idx);
+  cf = (struct Curl_cfilter*) SSL_get_app_data(ssl);
+  connssl = cf? cf->ctx : NULL;
+  data = connssl? CF_DATA_CURRENT(cf) : NULL;
   /* The sockindex has been stored as a pointer to an array element */
-  sockindex_ptr = (curl_socket_t*) SSL_get_ex_data(ssl, sockindex_idx);
-  if(!cf || !data || !sockindex_ptr)
+  if(!cf || !data)
     return 0;
 
   isproxy = Curl_ssl_cf_is_proxy(cf);
@@ -3091,7 +3003,7 @@
   BIO_free(cbio);
 
   /* if we didn't end up importing anything, treat that as an error */
-  return (count > 0 ? CURLE_OK : CURLE_SSL_CACERT_BADFILE);
+  return (count > 0) ? CURLE_OK : CURLE_SSL_CACERT_BADFILE;
 }
 
 static CURLcode populate_x509_store(struct Curl_cfilter *cf,
@@ -3110,206 +3022,219 @@
   const char * const ssl_crlfile = ssl_config->primary.CRLfile;
   const bool verifypeer = conn_config->verifypeer;
   bool imported_native_ca = false;
+  bool imported_ca_info_blob = false;
 
   if(!store)
     return CURLE_OUT_OF_MEMORY;
 
+  if(verifypeer) {
 #if defined(USE_WIN32_CRYPTO)
-  /* Import certificates from the Windows root certificate store if requested.
-     https://stackoverflow.com/questions/9507184/
-     https://github.com/d3x0r/SACK/blob/master/src/netlib/ssl_layer.c#L1037
-     https://datatracker.ietf.org/doc/html/rfc5280 */
-  if((conn_config->verifypeer || conn_config->verifyhost) &&
-     (ssl_config->native_ca_store)) {
-    HCERTSTORE hStore = CertOpenSystemStore(0, TEXT("ROOT"));
+    /* Import certificates from the Windows root certificate store if
+       requested.
+       https://stackoverflow.com/questions/9507184/
+       https://github.com/d3x0r/SACK/blob/master/src/netlib/ssl_layer.c#L1037
+       https://datatracker.ietf.org/doc/html/rfc5280 */
+    if(ssl_config->native_ca_store) {
+      HCERTSTORE hStore = CertOpenSystemStore(0, TEXT("ROOT"));
 
-    if(hStore) {
-      PCCERT_CONTEXT pContext = NULL;
-      /* The array of enhanced key usage OIDs will vary per certificate and is
-         declared outside of the loop so that rather than malloc/free each
-         iteration we can grow it with realloc, when necessary. */
-      CERT_ENHKEY_USAGE *enhkey_usage = NULL;
-      DWORD enhkey_usage_size = 0;
+      if(hStore) {
+        PCCERT_CONTEXT pContext = NULL;
+        /* The array of enhanced key usage OIDs will vary per certificate and
+           is declared outside of the loop so that rather than malloc/free each
+           iteration we can grow it with realloc, when necessary. */
+        CERT_ENHKEY_USAGE *enhkey_usage = NULL;
+        DWORD enhkey_usage_size = 0;
 
-      /* This loop makes a best effort to import all valid certificates from
-         the MS root store. If a certificate cannot be imported it is skipped.
-         'result' is used to store only hard-fail conditions (such as out of
-         memory) that cause an early break. */
-      result = CURLE_OK;
-      for(;;) {
-        X509 *x509;
-        FILETIME now;
-        BYTE key_usage[2];
-        DWORD req_size;
-        const unsigned char *encoded_cert;
+        /* This loop makes a best effort to import all valid certificates from
+           the MS root store. If a certificate cannot be imported it is
+           skipped. 'result' is used to store only hard-fail conditions (such
+           as out of memory) that cause an early break. */
+        result = CURLE_OK;
+        for(;;) {
+          X509 *x509;
+          FILETIME now;
+          BYTE key_usage[2];
+          DWORD req_size;
+          const unsigned char *encoded_cert;
 #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
-        char cert_name[256];
+          char cert_name[256];
 #endif
 
-        pContext = CertEnumCertificatesInStore(hStore, pContext);
-        if(!pContext)
-          break;
+          pContext = CertEnumCertificatesInStore(hStore, pContext);
+          if(!pContext)
+            break;
 
 #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
-        if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0,
-                               NULL, cert_name, sizeof(cert_name))) {
-          strcpy(cert_name, "Unknown");
-        }
-        infof(data, "SSL: Checking cert \"%s\"", cert_name);
-#endif
-
-        encoded_cert = (const unsigned char *)pContext->pbCertEncoded;
-        if(!encoded_cert)
-          continue;
-
-        GetSystemTimeAsFileTime(&now);
-        if(CompareFileTime(&pContext->pCertInfo->NotBefore, &now) > 0 ||
-           CompareFileTime(&now, &pContext->pCertInfo->NotAfter) > 0)
-          continue;
-
-        /* If key usage exists check for signing attribute */
-        if(CertGetIntendedKeyUsage(pContext->dwCertEncodingType,
-                                   pContext->pCertInfo,
-                                   key_usage, sizeof(key_usage))) {
-          if(!(key_usage[0] & CERT_KEY_CERT_SIGN_KEY_USAGE))
-            continue;
-        }
-        else if(GetLastError())
-          continue;
-
-        /* If enhanced key usage exists check for server auth attribute.
-         *
-         * Note "In a Microsoft environment, a certificate might also have EKU
-         * extended properties that specify valid uses for the certificate."
-         * The call below checks both, and behavior varies depending on what is
-         * found. For more details see CertGetEnhancedKeyUsage doc.
-         */
-        if(CertGetEnhancedKeyUsage(pContext, 0, NULL, &req_size)) {
-          if(req_size && req_size > enhkey_usage_size) {
-            void *tmp = realloc(enhkey_usage, req_size);
-
-            if(!tmp) {
-              failf(data, "SSL: Out of memory allocating for OID list");
-              result = CURLE_OUT_OF_MEMORY;
-              break;
-            }
-
-            enhkey_usage = (CERT_ENHKEY_USAGE *)tmp;
-            enhkey_usage_size = req_size;
+          if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0,
+                                 NULL, cert_name, sizeof(cert_name))) {
+            strcpy(cert_name, "Unknown");
           }
+          infof(data, "SSL: Checking cert \"%s\"", cert_name);
+#endif
+          encoded_cert = (const unsigned char *)pContext->pbCertEncoded;
+          if(!encoded_cert)
+            continue;
 
-          if(CertGetEnhancedKeyUsage(pContext, 0, enhkey_usage, &req_size)) {
-            if(!enhkey_usage->cUsageIdentifier) {
-              /* "If GetLastError returns CRYPT_E_NOT_FOUND, the certificate is
-                 good for all uses. If it returns zero, the certificate has no
-                 valid uses." */
-              if((HRESULT)GetLastError() != CRYPT_E_NOT_FOUND)
-                continue;
-            }
-            else {
-              DWORD i;
-              bool found = false;
+          GetSystemTimeAsFileTime(&now);
+          if(CompareFileTime(&pContext->pCertInfo->NotBefore, &now) > 0 ||
+             CompareFileTime(&now, &pContext->pCertInfo->NotAfter) > 0)
+            continue;
 
-              for(i = 0; i < enhkey_usage->cUsageIdentifier; ++i) {
-                if(!strcmp("1.3.6.1.5.5.7.3.1" /* OID server auth */,
-                           enhkey_usage->rgpszUsageIdentifier[i])) {
-                  found = true;
-                  break;
-                }
+          /* If key usage exists check for signing attribute */
+          if(CertGetIntendedKeyUsage(pContext->dwCertEncodingType,
+                                     pContext->pCertInfo,
+                                     key_usage, sizeof(key_usage))) {
+            if(!(key_usage[0] & CERT_KEY_CERT_SIGN_KEY_USAGE))
+              continue;
+          }
+          else if(GetLastError())
+            continue;
+
+          /* If enhanced key usage exists check for server auth attribute.
+           *
+           * Note "In a Microsoft environment, a certificate might also have
+           * EKU extended properties that specify valid uses for the
+           * certificate."  The call below checks both, and behavior varies
+           * depending on what is found. For more details see
+           * CertGetEnhancedKeyUsage doc.
+           */
+          if(CertGetEnhancedKeyUsage(pContext, 0, NULL, &req_size)) {
+            if(req_size && req_size > enhkey_usage_size) {
+              void *tmp = realloc(enhkey_usage, req_size);
+
+              if(!tmp) {
+                failf(data, "SSL: Out of memory allocating for OID list");
+                result = CURLE_OUT_OF_MEMORY;
+                break;
               }
 
-              if(!found)
-                continue;
+              enhkey_usage = (CERT_ENHKEY_USAGE *)tmp;
+              enhkey_usage_size = req_size;
             }
+
+            if(CertGetEnhancedKeyUsage(pContext, 0, enhkey_usage, &req_size)) {
+              if(!enhkey_usage->cUsageIdentifier) {
+                /* "If GetLastError returns CRYPT_E_NOT_FOUND, the certificate
+                   is good for all uses. If it returns zero, the certificate
+                   has no valid uses." */
+                if((HRESULT)GetLastError() != CRYPT_E_NOT_FOUND)
+                  continue;
+              }
+              else {
+                DWORD i;
+                bool found = false;
+
+                for(i = 0; i < enhkey_usage->cUsageIdentifier; ++i) {
+                  if(!strcmp("1.3.6.1.5.5.7.3.1" /* OID server auth */,
+                             enhkey_usage->rgpszUsageIdentifier[i])) {
+                    found = true;
+                    break;
+                  }
+                }
+
+                if(!found)
+                  continue;
+              }
+            }
+            else
+              continue;
           }
           else
             continue;
-        }
-        else
-          continue;
 
-        x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded);
-        if(!x509)
-          continue;
+          x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded);
+          if(!x509)
+            continue;
 
-        /* Try to import the certificate. This may fail for legitimate reasons
-           such as duplicate certificate, which is allowed by MS but not
-           OpenSSL. */
-        if(X509_STORE_add_cert(store, x509) == 1) {
+          /* Try to import the certificate. This may fail for legitimate
+             reasons such as duplicate certificate, which is allowed by MS but
+             not OpenSSL. */
+          if(X509_STORE_add_cert(store, x509) == 1) {
 #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
-          infof(data, "SSL: Imported cert \"%s\"", cert_name);
+            infof(data, "SSL: Imported cert \"%s\"", cert_name);
 #endif
-          imported_native_ca = true;
+            imported_native_ca = true;
+          }
+          X509_free(x509);
         }
-        X509_free(x509);
+
+        free(enhkey_usage);
+        CertFreeCertificateContext(pContext);
+        CertCloseStore(hStore, 0);
+
+        if(result)
+          return result;
       }
-
-      free(enhkey_usage);
-      CertFreeCertificateContext(pContext);
-      CertCloseStore(hStore, 0);
-
-      if(result)
-        return result;
+      if(imported_native_ca)
+        infof(data, "successfully imported Windows CA store");
+      else
+        infof(data, "error importing Windows CA store, continuing anyway");
     }
-    if(imported_native_ca)
-      infof(data, "successfully imported Windows CA store");
-    else
-      infof(data, "error importing Windows CA store, continuing anyway");
-  }
 #endif
-
-  if(ca_info_blob) {
-    result = load_cacert_from_memory(store, ca_info_blob);
-    if(result) {
-      if(result == CURLE_OUT_OF_MEMORY ||
-         (verifypeer && !imported_native_ca)) {
+    if(ca_info_blob) {
+      result = load_cacert_from_memory(store, ca_info_blob);
+      if(result) {
         failf(data, "error importing CA certificate blob");
         return result;
       }
-      /* Only warn if no certificate verification is required. */
-      infof(data, "error importing CA certificate blob, continuing anyway");
+      else {
+        imported_ca_info_blob = true;
+        infof(data, "successfully imported CA certificate blob");
+      }
     }
-  }
 
-  if(verifypeer && !imported_native_ca && (ssl_cafile || ssl_capath)) {
+    if(ssl_cafile || ssl_capath) {
 #if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
-  /* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */
-    if(ssl_cafile &&
-       !X509_STORE_load_file(store, ssl_cafile)) {
-      /* Fail if we insist on successfully verifying the server. */
-      failf(data, "error setting certificate file: %s", ssl_cafile);
-      return CURLE_SSL_CACERT_BADFILE;
-    }
-    if(ssl_capath &&
-       !X509_STORE_load_path(store, ssl_capath)) {
-      /* Fail if we insist on successfully verifying the server. */
-      failf(data, "error setting certificate path: %s", ssl_capath);
-      return CURLE_SSL_CACERT_BADFILE;
-    }
+      /* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */
+      if(ssl_cafile && !X509_STORE_load_file(store, ssl_cafile)) {
+        if(!imported_native_ca && !imported_ca_info_blob) {
+          /* Fail if we insist on successfully verifying the server. */
+          failf(data, "error setting certificate file: %s", ssl_cafile);
+          return CURLE_SSL_CACERT_BADFILE;
+        }
+        else
+          infof(data, "error setting certificate file, continuing anyway");
+      }
+      if(ssl_capath && !X509_STORE_load_path(store, ssl_capath)) {
+        if(!imported_native_ca && !imported_ca_info_blob) {
+          /* Fail if we insist on successfully verifying the server. */
+          failf(data, "error setting certificate path: %s", ssl_capath);
+          return CURLE_SSL_CACERT_BADFILE;
+        }
+        else
+          infof(data, "error setting certificate path, continuing anyway");
+      }
 #else
-    /* tell OpenSSL where to find CA certificates that are used to verify the
-       server's certificate. */
-    if(!X509_STORE_load_locations(store, ssl_cafile, ssl_capath)) {
-      /* Fail if we insist on successfully verifying the server. */
-      failf(data, "error setting certificate verify locations:"
-            "  CAfile: %s CApath: %s",
-            ssl_cafile ? ssl_cafile : "none",
-            ssl_capath ? ssl_capath : "none");
-      return CURLE_SSL_CACERT_BADFILE;
-    }
+      /* tell OpenSSL where to find CA certificates that are used to verify the
+         server's certificate. */
+      if(!X509_STORE_load_locations(store, ssl_cafile, ssl_capath)) {
+        if(!imported_native_ca && !imported_ca_info_blob) {
+          /* Fail if we insist on successfully verifying the server. */
+          failf(data, "error setting certificate verify locations:"
+                "  CAfile: %s CApath: %s",
+                ssl_cafile ? ssl_cafile : "none",
+                ssl_capath ? ssl_capath : "none");
+          return CURLE_SSL_CACERT_BADFILE;
+        }
+        else {
+          infof(data, "error setting certificate verify locations,"
+                " continuing anyway");
+        }
+      }
 #endif
-    infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
-    infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
-  }
+      infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
+      infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
+    }
 
 #ifdef CURL_CA_FALLBACK
-  if(verifypeer &&
-     !ca_info_blob && !ssl_cafile && !ssl_capath && !imported_native_ca) {
-    /* verifying the peer without any CA certificates won't
-       work so use openssl's built-in default as fallback */
-    X509_STORE_set_default_paths(store);
-  }
+    if(!ssl_cafile && !ssl_capath &&
+       !imported_native_ca && !imported_ca_info_blob) {
+      /* verifying the peer without any CA certificates won't
+         work so use openssl's built-in default as fallback */
+      X509_STORE_set_default_paths(store);
+    }
 #endif
+  }
 
   if(ssl_crlfile) {
     /* tell OpenSSL where to find CRL file that is used to check certificate
@@ -3440,9 +3365,9 @@
   }
 }
 
-static CURLcode set_up_x509_store(struct Curl_cfilter *cf,
-                                  struct Curl_easy *data,
-                                  struct ssl_backend_data *backend)
+CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf,
+                                   struct Curl_easy *data,
+                                   SSL_CTX *ssl_ctx)
 {
   struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
   struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
@@ -3462,10 +3387,10 @@
 
   cached_store = get_cached_x509_store(cf, data);
   if(cached_store && cache_criteria_met && X509_STORE_up_ref(cached_store)) {
-    SSL_CTX_set_cert_store(backend->ctx, cached_store);
+    SSL_CTX_set_cert_store(ssl_ctx, cached_store);
   }
   else {
-    X509_STORE *store = SSL_CTX_get_cert_store(backend->ctx);
+    X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx);
 
     result = populate_x509_store(cf, data, store);
     if(result == CURLE_OK && cache_criteria_met) {
@@ -3476,11 +3401,11 @@
   return result;
 }
 #else /* HAVE_SSL_X509_STORE_SHARE */
-static CURLcode set_up_x509_store(struct Curl_cfilter *cf,
-                                  struct Curl_easy *data,
-                                  struct ssl_backend_data *backend)
+CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf,
+                                   struct Curl_easy *data,
+                                   SSL_CTX *ssl_ctx)
 {
-  X509_STORE *store = SSL_CTX_get_cert_store(backend->ctx);
+  X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx);
 
   return populate_x509_store(cf, data, store);
 }
@@ -3510,9 +3435,6 @@
 #endif
 #endif
   const long int ssl_version = conn_config->version;
-#ifdef USE_OPENSSL_SRP
-  const enum CURL_TLSAUTH ssl_authtype = ssl_config->primary.authtype;
-#endif
   char * const ssl_cert = ssl_config->primary.clientcert;
   const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob;
   const char * const ssl_cert_type = ssl_config->cert_type;
@@ -3580,8 +3502,7 @@
   if(data->set.fdebug && data->set.verbose) {
     /* the SSL trace callback is only used for verbose logging */
     SSL_CTX_set_msg_callback(backend->ctx, ossl_trace);
-    SSL_CTX_set_msg_callback_arg(backend->ctx, cf->conn);
-    set_logger(connssl, data);
+    SSL_CTX_set_msg_callback_arg(backend->ctx, cf);
   }
 #endif
 
@@ -3677,36 +3598,17 @@
   SSL_CTX_set_options(backend->ctx, ctx_options);
 
 #ifdef HAS_ALPN
-  if(cf->conn->bits.tls_enable_alpn) {
-    int cur = 0;
-    unsigned char protocols[128];
+  if(connssl->alpn) {
+    struct alpn_proto_buf proto;
 
-#ifdef USE_HTTP2
-    if(data->state.httpwant >= CURL_HTTP_VERSION_2
-#ifndef CURL_DISABLE_PROXY
-       && (!Curl_ssl_cf_is_proxy(cf) || !cf->conn->bits.tunnel_proxy)
-#endif
-      ) {
-      protocols[cur++] = ALPN_H2_LENGTH;
-
-      memcpy(&protocols[cur], ALPN_H2, ALPN_H2_LENGTH);
-      cur += ALPN_H2_LENGTH;
-      infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
-    }
-#endif
-
-    protocols[cur++] = ALPN_HTTP_1_1_LENGTH;
-    memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH);
-    cur += ALPN_HTTP_1_1_LENGTH;
-    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
-
-    /* expects length prefixed preference ordered list of protocols in wire
-     * format
-     */
-    if(SSL_CTX_set_alpn_protos(backend->ctx, protocols, cur)) {
+    result = Curl_alpn_to_proto_buf(&proto, connssl->alpn);
+    if(result ||
+       SSL_CTX_set_alpn_protos(backend->ctx, proto.data, proto.len)) {
       failf(data, "Error setting ALPN");
       return CURLE_SSL_CONNECT_ERROR;
     }
+    Curl_alpn_to_proto_str(&proto, connssl->alpn);
+    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data);
   }
 #endif
 
@@ -3764,8 +3666,7 @@
 #endif
 
 #ifdef USE_OPENSSL_SRP
-  if((ssl_authtype == CURL_TLSAUTH_SRP) &&
-     Curl_auth_allowed_to_host(data)) {
+  if(ssl_config->primary.username && Curl_auth_allowed_to_host(data)) {
     char * const ssl_username = ssl_config->primary.username;
     char * const ssl_password = ssl_config->primary.password;
     infof(data, "Using TLS-SRP username: %s", ssl_username);
@@ -3789,10 +3690,6 @@
   }
 #endif
 
-  result = set_up_x509_store(cf, data, backend);
-  if(result)
-    return result;
-
   /* OpenSSL always tries to verify the peer, this only says whether it should
    * fail to connect if the verification fails, or if it should continue
    * anyway. In the latter case the result of the verification is checked with
@@ -3836,6 +3733,8 @@
     return CURLE_OUT_OF_MEMORY;
   }
 
+  SSL_set_app_data(backend->handle, cf);
+
 #if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \
     !defined(OPENSSL_NO_OCSP)
   if(conn_config->verifystatus)
@@ -3863,13 +3762,7 @@
   }
 #endif
 
-  if(!ossl_attach_data(cf, data)) {
-    /* Maybe the internal errors of SSL_get_ex_new_index or SSL_set_ex_data */
-    failf(data, "SSL: ossl_attach_data failed: %s",
-          ossl_strerror(ERR_get_error(), error_buffer,
-                        sizeof(error_buffer)));
-    return CURLE_SSL_CONNECT_ERROR;
-  }
+  SSL_set_app_data(backend->handle, cf);
 
   if(ssl_config->primary.sessionid) {
     Curl_ssl_sessionid_lock(data);
@@ -3888,13 +3781,26 @@
     Curl_ssl_sessionid_unlock(data);
   }
 
-  bio = BIO_new(bio_cf_method);
+  backend->bio_method = bio_cf_method_create();
+  if(!backend->bio_method)
+    return CURLE_OUT_OF_MEMORY;
+  bio = BIO_new(backend->bio_method);
   if(!bio)
     return CURLE_OUT_OF_MEMORY;
 
   BIO_set_data(bio, cf);
+#ifdef HAVE_SSL_SET0_WBIO
+  /* with OpenSSL v1.1.1 we get an alternative to SSL_set_bio() that works
+   * without backward compat quirks. Every call takes one reference, so we
+   * up it and pass. SSL* then owns it and will free.
+   * We check on the function in configure, since libressl and friends
+   * each have their own versions to add support for this. */
+  BIO_up_ref(bio);
+  SSL_set0_rbio(backend->handle, bio);
+  SSL_set0_wbio(backend->handle, bio);
+#else
   SSL_set_bio(backend->handle, bio, bio);
-
+#endif
   connssl->connecting_state = ssl_connect_2;
 
   return CURLE_OK;
@@ -3915,6 +3821,16 @@
   ERR_clear_error();
 
   err = SSL_connect(backend->handle);
+
+  if(!backend->x509_store_setup) {
+    /* After having send off the ClientHello, we prepare the x509
+     * store to verify the coming certificate from the server */
+    CURLcode result = Curl_ssl_setup_x509_store(cf, data, backend->ctx);
+    if(result)
+      return result;
+    backend->x509_store_setup = TRUE;
+  }
+
 #ifndef HAVE_KEYLOG_CALLBACK
   if(Curl_tls_keylog_enabled()) {
     /* If key logging is enabled, wait for the handshake to complete and then
@@ -3949,7 +3865,7 @@
     }
     else {
       /* untreated error */
-      unsigned long errdetail;
+      sslerr_t errdetail;
       char error_buffer[256]="";
       CURLcode result;
       long lerr;
@@ -4043,26 +3959,8 @@
       const unsigned char *neg_protocol;
       unsigned int len;
       SSL_get0_alpn_selected(backend->handle, &neg_protocol, &len);
-      if(len) {
-        infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, len, neg_protocol);
 
-#ifdef USE_HTTP2
-        if(len == ALPN_H2_LENGTH &&
-           !memcmp(ALPN_H2, neg_protocol, len)) {
-          cf->conn->alpn = CURL_HTTP_VERSION_2;
-        }
-        else
-#endif
-        if(len == ALPN_HTTP_1_1_LENGTH &&
-           !memcmp(ALPN_HTTP_1_1, neg_protocol, ALPN_HTTP_1_1_LENGTH)) {
-          cf->conn->alpn = CURL_HTTP_VERSION_1_1;
-        }
-      }
-      else
-        infof(data, VTLS_INFOF_NO_ALPN);
-
-      Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
-                          BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
+      return Curl_alpn_set_negotiated(cf, data, neg_protocol, len);
     }
 #endif
 
@@ -4205,7 +4103,8 @@
   BIO_free(mem);
 
   if(conn_config->verifyhost) {
-    result = Curl_ossl_verifyhost(data, conn, backend->server_cert);
+    result = ossl_verifyhost(data, conn, backend->server_cert,
+                             connssl->hostname, connssl->dispname);
     if(result) {
       X509_free(backend->server_cert);
       backend->server_cert = NULL;
@@ -4379,7 +4278,7 @@
 {
   CURLcode result = CURLE_OK;
   struct ssl_connect_data *connssl = cf->ctx;
-  curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+  curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data);
   int what;
 
   /* check if the connection has already been established */
@@ -4418,8 +4317,9 @@
     }
 
     /* if ssl is expecting something, check if it's available. */
-    if(connssl->connecting_state == ssl_connect_2_reading ||
-       connssl->connecting_state == ssl_connect_2_writing) {
+    if(!nonblocking &&
+       (connssl->connecting_state == ssl_connect_2_reading ||
+        connssl->connecting_state == ssl_connect_2_writing)) {
 
       curl_socket_t writefd = ssl_connect_2_writing ==
         connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
@@ -4427,7 +4327,7 @@
         connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
 
       what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd,
-                               nonblocking?0:timeout_ms);
+                               timeout_ms);
       if(what < 0) {
         /* fatal error */
         failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
@@ -4435,11 +4335,6 @@
         goto out;
       }
       if(0 == what) {
-        if(nonblocking) {
-          *done = FALSE;
-          result = CURLE_OK;
-          goto out;
-        }
         /* timeout */
         failf(data, "SSL connection timeout");
         result = CURLE_OPERATION_TIMEDOUT;
@@ -4527,7 +4422,7 @@
      'size_t' */
   int err;
   char error_buffer[256];
-  unsigned long sslerror;
+  sslerr_t sslerror;
   int memlen;
   int rc;
   struct ssl_connect_data *connssl = cf->ctx;
@@ -4539,7 +4434,6 @@
   ERR_clear_error();
 
   memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len;
-  set_logger(connssl, data);
   rc = SSL_write(backend->handle, mem, memlen);
 
   if(rc <= 0) {
@@ -4636,7 +4530,6 @@
   ERR_clear_error();
 
   buffsize = (buffersize > (size_t)INT_MAX) ? INT_MAX : (int)buffersize;
-  set_logger(connssl, data);
   nread = (ssize_t)SSL_read(backend->handle, buf, buffsize);
 
   if(nread <= 0) {
@@ -4854,89 +4747,6 @@
          (void *)backend->ctx : (void *)backend->handle;
 }
 
-static bool ossl_attach_data(struct Curl_cfilter *cf,
-                             struct Curl_easy *data)
-{
-  struct ssl_connect_data *connssl = cf->ctx;
-  struct ssl_backend_data *backend = connssl->backend;
-  const struct ssl_config_data *config;
-
-  DEBUGASSERT(backend);
-
-  /* If we don't have SSL context, do nothing. */
-  if(!backend->handle)
-    return FALSE;
-
-  config = Curl_ssl_cf_get_config(cf, data);
-  if(config->primary.sessionid) {
-    int data_idx = ossl_get_ssl_data_index();
-    int cf_idx = ossl_get_ssl_cf_index();
-    int sockindex_idx = ossl_get_ssl_sockindex_index();
-    int proxy_idx = ossl_get_proxy_index();
-
-    if(data_idx >= 0 && cf_idx >= 0 && sockindex_idx >= 0 &&
-       proxy_idx >= 0) {
-      int data_status, cf_status, sockindex_status, proxy_status;
-
-      /* Store the data needed for the "new session" callback.
-       * The sockindex is stored as a pointer to an array element. */
-      data_status = SSL_set_ex_data(backend->handle, data_idx, data);
-      cf_status = SSL_set_ex_data(backend->handle, cf_idx, cf);
-      sockindex_status = SSL_set_ex_data(backend->handle, sockindex_idx,
-                                         cf->conn->sock + cf->sockindex);
-#ifndef CURL_DISABLE_PROXY
-      proxy_status = SSL_set_ex_data(backend->handle, proxy_idx,
-                                     Curl_ssl_cf_is_proxy(cf)?
-                                       (void *) 1 : NULL);
-#else
-      proxy_status = SSL_set_ex_data(backend->handle, proxy_idx, NULL);
-#endif
-      if(data_status && cf_status && sockindex_status && proxy_status)
-        return TRUE;
-    }
-    return FALSE;
-  }
-  return TRUE;
-}
-
-/*
- * Starting with TLS 1.3, the ossl_new_session_cb callback gets called after
- * the handshake. If the transfer that sets up the callback gets killed before
- * this callback arrives, we must make sure to properly clear the data to
- * avoid UAF problems. A future optimization could be to instead store another
- * transfer that might still be using the same connection.
- */
-
-static void ossl_detach_data(struct Curl_cfilter *cf,
-                             struct Curl_easy *data)
-{
-  struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
-  struct ssl_connect_data *connssl = cf->ctx;
-  struct ssl_backend_data *backend = connssl->backend;
-  DEBUGASSERT(backend);
-
-  /* If we don't have SSL context, do nothing. */
-  if(!backend->handle)
-    return;
-
-  if(ssl_config->primary.sessionid) {
-    int data_idx = ossl_get_ssl_data_index();
-    int cf_idx = ossl_get_ssl_cf_index();
-    int sockindex_idx = ossl_get_ssl_sockindex_index();
-    int proxy_idx = ossl_get_proxy_index();
-
-    if(data_idx >= 0 && cf_idx >= 0 && sockindex_idx >= 0 &&
-       proxy_idx >= 0) {
-      /* Disable references to data in "new session" callback to avoid
-       * accessing a stale pointer. */
-      SSL_set_ex_data(backend->handle, data_idx, NULL);
-      SSL_set_ex_data(backend->handle, cf_idx, NULL);
-      SSL_set_ex_data(backend->handle, sockindex_idx, NULL);
-      SSL_set_ex_data(backend->handle, proxy_idx, NULL);
-    }
-  }
-}
-
 static void ossl_free_multi_ssl_backend_data(
   struct multi_ssl_backend_data *mbackend)
 {
@@ -4969,7 +4779,7 @@
   ossl_init,                /* init */
   ossl_cleanup,             /* cleanup */
   ossl_version,             /* version */
-  ossl_check_cxn,           /* check_cxn */
+  Curl_none_check_cxn,      /* check_cxn */
   ossl_shutdown,            /* shutdown */
   ossl_data_pending,        /* data_pending */
   ossl_random,              /* random */
@@ -4990,8 +4800,8 @@
 #else
   NULL,                     /* sha256sum */
 #endif
-  ossl_attach_data,         /* use of data in this connection */
-  ossl_detach_data,         /* remote of data from this connection */
+  NULL,                     /* use of data in this connection */
+  NULL,                     /* remote of data from this connection */
   ossl_free_multi_ssl_backend_data, /* free_multi_ssl_backend_data */
   ossl_recv,                /* recv decrypted data */
   ossl_send,                /* send data to encrypt */
diff --git a/Utilities/cmcurl/lib/vtls/openssl.h b/Utilities/cmcurl/lib/vtls/openssl.h
index 9df4ecd..950faab 100644
--- a/Utilities/cmcurl/lib/vtls/openssl.h
+++ b/Utilities/cmcurl/lib/vtls/openssl.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -56,5 +56,14 @@
 
 CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl);
 
+/**
+ * Setup the OpenSSL X509_STORE in `ssl_ctx` for the cfilter `cf` and
+ * easy handle `data`. Will allow reuse of a shared cache if suitable
+ * and configured.
+ */
+CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf,
+                                   struct Curl_easy *data,
+                                   SSL_CTX *ssl_ctx);
+
 #endif /* USE_OPENSSL */
 #endif /* HEADER_CURL_SSLUSE_H */
diff --git a/Utilities/cmcurl/lib/vtls/rustls.c b/Utilities/cmcurl/lib/vtls/rustls.c
index 27f4ec8..003533d 100644
--- a/Utilities/cmcurl/lib/vtls/rustls.c
+++ b/Utilities/cmcurl/lib/vtls/rustls.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2020 - 2022, Jacob Hoffman-Andrews,
+ * Copyright (C) Jacob Hoffman-Andrews,
  * <github@hoffman-andrews.com>
  *
  * This software is licensed as described in the file COPYING, which
@@ -150,6 +150,7 @@
   size_t plain_bytes_copied = 0;
   rustls_result rresult = 0;
   char errorbuf[255];
+  size_t errorlen;
   rustls_io_result io_error;
 
   DEBUGASSERT(backend);
@@ -161,7 +162,7 @@
   io_error = rustls_connection_read_tls(rconn, read_cb, &io_ctx,
                                         &tls_bytes_read);
   if(io_error == EAGAIN || io_error == EWOULDBLOCK) {
-    infof(data, CFMSG(cf, "cr_recv: EAGAIN or EWOULDBLOCK"));
+    DEBUGF(LOG_CF(data, cf, "cr_recv: EAGAIN or EWOULDBLOCK"));
   }
   else if(io_error) {
     char buffer[STRERROR_LEN];
@@ -171,12 +172,13 @@
     return -1;
   }
 
-  infof(data, CFMSG(cf, "cr_recv: read %ld TLS bytes"), tls_bytes_read);
+  DEBUGF(LOG_CF(data, cf, "cr_recv: read %ld TLS bytes", tls_bytes_read));
 
   rresult = rustls_connection_process_new_packets(rconn);
   if(rresult != RUSTLS_RESULT_OK) {
-    rustls_error(rresult, errorbuf, sizeof(errorbuf), &n);
-    failf(data, "%.*s", n, errorbuf);
+    rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen);
+    failf(data, "rustls_connection_process_new_packets: %.*s",
+      errorlen, errorbuf);
     *err = map_error(rresult);
     return -1;
   }
@@ -189,14 +191,21 @@
       plainlen - plain_bytes_copied,
       &n);
     if(rresult == RUSTLS_RESULT_PLAINTEXT_EMPTY) {
-      infof(data, CFMSG(cf, "cr_recv: got PLAINTEXT_EMPTY. "
-            "will try again later."));
+      DEBUGF(LOG_CF(data, cf, "cr_recv: got PLAINTEXT_EMPTY. "
+                    "will try again later."));
       backend->data_pending = FALSE;
       break;
     }
+    else if(rresult == RUSTLS_RESULT_UNEXPECTED_EOF) {
+      failf(data, "rustls: peer closed TCP connection "
+        "without first closing TLS connection");
+      *err = CURLE_READ_ERROR;
+      return -1;
+    }
     else if(rresult != RUSTLS_RESULT_OK) {
       /* n always equals 0 in this case, don't need to check it */
-      failf(data, "error in rustls_connection_read: %d", rresult);
+      rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen);
+      failf(data, "rustls_connection_read: %.*s", errorlen, errorbuf);
       *err = CURLE_READ_ERROR;
       return -1;
     }
@@ -207,7 +216,7 @@
       break;
     }
     else {
-      infof(data, CFMSG(cf, "cr_recv: got %ld plain bytes"), n);
+      DEBUGF(LOG_CF(data, cf, "cr_recv: got %ld plain bytes", n));
       plain_bytes_copied += n;
     }
   }
@@ -254,22 +263,25 @@
   size_t tlswritten_total = 0;
   rustls_result rresult;
   rustls_io_result io_error;
+  char errorbuf[256];
+  size_t errorlen;
 
   DEBUGASSERT(backend);
   rconn = backend->conn;
 
-  infof(data, CFMSG(cf, "cr_send: %ld plain bytes"), plainlen);
+  DEBUGF(LOG_CF(data, cf, "cr_send: %ld plain bytes", plainlen));
 
   if(plainlen > 0) {
     rresult = rustls_connection_write(rconn, plainbuf, plainlen,
                                       &plainwritten);
     if(rresult != RUSTLS_RESULT_OK) {
-      failf(data, "error in rustls_connection_write");
+      rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen);
+      failf(data, "rustls_connection_write: %.*s", errorlen, errorbuf);
       *err = CURLE_WRITE_ERROR;
       return -1;
     }
     else if(plainwritten == 0) {
-      failf(data, "EOF in rustls_connection_write");
+      failf(data, "rustls_connection_write: EOF");
       *err = CURLE_WRITE_ERROR;
       return -1;
     }
@@ -282,8 +294,8 @@
     io_error = rustls_connection_write_tls(rconn, write_cb, &io_ctx,
                                            &tlswritten);
     if(io_error == EAGAIN || io_error == EWOULDBLOCK) {
-      infof(data, CFMSG(cf, "cr_send: EAGAIN after %ld bytes"),
-            tlswritten_total);
+      DEBUGF(LOG_CF(data, cf, "cr_send: EAGAIN after %zu bytes",
+                    tlswritten_total));
       *err = CURLE_AGAIN;
       return -1;
     }
@@ -299,7 +311,7 @@
       *err = CURLE_WRITE_ERROR;
       return -1;
     }
-    infof(data, CFMSG(cf, "cr_send: wrote %ld TLS bytes"), tlswritten);
+    DEBUGF(LOG_CF(data, cf, "cr_send: wrote %zu TLS bytes", tlswritten));
     tlswritten_total += tlswritten;
   }
 
@@ -349,22 +361,25 @@
   char errorbuf[256];
   size_t errorlen;
   int result;
-  rustls_slice_bytes alpn[2] = {
-    { (const uint8_t *)ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH },
-    { (const uint8_t *)ALPN_H2, ALPN_H2_LENGTH },
-  };
 
   DEBUGASSERT(backend);
   rconn = backend->conn;
 
   config_builder = rustls_client_config_builder_new();
-#ifdef USE_HTTP2
-  infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
-  rustls_client_config_builder_set_alpn_protocols(config_builder, alpn, 2);
-#else
-  rustls_client_config_builder_set_alpn_protocols(config_builder, alpn, 1);
-#endif
-  infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
+  if(connssl->alpn) {
+    struct alpn_proto_buf proto;
+    rustls_slice_bytes alpn[ALPN_ENTRIES_MAX];
+    size_t i;
+
+    for(i = 0; i < connssl->alpn->count; ++i) {
+      alpn[i].data = (const uint8_t *)connssl->alpn->entries[i];
+      alpn[i].len = strlen(connssl->alpn->entries[i]);
+    }
+    rustls_client_config_builder_set_alpn_protocols(config_builder, alpn,
+                                                    connssl->alpn->count);
+    Curl_alpn_to_proto_str(&proto, connssl->alpn);
+    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data);
+  }
   if(!verifypeer) {
     rustls_client_config_builder_dangerous_set_certificate_verifier(
       config_builder, cr_verify_none);
@@ -384,7 +399,7 @@
     result = rustls_root_cert_store_add_pem(roots, ca_info_blob->data,
                                             ca_info_blob->len, verifypeer);
     if(result != RUSTLS_RESULT_OK) {
-      failf(data, "failed to parse trusted certificates from blob");
+      failf(data, "rustls: failed to parse trusted certificates from blob");
       rustls_root_cert_store_free(roots);
       rustls_client_config_free(
         rustls_client_config_builder_build(config_builder));
@@ -394,7 +409,7 @@
     result = rustls_client_config_builder_use_roots(config_builder, roots);
     rustls_root_cert_store_free(roots);
     if(result != RUSTLS_RESULT_OK) {
-      failf(data, "failed to load trusted certificates");
+      failf(data, "rustls: failed to load trusted certificates");
       rustls_client_config_free(
         rustls_client_config_builder_build(config_builder));
       return CURLE_SSL_CACERT_BADFILE;
@@ -404,7 +419,7 @@
     result = rustls_client_config_builder_load_roots_from_file(
       config_builder, ssl_cafile);
     if(result != RUSTLS_RESULT_OK) {
-      failf(data, "failed to load trusted certificates");
+      failf(data, "rustls: failed to load trusted certificates");
       rustls_client_config_free(
         rustls_client_config_builder_build(config_builder));
       return CURLE_SSL_CACERT_BADFILE;
@@ -416,7 +431,7 @@
   {
     char *snihost = Curl_ssl_snihost(data, hostname, NULL);
     if(!snihost) {
-      failf(data, "Failed to set SNI");
+      failf(data, "rustls: failed to get SNI");
       return CURLE_SSL_CONNECT_ERROR;
     }
     result = rustls_client_connection_new(backend->config, snihost, &rconn);
@@ -439,29 +454,7 @@
   size_t len = 0;
 
   rustls_connection_get_alpn_protocol(rconn, &protocol, &len);
-  if(!protocol) {
-    infof(data, VTLS_INFOF_NO_ALPN);
-    return;
-  }
-
-#ifdef USE_HTTP2
-  if(len == ALPN_H2_LENGTH && 0 == memcmp(ALPN_H2, protocol, len)) {
-    infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, ALPN_H2);
-    cf->conn->alpn = CURL_HTTP_VERSION_2;
-  }
-  else
-#endif
-  if(len == ALPN_HTTP_1_1_LENGTH &&
-      0 == memcmp(ALPN_HTTP_1_1, protocol, len)) {
-    infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, ALPN_HTTP_1_1);
-    cf->conn->alpn = CURL_HTTP_VERSION_1_1;
-  }
-  else {
-    infof(data, "ALPN, negotiated an unrecognized protocol");
-  }
-
-  Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
-                      BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
+  Curl_alpn_set_negotiated(cf, data, protocol, len);
 }
 
 static CURLcode
@@ -469,7 +462,7 @@
                        struct Curl_easy *data, bool *done)
 {
   struct ssl_connect_data *const connssl = cf->ctx;
-  curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+  curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data);
   struct ssl_backend_data *const backend = connssl->backend;
   struct rustls_connection *rconn = NULL;
   CURLcode tmperr = CURLE_OK;
@@ -573,7 +566,7 @@
                     curl_socket_t *socks)
 {
   struct ssl_connect_data *const connssl = cf->ctx;
-  curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+  curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data);
   struct ssl_backend_data *const backend = connssl->backend;
   struct rustls_connection *rconn = NULL;
 
@@ -616,7 +609,7 @@
     rustls_connection_send_close_notify(backend->conn);
     n = cr_send(cf, data, NULL, 0, &tmperr);
     if(n < 0) {
-      failf(data, "error sending close notify: %d", tmperr);
+      failf(data, "rustls: error sending close_notify: %d", tmperr);
     }
 
     rustls_connection_free(backend->conn);
diff --git a/Utilities/cmcurl/lib/vtls/rustls.h b/Utilities/cmcurl/lib/vtls/rustls.h
index 6b393dd..bfbe23d 100644
--- a/Utilities/cmcurl/lib/vtls/rustls.h
+++ b/Utilities/cmcurl/lib/vtls/rustls.h
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2020 - 2022, Jacob Hoffman-Andrews,
+ * Copyright (C) Jacob Hoffman-Andrews,
  * <github@hoffman-andrews.com>
  *
  * This software is licensed as described in the file COPYING, which
diff --git a/Utilities/cmcurl/lib/vtls/schannel.c b/Utilities/cmcurl/lib/vtls/schannel.c
index 7eab954..6f94c7e 100644
--- a/Utilities/cmcurl/lib/vtls/schannel.c
+++ b/Utilities/cmcurl/lib/vtls/schannel.c
@@ -5,9 +5,9 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2012 - 2016, Marc Hoersken, <info@marc-hoersken.de>
- * Copyright (C) 2012, Mark Salisbury, <mark.salisbury@hp.com>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Marc Hoersken, <info@marc-hoersken.de>
+ * Copyright (C) Mark Salisbury, <mark.salisbury@hp.com>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -264,128 +264,133 @@
 
 /* longest is 26, buffer is slightly bigger */
 #define LONGEST_ALG_ID 32
-#define CIPHEROPTION(X)                         \
-  if(strcmp(#X, tmp) == 0)                      \
-    return X
+#define CIPHEROPTION(x) {#x, x}
+
+struct algo {
+  const char *name;
+  int id;
+};
+
+static const struct algo algs[]= {
+  CIPHEROPTION(CALG_MD2),
+  CIPHEROPTION(CALG_MD4),
+  CIPHEROPTION(CALG_MD5),
+  CIPHEROPTION(CALG_SHA),
+  CIPHEROPTION(CALG_SHA1),
+  CIPHEROPTION(CALG_MAC),
+  CIPHEROPTION(CALG_RSA_SIGN),
+  CIPHEROPTION(CALG_DSS_SIGN),
+/* ifdefs for the options that are defined conditionally in wincrypt.h */
+#ifdef CALG_NO_SIGN
+  CIPHEROPTION(CALG_NO_SIGN),
+#endif
+  CIPHEROPTION(CALG_RSA_KEYX),
+  CIPHEROPTION(CALG_DES),
+#ifdef CALG_3DES_112
+  CIPHEROPTION(CALG_3DES_112),
+#endif
+  CIPHEROPTION(CALG_3DES),
+  CIPHEROPTION(CALG_DESX),
+  CIPHEROPTION(CALG_RC2),
+  CIPHEROPTION(CALG_RC4),
+  CIPHEROPTION(CALG_SEAL),
+#ifdef CALG_DH_SF
+  CIPHEROPTION(CALG_DH_SF),
+#endif
+  CIPHEROPTION(CALG_DH_EPHEM),
+#ifdef CALG_AGREEDKEY_ANY
+  CIPHEROPTION(CALG_AGREEDKEY_ANY),
+#endif
+#ifdef CALG_HUGHES_MD5
+  CIPHEROPTION(CALG_HUGHES_MD5),
+#endif
+  CIPHEROPTION(CALG_SKIPJACK),
+#ifdef CALG_TEK
+  CIPHEROPTION(CALG_TEK),
+#endif
+  CIPHEROPTION(CALG_CYLINK_MEK),
+  CIPHEROPTION(CALG_SSL3_SHAMD5),
+#ifdef CALG_SSL3_MASTER
+  CIPHEROPTION(CALG_SSL3_MASTER),
+#endif
+#ifdef CALG_SCHANNEL_MASTER_HASH
+  CIPHEROPTION(CALG_SCHANNEL_MASTER_HASH),
+#endif
+#ifdef CALG_SCHANNEL_MAC_KEY
+  CIPHEROPTION(CALG_SCHANNEL_MAC_KEY),
+#endif
+#ifdef CALG_SCHANNEL_ENC_KEY
+  CIPHEROPTION(CALG_SCHANNEL_ENC_KEY),
+#endif
+#ifdef CALG_PCT1_MASTER
+  CIPHEROPTION(CALG_PCT1_MASTER),
+#endif
+#ifdef CALG_SSL2_MASTER
+  CIPHEROPTION(CALG_SSL2_MASTER),
+#endif
+#ifdef CALG_TLS1_MASTER
+  CIPHEROPTION(CALG_TLS1_MASTER),
+#endif
+#ifdef CALG_RC5
+  CIPHEROPTION(CALG_RC5),
+#endif
+#ifdef CALG_HMAC
+  CIPHEROPTION(CALG_HMAC),
+#endif
+#ifdef CALG_TLS1PRF
+  CIPHEROPTION(CALG_TLS1PRF),
+#endif
+#ifdef CALG_HASH_REPLACE_OWF
+  CIPHEROPTION(CALG_HASH_REPLACE_OWF),
+#endif
+#ifdef CALG_AES_128
+  CIPHEROPTION(CALG_AES_128),
+#endif
+#ifdef CALG_AES_192
+  CIPHEROPTION(CALG_AES_192),
+#endif
+#ifdef CALG_AES_256
+  CIPHEROPTION(CALG_AES_256),
+#endif
+#ifdef CALG_AES
+  CIPHEROPTION(CALG_AES),
+#endif
+#ifdef CALG_SHA_256
+  CIPHEROPTION(CALG_SHA_256),
+#endif
+#ifdef CALG_SHA_384
+  CIPHEROPTION(CALG_SHA_384),
+#endif
+#ifdef CALG_SHA_512
+  CIPHEROPTION(CALG_SHA_512),
+#endif
+#ifdef CALG_ECDH
+  CIPHEROPTION(CALG_ECDH),
+#endif
+#ifdef CALG_ECMQV
+  CIPHEROPTION(CALG_ECMQV),
+#endif
+#ifdef CALG_ECDSA
+  CIPHEROPTION(CALG_ECDSA),
+#endif
+#ifdef CALG_ECDH_EPHEM
+  CIPHEROPTION(CALG_ECDH_EPHEM),
+#endif
+  {NULL, 0},
+};
 
 static int
 get_alg_id_by_name(char *name)
 {
-  char tmp[LONGEST_ALG_ID] = { 0 };
   char *nameEnd = strchr(name, ':');
   size_t n = nameEnd ? (size_t)(nameEnd - name) : strlen(name);
+  int i;
 
-  /* reject too-long alg names */
-  if(n > (LONGEST_ALG_ID - 1))
-    return 0;
-
-  strncpy(tmp, name, n);
-  tmp[n] = 0;
-  CIPHEROPTION(CALG_MD2);
-  CIPHEROPTION(CALG_MD4);
-  CIPHEROPTION(CALG_MD5);
-  CIPHEROPTION(CALG_SHA);
-  CIPHEROPTION(CALG_SHA1);
-  CIPHEROPTION(CALG_MAC);
-  CIPHEROPTION(CALG_RSA_SIGN);
-  CIPHEROPTION(CALG_DSS_SIGN);
-/* ifdefs for the options that are defined conditionally in wincrypt.h */
-#ifdef CALG_NO_SIGN
-  CIPHEROPTION(CALG_NO_SIGN);
-#endif
-  CIPHEROPTION(CALG_RSA_KEYX);
-  CIPHEROPTION(CALG_DES);
-#ifdef CALG_3DES_112
-  CIPHEROPTION(CALG_3DES_112);
-#endif
-  CIPHEROPTION(CALG_3DES);
-  CIPHEROPTION(CALG_DESX);
-  CIPHEROPTION(CALG_RC2);
-  CIPHEROPTION(CALG_RC4);
-  CIPHEROPTION(CALG_SEAL);
-#ifdef CALG_DH_SF
-  CIPHEROPTION(CALG_DH_SF);
-#endif
-  CIPHEROPTION(CALG_DH_EPHEM);
-#ifdef CALG_AGREEDKEY_ANY
-  CIPHEROPTION(CALG_AGREEDKEY_ANY);
-#endif
-#ifdef CALG_HUGHES_MD5
-  CIPHEROPTION(CALG_HUGHES_MD5);
-#endif
-  CIPHEROPTION(CALG_SKIPJACK);
-#ifdef CALG_TEK
-  CIPHEROPTION(CALG_TEK);
-#endif
-  CIPHEROPTION(CALG_CYLINK_MEK);
-  CIPHEROPTION(CALG_SSL3_SHAMD5);
-#ifdef CALG_SSL3_MASTER
-  CIPHEROPTION(CALG_SSL3_MASTER);
-#endif
-#ifdef CALG_SCHANNEL_MASTER_HASH
-  CIPHEROPTION(CALG_SCHANNEL_MASTER_HASH);
-#endif
-#ifdef CALG_SCHANNEL_MAC_KEY
-  CIPHEROPTION(CALG_SCHANNEL_MAC_KEY);
-#endif
-#ifdef CALG_SCHANNEL_ENC_KEY
-  CIPHEROPTION(CALG_SCHANNEL_ENC_KEY);
-#endif
-#ifdef CALG_PCT1_MASTER
-  CIPHEROPTION(CALG_PCT1_MASTER);
-#endif
-#ifdef CALG_SSL2_MASTER
-  CIPHEROPTION(CALG_SSL2_MASTER);
-#endif
-#ifdef CALG_TLS1_MASTER
-  CIPHEROPTION(CALG_TLS1_MASTER);
-#endif
-#ifdef CALG_RC5
-  CIPHEROPTION(CALG_RC5);
-#endif
-#ifdef CALG_HMAC
-  CIPHEROPTION(CALG_HMAC);
-#endif
-#ifdef CALG_TLS1PRF
-  CIPHEROPTION(CALG_TLS1PRF);
-#endif
-#ifdef CALG_HASH_REPLACE_OWF
-  CIPHEROPTION(CALG_HASH_REPLACE_OWF);
-#endif
-#ifdef CALG_AES_128
-  CIPHEROPTION(CALG_AES_128);
-#endif
-#ifdef CALG_AES_192
-  CIPHEROPTION(CALG_AES_192);
-#endif
-#ifdef CALG_AES_256
-  CIPHEROPTION(CALG_AES_256);
-#endif
-#ifdef CALG_AES
-  CIPHEROPTION(CALG_AES);
-#endif
-#ifdef CALG_SHA_256
-  CIPHEROPTION(CALG_SHA_256);
-#endif
-#ifdef CALG_SHA_384
-  CIPHEROPTION(CALG_SHA_384);
-#endif
-#ifdef CALG_SHA_512
-  CIPHEROPTION(CALG_SHA_512);
-#endif
-#ifdef CALG_ECDH
-  CIPHEROPTION(CALG_ECDH);
-#endif
-#ifdef CALG_ECMQV
-  CIPHEROPTION(CALG_ECMQV);
-#endif
-#ifdef CALG_ECDSA
-  CIPHEROPTION(CALG_ECDSA);
-#endif
-#ifdef CALG_ECDH_EPHEM
-  CIPHEROPTION(CALG_ECDH_EPHEM);
-#endif
-  return 0;
+  for(i = 0; algs[i].name; i++) {
+    if((n == strlen(algs[i].name) && !strncmp(algs[i].name, name, n)))
+      return algs[i].id;
+  }
+  return 0; /* not found */
 }
 
 #define NUM_CIPHERS 47 /* There are 47 options listed above */
@@ -1105,7 +1110,7 @@
 #ifdef HAS_ALPN
   /* ALPN is only supported on Windows 8.1 / Server 2012 R2 and above.
      Also it doesn't seem to be supported for Wine, see curl bug #983. */
-  backend->use_alpn = cf->conn->bits.tls_enable_alpn &&
+  backend->use_alpn = connssl->alpn &&
     !GetProcAddress(GetModuleHandle(TEXT("ntdll")),
                     "wine_get_version") &&
     curlx_verify_windows_version(6, 3, 0, PLATFORM_WINNT,
@@ -1196,44 +1201,44 @@
     int list_start_index = 0;
     unsigned int *extension_len = NULL;
     unsigned short* list_len = NULL;
+    struct alpn_proto_buf proto;
 
     /* The first four bytes will be an unsigned int indicating number
        of bytes of data in the rest of the buffer. */
     extension_len = (unsigned int *)(void *)(&alpn_buffer[cur]);
-    cur += sizeof(unsigned int);
+    cur += (int)sizeof(unsigned int);
 
     /* The next four bytes are an indicator that this buffer will contain
        ALPN data, as opposed to NPN, for example. */
     *(unsigned int *)(void *)&alpn_buffer[cur] =
       SecApplicationProtocolNegotiationExt_ALPN;
-    cur += sizeof(unsigned int);
+    cur += (int)sizeof(unsigned int);
 
     /* The next two bytes will be an unsigned short indicating the number
        of bytes used to list the preferred protocols. */
     list_len = (unsigned short*)(void *)(&alpn_buffer[cur]);
-    cur += sizeof(unsigned short);
+    cur += (int)sizeof(unsigned short);
 
     list_start_index = cur;
 
-#ifdef USE_HTTP2
-    if(data->state.httpwant >= CURL_HTTP_VERSION_2) {
-      alpn_buffer[cur++] = ALPN_H2_LENGTH;
-      memcpy(&alpn_buffer[cur], ALPN_H2, ALPN_H2_LENGTH);
-      cur += ALPN_H2_LENGTH;
-      infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
+    result = Curl_alpn_to_proto_buf(&proto, connssl->alpn);
+    if(result) {
+      failf(data, "Error setting ALPN");
+      return CURLE_SSL_CONNECT_ERROR;
     }
-#endif
-
-    alpn_buffer[cur++] = ALPN_HTTP_1_1_LENGTH;
-    memcpy(&alpn_buffer[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH);
-    cur += ALPN_HTTP_1_1_LENGTH;
-    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
+    memcpy(&alpn_buffer[cur], proto.data, proto.len);
+    cur += proto.len;
 
     *list_len = curlx_uitous(cur - list_start_index);
-    *extension_len = *list_len + sizeof(unsigned int) + sizeof(unsigned short);
+    *extension_len = *list_len +
+      (unsigned short)sizeof(unsigned int) +
+      (unsigned short)sizeof(unsigned short);
 
     InitSecBuffer(&inbuf, SECBUFFER_APPLICATION_PROTOCOLS, alpn_buffer, cur);
     InitSecBufferDesc(&inbuf_desc, &inbuf, 1);
+
+    Curl_alpn_to_proto_str(&proto, connssl->alpn);
+    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data);
   }
   else {
     InitSecBuffer(&inbuf, SECBUFFER_EMPTY, NULL, 0);
@@ -1727,40 +1732,23 @@
 
     if(alpn_result.ProtoNegoStatus ==
        SecApplicationProtocolNegotiationStatus_Success) {
-      unsigned char alpn = 0;
+      unsigned char prev_alpn = cf->conn->alpn;
 
-      infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR,
-            alpn_result.ProtocolIdSize, alpn_result.ProtocolId);
-
-#ifdef USE_HTTP2
-      if(alpn_result.ProtocolIdSize == ALPN_H2_LENGTH &&
-         !memcmp(ALPN_H2, alpn_result.ProtocolId, ALPN_H2_LENGTH)) {
-        alpn = CURL_HTTP_VERSION_2;
-      }
-      else
-#endif
-        if(alpn_result.ProtocolIdSize == ALPN_HTTP_1_1_LENGTH &&
-           !memcmp(ALPN_HTTP_1_1, alpn_result.ProtocolId,
-                   ALPN_HTTP_1_1_LENGTH)) {
-          alpn = CURL_HTTP_VERSION_1_1;
-        }
+      Curl_alpn_set_negotiated(cf, data, alpn_result.ProtocolId,
+                               alpn_result.ProtocolIdSize);
       if(backend->recv_renegotiating) {
-        if(alpn != cf->conn->alpn) {
+        if(prev_alpn != cf->conn->alpn &&
+           prev_alpn != CURL_HTTP_VERSION_NONE) {
+          /* Renegotiation selected a different protocol now, we cannot
+           * deal with this */
           failf(data, "schannel: server selected an ALPN protocol too late");
           return CURLE_SSL_CONNECT_ERROR;
         }
       }
-      else
-        cf->conn->alpn = alpn;
     }
     else {
       if(!backend->recv_renegotiating)
-        infof(data, VTLS_INFOF_NO_ALPN);
-    }
-
-    if(!backend->recv_renegotiating) {
-      Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
-                          BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
+        Curl_alpn_set_negotiated(cf, data, NULL, 0);
     }
   }
 #endif
@@ -1841,7 +1829,7 @@
 {
   CURLcode result;
   struct ssl_connect_data *connssl = cf->ctx;
-  curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+  curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data);
   timediff_t timeout_ms;
   int what;
 
@@ -2056,7 +2044,7 @@
       }
       else if(!timeout_ms)
         timeout_ms = TIMEDIFF_T_MAX;
-      what = SOCKET_WRITABLE(cf->conn->sock[cf->sockindex], timeout_ms);
+      what = SOCKET_WRITABLE(Curl_conn_cf_get_socket(cf, data), timeout_ms);
       if(what < 0) {
         /* fatal error */
         failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
diff --git a/Utilities/cmcurl/lib/vtls/schannel.h b/Utilities/cmcurl/lib/vtls/schannel.h
index 6d4235a..7fae39f 100644
--- a/Utilities/cmcurl/lib/vtls/schannel.h
+++ b/Utilities/cmcurl/lib/vtls/schannel.h
@@ -7,8 +7,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012, Marc Hoersken, <info@marc-hoersken.de>, et al.
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Marc Hoersken, <info@marc-hoersken.de>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -179,8 +179,8 @@
   size_t encdata_offset, decdata_offset;
   unsigned char *encdata_buffer, *decdata_buffer;
   /* encdata_is_incomplete: if encdata contains only a partial record that
-     can't be decrypted without another Curl_read_plain (that is, status is
-     SEC_E_INCOMPLETE_MESSAGE) then set this true. after Curl_read_plain writes
+     can't be decrypted without another recv() (that is, status is
+     SEC_E_INCOMPLETE_MESSAGE) then set this true. after an recv() adds
      more bytes into encdata then set this back to false. */
   bool encdata_is_incomplete;
   unsigned long req_flags, ret_flags;
diff --git a/Utilities/cmcurl/lib/vtls/schannel_verify.c b/Utilities/cmcurl/lib/vtls/schannel_verify.c
index e499216..d75ee8d 100644
--- a/Utilities/cmcurl/lib/vtls/schannel_verify.c
+++ b/Utilities/cmcurl/lib/vtls/schannel_verify.c
@@ -5,9 +5,9 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012 - 2016, Marc Hoersken, <info@marc-hoersken.de>
- * Copyright (C) 2012, Mark Salisbury, <mark.salisbury@hp.com>
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Marc Hoersken, <info@marc-hoersken.de>
+ * Copyright (C) Mark Salisbury, <mark.salisbury@hp.com>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vtls/sectransp.c b/Utilities/cmcurl/lib/vtls/sectransp.c
index d903c53..7f55fb5 100644
--- a/Utilities/cmcurl/lib/vtls/sectransp.c
+++ b/Utilities/cmcurl/lib/vtls/sectransp.c
@@ -5,8 +5,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2012 - 2017, Nick Zitzmann, <nickzman@gmail.com>.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Nick Zitzmann, <nickzman@gmail.com>.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -137,14 +137,6 @@
 #include "memdebug.h"
 
 
-#define DEBUG_CF 0
-
-#if DEBUG_CF
-#define CF_DEBUGF(x) x
-#else
-#define CF_DEBUGF(x) do { } while(0)
-#endif
-
 /* From MacTypes.h (which we can't include because it isn't present in iOS: */
 #define ioErr -36
 #define paramErr -50
@@ -840,15 +832,15 @@
   struct Curl_cfilter *cf = (struct Curl_cfilter *)connection;
   struct ssl_connect_data *connssl = cf->ctx;
   struct ssl_backend_data *backend = connssl->backend;
-  struct Curl_easy *data = connssl->call_data;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
   ssize_t nread;
   CURLcode result;
   OSStatus rtn = noErr;
 
   DEBUGASSERT(data);
   nread = Curl_conn_cf_recv(cf->next, data, buf, *dataLength, &result);
-  CF_DEBUGF(infof(data, CFMSG(cf, "bio_read(len=%zu) -> %zd, result=%d"),
-            *dataLength, nread, result));
+  DEBUGF(LOG_CF(data, cf, "bio_read(len=%zu) -> %zd, result=%d",
+                *dataLength, nread, result));
   if(nread < 0) {
     switch(result) {
       case CURLE_OK:
@@ -876,15 +868,15 @@
   struct Curl_cfilter *cf = (struct Curl_cfilter *)connection;
   struct ssl_connect_data *connssl = cf->ctx;
   struct ssl_backend_data *backend = connssl->backend;
-  struct Curl_easy *data = connssl->call_data;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
   ssize_t nwritten;
   CURLcode result;
   OSStatus rtn = noErr;
 
   DEBUGASSERT(data);
   nwritten = Curl_conn_cf_send(cf->next, data, buf, *dataLength, &result);
-  CF_DEBUGF(infof(data, CFMSG(cf, "bio_send(len=%zu) -> %zd, result=%d"),
-            *dataLength, nwritten, result));
+  DEBUGF(LOG_CF(data, cf, "bio_send(len=%zu) -> %zd, result=%d",
+                *dataLength, nwritten, result));
   if(nwritten <= 0) {
     if(result == CURLE_AGAIN) {
       rtn = errSSLWouldBlock;
@@ -1644,7 +1636,6 @@
   const bool verifypeer = conn_config->verifypeer;
   char * const ssl_cert = ssl_config->primary.clientcert;
   const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob;
-  bool isproxy = Curl_ssl_cf_is_proxy(cf);
 #ifdef ENABLE_IPV6
   struct in6_addr addr;
 #else
@@ -1657,7 +1648,7 @@
 
   DEBUGASSERT(backend);
 
-  CF_DEBUGF(infof(data, CFMSG(cf, "connect_step1")));
+  DEBUGF(LOG_CF(data, cf, "connect_step1"));
   GetDarwinVersionNumber(&darwinver_maj, &darwinver_min);
 #endif /* CURL_BUILD_MAC */
 
@@ -1805,33 +1796,28 @@
 #endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */
 
 #if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1
-  if(cf->conn->bits.tls_enable_alpn) {
+  if(connssl->alpn) {
     if(__builtin_available(macOS 10.13.4, iOS 11, tvOS 11, *)) {
+      struct alpn_proto_buf proto;
+      size_t i;
+      CFStringRef cstr;
       CFMutableArrayRef alpnArr = CFArrayCreateMutable(NULL, 0,
                                                        &kCFTypeArrayCallBacks);
-
-#ifdef USE_HTTP2
-      if(data->state.httpwant >= CURL_HTTP_VERSION_2
-#ifndef CURL_DISABLE_PROXY
-         && (!isproxy || !cf->conn->bits.tunnel_proxy)
-#endif
-        ) {
-        CFArrayAppendValue(alpnArr, CFSTR(ALPN_H2));
-        infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
+      for(i = 0; i < connssl->alpn->count; ++i) {
+        cstr = CFStringCreateWithCString(NULL, connssl->alpn->entries[i],
+                                         kCFStringEncodingUTF8);
+        if(!cstr)
+          return CURLE_OUT_OF_MEMORY;
+        CFArrayAppendValue(alpnArr, cstr);
+        CFRelease(cstr);
       }
-#endif
-
-      CFArrayAppendValue(alpnArr, CFSTR(ALPN_HTTP_1_1));
-      infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
-
-      /* expects length prefixed preference ordered list of protocols in wire
-       * format
-       */
       err = SSLSetALPNProtocols(backend->ssl_ctx, alpnArr);
       if(err != noErr)
         infof(data, "WARNING: failed to set ALPN protocols; OSStatus %d",
               err);
       CFRelease(alpnArr);
+      Curl_alpn_to_proto_str(&proto, connssl->alpn);
+      infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data);
     }
   }
 #endif
@@ -2164,50 +2150,39 @@
   return sep_end - in;
 }
 
+#define MAX_CERTS_SIZE (50*1024*1024) /* arbitrary - to catch mistakes */
+
 static int read_cert(const char *file, unsigned char **out, size_t *outlen)
 {
   int fd;
-  ssize_t n, len = 0, cap = 512;
-  unsigned char buf[512], *data;
+  ssize_t n;
+  unsigned char buf[512];
+  struct dynbuf certs;
+
+  Curl_dyn_init(&certs, MAX_CERTS_SIZE);
 
   fd = open(file, 0);
   if(fd < 0)
     return -1;
 
-  data = malloc(cap);
-  if(!data) {
-    close(fd);
-    return -1;
-  }
-
   for(;;) {
     n = read(fd, buf, sizeof(buf));
+    if(!n)
+      break;
     if(n < 0) {
       close(fd);
-      free(data);
+      Curl_dyn_free(&certs);
       return -1;
     }
-    else if(n == 0) {
+    if(Curl_dyn_addn(&certs, buf, n)) {
       close(fd);
-      break;
+      return -1;
     }
-
-    if(len + n >= cap) {
-      cap *= 2;
-      data = Curl_saferealloc(data, cap);
-      if(!data) {
-        close(fd);
-        return -1;
-      }
-    }
-
-    memcpy(data + len, buf, n);
-    len += n;
   }
-  data[len] = '\0';
+  close(fd);
 
-  *out = data;
-  *outlen = len;
+  *out = Curl_dyn_uptr(&certs);
+  *outlen = Curl_dyn_len(&certs);
 
   return 0;
 }
@@ -2216,16 +2191,18 @@
                                 const unsigned char *buf, size_t buflen,
                                 CFMutableArrayRef array)
 {
-    CFDataRef certdata = CFDataCreate(kCFAllocatorDefault, buf, buflen);
     char *certp;
     CURLcode result;
+    SecCertificateRef cacert;
+    CFDataRef certdata;
+
+    certdata = CFDataCreate(kCFAllocatorDefault, buf, buflen);
     if(!certdata) {
       failf(data, "SSL: failed to allocate array for CA certificate");
       return CURLE_OUT_OF_MEMORY;
     }
 
-    SecCertificateRef cacert =
-      SecCertificateCreateWithData(kCFAllocatorDefault, certdata);
+    cacert = SecCertificateCreateWithData(kCFAllocatorDefault, certdata);
     CFRelease(certdata);
     if(!cacert) {
       failf(data, "SSL: failed to create SecCertificate from CA certificate");
@@ -2302,7 +2279,7 @@
       /* This is not a PEM file, probably a certificate in DER format. */
       rc = append_cert_to_array(data, certbuf, buflen, array);
       if(rc != CURLE_OK) {
-        CF_DEBUGF(infof(data, CFMSG(cf, "append_cert for CA failed")));
+        DEBUGF(LOG_CF(data, cf, "append_cert for CA failed"));
         result = rc;
         goto out;
       }
@@ -2316,7 +2293,7 @@
     rc = append_cert_to_array(data, der, derlen, array);
     free(der);
     if(rc != CURLE_OK) {
-      CF_DEBUGF(infof(data, CFMSG(cf, "append_cert for CA failed")));
+      DEBUGF(LOG_CF(data, cf, "append_cert for CA failed"));
       result = rc;
       goto out;
     }
@@ -2332,7 +2309,7 @@
     goto out;
   }
 
-  CF_DEBUGF(infof(data, CFMSG(cf, "setting %d trust anchors"), n));
+  DEBUGF(LOG_CF(data, cf, "setting %d trust anchors", n));
   ret = SecTrustSetAnchorCertificates(trust, array);
   if(ret != noErr) {
     failf(data, "SecTrustSetAnchorCertificates() returned error %d", ret);
@@ -2354,11 +2331,11 @@
   switch(trust_eval) {
     case kSecTrustResultUnspecified:
       /* what does this really mean? */
-      CF_DEBUGF(infof(data, CFMSG(cf, "trust result: Unspecified")));
+      DEBUGF(LOG_CF(data, cf, "trust result: Unspecified"));
       result = CURLE_OK;
       goto out;
     case kSecTrustResultProceed:
-      CF_DEBUGF(infof(data, CFMSG(cf, "trust result: Proceed")));
+      DEBUGF(LOG_CF(data, cf, "trust result: Proceed"));
       result = CURLE_OK;
       goto out;
 
@@ -2391,7 +2368,7 @@
   size_t buflen;
 
   if(ca_info_blob) {
-    CF_DEBUGF(infof(data, CFMSG(cf, "verify_peer, CA from config blob")));
+    DEBUGF(LOG_CF(data, cf, "verify_peer, CA from config blob"));
     certbuf = (unsigned char *)malloc(ca_info_blob->len + 1);
     if(!certbuf) {
       return CURLE_OUT_OF_MEMORY;
@@ -2401,8 +2378,7 @@
     certbuf[ca_info_blob->len]='\0';
   }
   else if(cafile) {
-    CF_DEBUGF(infof(data, CFMSG(cf, "verify_peer, CA from file '%s'"),
-              cafile));
+    DEBUGF(LOG_CF(data, cf, "verify_peer, CA from file '%s'", cafile));
     if(read_cert(cafile, &certbuf, &buflen) < 0) {
       failf(data, "SSL: failed to read or invalid CA certificate");
       return CURLE_SSL_CACERT_BADFILE;
@@ -2440,11 +2416,15 @@
 
   do {
     SecTrustRef trust;
-    OSStatus ret = SSLCopyPeerTrust(ctx, &trust);
+    OSStatus ret;
+    SecKeyRef keyRef;
+    OSStatus success;
+
+    ret = SSLCopyPeerTrust(ctx, &trust);
     if(ret != noErr || !trust)
       break;
 
-    SecKeyRef keyRef = SecTrustCopyPublicKey(trust);
+    keyRef = SecTrustCopyPublicKey(trust);
     CFRelease(trust);
     if(!keyRef)
       break;
@@ -2458,8 +2438,8 @@
 
 #elif SECTRANSP_PINNEDPUBKEY_V2
 
-    OSStatus success = SecItemExport(keyRef, kSecFormatOpenSSL, 0, NULL,
-                                     &publicKeyBits);
+    success = SecItemExport(keyRef, kSecFormatOpenSSL, 0, NULL,
+                            &publicKeyBits);
     CFRelease(keyRef);
     if(success != errSecSuccess || !publicKeyBits)
       break;
@@ -2538,7 +2518,7 @@
               || ssl_connect_2_reading == connssl->connecting_state
               || ssl_connect_2_writing == connssl->connecting_state);
   DEBUGASSERT(backend);
-  CF_DEBUGF(infof(data, CFMSG(cf, "connect_step2")));
+  DEBUGF(LOG_CF(data, cf, "connect_step2"));
 
   /* Here goes nothing: */
 check_handshake:
@@ -3002,12 +2982,13 @@
                                         struct Curl_easy *data)
 {
   struct ssl_connect_data *connssl = cf->ctx;
+  CURLcode result;
 
-  CF_DEBUGF(infof(data, CFMSG(cf, "connect_step3")));
+  DEBUGF(LOG_CF(data, cf, "connect_step3"));
   /* There is no step 3!
    * Well, okay, let's collect server certificates, and if verbose mode is on,
    * let's print the details of the server certificates. */
-  const CURLcode result = collect_server_cert(cf, data);
+  result = collect_server_cert(cf, data);
   if(result)
     return result;
 
@@ -3022,7 +3003,7 @@
 {
   CURLcode result;
   struct ssl_connect_data *connssl = cf->ctx;
-  curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+  curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data);
   int what;
 
   /* check if the connection has already been established */
@@ -3112,7 +3093,7 @@
   }
 
   if(ssl_connect_done == connssl->connecting_state) {
-    CF_DEBUGF(infof(data, CFMSG(cf, "connected")));
+    DEBUGF(LOG_CF(data, cf, "connected"));
     connssl->state = ssl_connection_complete;
     *done = TRUE;
   }
@@ -3158,7 +3139,7 @@
   DEBUGASSERT(backend);
 
   if(backend->ssl_ctx) {
-    CF_DEBUGF(infof(data, CFMSG(cf, "close")));
+    DEBUGF(LOG_CF(data, cf, "close"));
     (void)SSLClose(backend->ssl_ctx);
 #if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS
     if(SSLCreateContext)
@@ -3200,9 +3181,10 @@
 
   rc = 0;
 
-  what = SOCKET_READABLE(cf->conn->sock[cf->sockindex], SSL_SHUTDOWN_TIMEOUT);
+  what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data),
+                         SSL_SHUTDOWN_TIMEOUT);
 
-  CF_DEBUGF(infof(data, CFMSG(cf, "shutdown")));
+  DEBUGF(LOG_CF(data, cf, "shutdown"));
   while(loop--) {
     if(what < 0) {
       /* anything that gets here is fatally bad */
@@ -3229,7 +3211,7 @@
     if(nread <= 0)
       break;
 
-    what = SOCKET_READABLE(cf->conn->sock[cf->sockindex], 0);
+    what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), 0);
   }
 
   return rc;
@@ -3251,35 +3233,6 @@
   return msnprintf(buffer, size, "SecureTransport");
 }
 
-/*
- * This function uses SSLGetSessionState to determine connection status.
- *
- * Return codes:
- *     1 means the connection is still in place
- *     0 means the connection has been closed
- *    -1 means the connection status is unknown
- */
-static int sectransp_check_cxn(struct Curl_cfilter *cf,
-                               struct Curl_easy *data)
-{
-  struct ssl_connect_data *connssl = cf->ctx;
-  struct ssl_backend_data *backend = connssl->backend;
-  OSStatus err;
-  SSLSessionState state;
-
-  (void)data;
-  DEBUGASSERT(backend);
-
-  if(backend->ssl_ctx) {
-    CF_DEBUGF(infof(data, CFMSG(cf, "check connection")));
-    err = SSLGetSessionState(backend->ssl_ctx, &state);
-    if(err == noErr)
-      return state == kSSLConnected || state == kSSLHandshake;
-    return -1;
-  }
-  return 0;
-}
-
 static bool sectransp_data_pending(struct Curl_cfilter *cf,
                                    const struct Curl_easy *data)
 {
@@ -3292,7 +3245,7 @@
   DEBUGASSERT(backend);
 
   if(backend->ssl_ctx) {  /* SSL is in use */
-    CF_DEBUGF(infof(data, CFMSG(cf, "data_pending")));
+    DEBUGF(LOG_CF((struct Curl_easy *)data, cf, "data_pending"));
     err = SSLGetBufferedReadSize(backend->ssl_ctx, &buffer);
     if(err == noErr)
       return buffer > 0UL;
@@ -3424,13 +3377,15 @@
   DEBUGASSERT(backend);
 
   again:
+  *curlcode = CURLE_OK;
   err = SSLRead(backend->ssl_ctx, buf, buffersize, &processed);
 
   if(err != noErr) {
     switch(err) {
       case errSSLWouldBlock:  /* return how much we read (if anything) */
-        if(processed)
+        if(processed) {
           return (ssize_t)processed;
+        }
         *curlcode = CURLE_AGAIN;
         return -1L;
         break;
@@ -3442,7 +3397,7 @@
       case errSSLClosedGraceful:
       case errSSLClosedNoNotify:
         *curlcode = CURLE_OK;
-        return -1L;
+        return 0;
         break;
 
         /* The below is errSSLPeerAuthCompleted; it's not defined in
@@ -3453,8 +3408,10 @@
           CURLcode result = verify_cert(cf, data, conn_config->CAfile,
                                         conn_config->ca_info_blob,
                                         backend->ssl_ctx);
-          if(result)
-            return result;
+          if(result) {
+            *curlcode = result;
+            return -1;
+          }
         }
         goto again;
       default:
@@ -3491,7 +3448,7 @@
   Curl_none_init,                     /* init */
   Curl_none_cleanup,                  /* cleanup */
   sectransp_version,                  /* version */
-  sectransp_check_cxn,                /* check_cxn */
+  Curl_none_check_cxn,                /* check_cxn */
   sectransp_shutdown,                 /* shutdown */
   sectransp_data_pending,             /* data_pending */
   sectransp_random,                   /* random */
diff --git a/Utilities/cmcurl/lib/vtls/sectransp.h b/Utilities/cmcurl/lib/vtls/sectransp.h
index 2d53b7c..0f1085a 100644
--- a/Utilities/cmcurl/lib/vtls/sectransp.h
+++ b/Utilities/cmcurl/lib/vtls/sectransp.h
@@ -7,8 +7,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012 - 2014, Nick Zitzmann, <nickzman@gmail.com>.
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Nick Zitzmann, <nickzman@gmail.com>.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vtls/vtls.c b/Utilities/cmcurl/lib/vtls/vtls.c
index 873ee6b..144e6ee 100644
--- a/Utilities/cmcurl/lib/vtls/vtls.c
+++ b/Utilities/cmcurl/lib/vtls/vtls.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -73,6 +73,7 @@
 #include "curl_memory.h"
 #include "memdebug.h"
 
+
 /* convenience macro to check if this handle is using a shared SSL session */
 #define SSLSESSION_SHARED(data) (data->share &&                        \
                                  (data->share->specifier &             \
@@ -150,7 +151,6 @@
 #ifdef USE_TLS_SRP
      !Curl_timestrcmp(data->username, needle->username) &&
      !Curl_timestrcmp(data->password, needle->password) &&
-     (data->authtype == needle->authtype) &&
 #endif
      strcasecompare(data->cipher_list, needle->cipher_list) &&
      strcasecompare(data->cipher_list13, needle->cipher_list13) &&
@@ -173,9 +173,6 @@
   dest->verifystatus = source->verifystatus;
   dest->sessionid = source->sessionid;
   dest->ssl_options = source->ssl_options;
-#ifdef USE_TLS_SRP
-  dest->authtype = source->authtype;
-#endif
 
   CLONE_BLOB(cert_blob);
   CLONE_BLOB(ca_info_blob);
@@ -272,8 +269,8 @@
 static bool ssl_prefs_check(struct Curl_easy *data)
 {
   /* check for CURLOPT_SSLVERSION invalid parameter value */
-  const long sslver = data->set.ssl.primary.version;
-  if((sslver < 0) || (sslver >= CURL_SSLVERSION_LAST)) {
+  const unsigned char sslver = data->set.ssl.primary.version;
+  if(sslver >= CURL_SSLVERSION_LAST) {
     failf(data, "Unrecognized parameter value passed via CURLOPT_SSLVERSION");
     return FALSE;
   }
@@ -293,7 +290,8 @@
   return TRUE;
 }
 
-static struct ssl_connect_data *cf_ctx_new(struct Curl_easy *data)
+static struct ssl_connect_data *cf_ctx_new(struct Curl_easy *data,
+                                           const struct alpn_spec *alpn)
 {
   struct ssl_connect_data *ctx;
 
@@ -302,6 +300,7 @@
   if(!ctx)
     return NULL;
 
+  ctx->alpn = alpn;
   ctx->backend = calloc(1, Curl_ssl->sizeof_ssl_backend_data);
   if(!ctx->backend) {
     free(ctx);
@@ -318,13 +317,6 @@
   }
 }
 
-static void cf_ctx_set_data(struct Curl_cfilter *cf,
-                            struct Curl_easy *data)
-{
-  if(cf->ctx)
-    ((struct ssl_connect_data *)cf->ctx)->call_data = data;
-}
-
 static CURLcode ssl_connect(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
   struct ssl_connect_data *connssl = cf->ctx;
@@ -339,7 +331,6 @@
   result = Curl_ssl->connect_blocking(cf, data);
 
   if(!result) {
-    Curl_pgrsTime(data, TIMER_APPCONNECT); /* SSL is connected */
     DEBUGASSERT(connssl->state == ssl_connection_complete);
   }
 
@@ -615,19 +606,20 @@
                               curl_socket_t *socks)
 {
   struct ssl_connect_data *connssl = cf->ctx;
+  curl_socket_t sock = Curl_conn_cf_get_socket(cf->next, data);
 
-  (void)data;
-  if(connssl->connecting_state == ssl_connect_2_writing) {
-    /* write mode */
-    socks[0] = cf->conn->sock[FIRSTSOCKET];
-    return GETSOCK_WRITESOCK(0);
+  if(sock != CURL_SOCKET_BAD) {
+    if(connssl->connecting_state == ssl_connect_2_writing) {
+      /* write mode */
+      socks[0] = sock;
+      return GETSOCK_WRITESOCK(0);
+    }
+    if(connssl->connecting_state == ssl_connect_2_reading) {
+      /* read mode */
+      socks[0] = sock;
+      return GETSOCK_READSOCK(0);
+    }
   }
-  if(connssl->connecting_state == ssl_connect_2_reading) {
-    /* read mode */
-    socks[0] = cf->conn->sock[FIRSTSOCKET];
-    return GETSOCK_READSOCK(0);
-  }
-
   return GETSOCK_BLANK;
 }
 
@@ -685,20 +677,6 @@
 #endif
 }
 
-/*
- * This function tries to determine connection status.
- *
- * Return codes:
- *     1 means the connection is still in place
- *     0 means the connection has been closed
- *    -1 means the connection status is unknown
- */
-int Curl_ssl_check_cxn(struct Curl_easy *data, struct connectdata *conn)
-{
-  struct Curl_cfilter *cf = Curl_ssl_cf_get_ssl(conn->cfilter[FIRSTSOCKET]);
-  return cf? Curl_ssl->check_cxn(cf, data) : -1;
-}
-
 void Curl_ssl_free_certinfo(struct Curl_easy *data)
 {
   struct curl_certinfo *ci = &data->info.certs;
@@ -1430,53 +1408,80 @@
 
 #ifdef USE_SSL
 
+static void free_hostname(struct ssl_connect_data *connssl)
+{
+  if(connssl->dispname != connssl->hostname)
+    free(connssl->dispname);
+  free(connssl->hostname);
+  connssl->hostname = connssl->dispname = NULL;
+}
+
 static void cf_close(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
   struct ssl_connect_data *connssl = cf->ctx;
-  /* TODO: close_one closes BOTH conn->ssl AND conn->proxy_ssl for this
-   * sockindex (if in use). Gladly, it is safe to call more than once. */
   if(connssl) {
     Curl_ssl->close(cf, data);
     connssl->state = ssl_connection_none;
+    free_hostname(connssl);
   }
   cf->connected = FALSE;
 }
 
-static void reinit_hostname(struct Curl_cfilter *cf)
+static CURLcode reinit_hostname(struct Curl_cfilter *cf)
 {
   struct ssl_connect_data *connssl = cf->ctx;
+  const char *ehostname, *edispname;
+  int eport;
 
+  /* We need the hostname for SNI negotiation. Once handshaked, this
+   * remains the SNI hostname for the TLS connection. But when the
+   * connection is reused, the settings in cf->conn might change.
+   * So we keep a copy of the hostname we use for SNI.
+   */
 #ifndef CURL_DISABLE_PROXY
   if(Curl_ssl_cf_is_proxy(cf)) {
-    /* TODO: there is not definition for a proxy setup on a secondary conn */
-    connssl->hostname = cf->conn->http_proxy.host.name;
-    connssl->dispname = cf->conn->http_proxy.host.dispname;
-    connssl->port = cf->conn->http_proxy.port;
+    ehostname = cf->conn->http_proxy.host.name;
+    edispname = cf->conn->http_proxy.host.dispname;
+    eport = cf->conn->http_proxy.port;
   }
   else
 #endif
   {
-    /* TODO: secondaryhostname is set to the IP address we connect to
-     * in the FTP handler, it is assumed that host verification uses the
-     * hostname from FIRSTSOCKET */
-    if(cf->sockindex == SECONDARYSOCKET && 0) {
-      connssl->hostname = cf->conn->secondaryhostname;
-      connssl->dispname = connssl->hostname;
-      connssl->port = cf->conn->secondary_port;
+    ehostname = cf->conn->host.name;
+    edispname = cf->conn->host.dispname;
+    eport = cf->conn->remote_port;
+  }
+
+  /* change if ehostname changed */
+  if(ehostname && (!connssl->hostname
+                   || strcmp(ehostname, connssl->hostname))) {
+    free_hostname(connssl);
+    connssl->hostname = strdup(ehostname);
+    if(!connssl->hostname) {
+      free_hostname(connssl);
+      return CURLE_OUT_OF_MEMORY;
     }
+    if(!edispname || !strcmp(ehostname, edispname))
+      connssl->dispname = connssl->hostname;
     else {
-      connssl->hostname = cf->conn->host.name;
-      connssl->dispname = cf->conn->host.dispname;
-      connssl->port = cf->conn->remote_port;
+      connssl->dispname = strdup(edispname);
+      if(!connssl->dispname) {
+        free_hostname(connssl);
+        return CURLE_OUT_OF_MEMORY;
+      }
     }
   }
-  DEBUGASSERT(connssl->hostname);
+  connssl->port = eport;
+  return CURLE_OK;
 }
 
 static void ssl_cf_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
-  cf_ctx_set_data(cf, data);
+  struct cf_call_data save;
+
+  CF_DATA_SAVE(save, cf, data);
   cf_close(cf, data);
+  CF_DATA_RESTORE(cf, save);
   cf_ctx_free(cf->ctx);
   cf->ctx = NULL;
 }
@@ -1484,10 +1489,12 @@
 static void ssl_cf_close(struct Curl_cfilter *cf,
                          struct Curl_easy *data)
 {
-  cf_ctx_set_data(cf, data);
+  struct cf_call_data save;
+
+  CF_DATA_SAVE(save, cf, data);
   cf_close(cf, data);
   cf->next->cft->close(cf->next, data);
-  cf_ctx_set_data(cf, NULL);
+  CF_DATA_RESTORE(cf, save);
 }
 
 static CURLcode ssl_cf_connect(struct Curl_cfilter *cf,
@@ -1495,6 +1502,7 @@
                                bool blocking, bool *done)
 {
   struct ssl_connect_data *connssl = cf->ctx;
+  struct cf_call_data save;
   CURLcode result;
 
   if(cf->connected) {
@@ -1502,7 +1510,7 @@
     return CURLE_OK;
   }
 
-  cf_ctx_set_data(cf, data);
+  CF_DATA_SAVE(save, cf, data);
   (void)connssl;
   DEBUGASSERT(data->conn);
   DEBUGASSERT(data->conn == cf->conn);
@@ -1513,10 +1521,10 @@
   if(result || !*done)
     goto out;
 
-  /* TODO: right now we do not fully control when hostname is set,
-   * assign it on each connect call. */
-  reinit_hostname(cf);
   *done = FALSE;
+  result = reinit_hostname(cf);
+  if(result)
+    goto out;
 
   if(blocking) {
     result = ssl_connect(cf, data);
@@ -1528,26 +1536,26 @@
 
   if(!result && *done) {
     cf->connected = TRUE;
-    if(cf->sockindex == FIRSTSOCKET && !Curl_ssl_cf_is_proxy(cf))
-      Curl_pgrsTime(data, TIMER_APPCONNECT); /* SSL is connected */
+    connssl->handshake_done = Curl_now();
     DEBUGASSERT(connssl->state == ssl_connection_complete);
   }
 out:
-  cf_ctx_set_data(cf, NULL);
+  CF_DATA_RESTORE(cf, save);
   return result;
 }
 
 static bool ssl_cf_data_pending(struct Curl_cfilter *cf,
                                 const struct Curl_easy *data)
 {
+  struct cf_call_data save;
   bool result;
 
-  cf_ctx_set_data(cf, (struct Curl_easy *)data);
-  if(cf->ctx && Curl_ssl->data_pending(cf, data))
+  CF_DATA_SAVE(save, cf, data);
+  if(Curl_ssl->data_pending(cf, data))
     result = TRUE;
   else
     result = cf->next->cft->has_data_pending(cf->next, data);
-  cf_ctx_set_data(cf, NULL);
+  CF_DATA_RESTORE(cf, save);
   return result;
 }
 
@@ -1555,12 +1563,13 @@
                            struct Curl_easy *data, const void *buf, size_t len,
                            CURLcode *err)
 {
+  struct cf_call_data save;
   ssize_t nwritten;
 
+  CF_DATA_SAVE(save, cf, data);
   *err = CURLE_OK;
-  cf_ctx_set_data(cf, data);
   nwritten = Curl_ssl->send_plain(cf, data, buf, len, err);
-  cf_ctx_set_data(cf, NULL);
+  CF_DATA_RESTORE(cf, save);
   return nwritten;
 }
 
@@ -1568,12 +1577,13 @@
                            struct Curl_easy *data, char *buf, size_t len,
                            CURLcode *err)
 {
+  struct cf_call_data save;
   ssize_t nread;
 
+  CF_DATA_SAVE(save, cf, data);
   *err = CURLE_OK;
-  cf_ctx_set_data(cf, data);
   nread = Curl_ssl->recv_plain(cf, data, buf, len, err);
-  cf_ctx_set_data(cf, NULL);
+  CF_DATA_RESTORE(cf, save);
   return nread;
 }
 
@@ -1581,39 +1591,100 @@
                                    struct Curl_easy *data,
                                    curl_socket_t *socks)
 {
+  struct cf_call_data save;
   int result;
 
-  cf_ctx_set_data(cf, data);
+  CF_DATA_SAVE(save, cf, data);
   result = Curl_ssl->get_select_socks(cf, data, socks);
-  cf_ctx_set_data(cf, NULL);
+  CF_DATA_RESTORE(cf, save);
   return result;
 }
 
-static void ssl_cf_attach_data(struct Curl_cfilter *cf,
-                               struct Curl_easy *data)
+static CURLcode ssl_cf_cntrl(struct Curl_cfilter *cf,
+                             struct Curl_easy *data,
+                             int event, int arg1, void *arg2)
 {
-  if(Curl_ssl->attach_data) {
-    cf_ctx_set_data(cf, data);
-    Curl_ssl->attach_data(cf, data);
-    cf_ctx_set_data(cf, NULL);
+  struct cf_call_data save;
+
+  (void)arg1;
+  (void)arg2;
+  switch(event) {
+  case CF_CTRL_DATA_ATTACH:
+    if(Curl_ssl->attach_data) {
+      CF_DATA_SAVE(save, cf, data);
+      Curl_ssl->attach_data(cf, data);
+      CF_DATA_RESTORE(cf, save);
+    }
+    break;
+  case CF_CTRL_DATA_DETACH:
+    if(Curl_ssl->detach_data) {
+      CF_DATA_SAVE(save, cf, data);
+      Curl_ssl->detach_data(cf, data);
+      CF_DATA_RESTORE(cf, save);
+    }
+    break;
+  default:
+    break;
   }
+  return CURLE_OK;
 }
 
-static void ssl_cf_detach_data(struct Curl_cfilter *cf,
-                               struct Curl_easy *data)
+static CURLcode ssl_cf_query(struct Curl_cfilter *cf,
+                             struct Curl_easy *data,
+                             int query, int *pres1, void *pres2)
 {
-  if(Curl_ssl->detach_data) {
-    cf_ctx_set_data(cf, data);
-    Curl_ssl->detach_data(cf, data);
-    cf_ctx_set_data(cf, NULL);
+  struct ssl_connect_data *connssl = cf->ctx;
+
+  switch(query) {
+  case CF_QUERY_TIMER_APPCONNECT: {
+    struct curltime *when = pres2;
+    if(cf->connected && !Curl_ssl_cf_is_proxy(cf))
+      *when = connssl->handshake_done;
+    return CURLE_OK;
   }
+  default:
+    break;
+  }
+  return cf->next?
+    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
+    CURLE_UNKNOWN_OPTION;
 }
 
-static const struct Curl_cftype cft_ssl = {
+static bool cf_ssl_is_alive(struct Curl_cfilter *cf, struct Curl_easy *data,
+                            bool *input_pending)
+{
+  struct cf_call_data save;
+  int result;
+  /*
+   * This function tries to determine connection status.
+   *
+   * Return codes:
+   *     1 means the connection is still in place
+   *     0 means the connection has been closed
+   *    -1 means the connection status is unknown
+   */
+  CF_DATA_SAVE(save, cf, data);
+  result = Curl_ssl->check_cxn(cf, data);
+  CF_DATA_RESTORE(cf, save);
+  if(result > 0) {
+    *input_pending = TRUE;
+    return TRUE;
+  }
+  if(result == 0) {
+    *input_pending = FALSE;
+    return FALSE;
+  }
+  /* ssl backend does not know */
+  return cf->next?
+    cf->next->cft->is_alive(cf->next, data, input_pending) :
+    FALSE; /* pessimistic in absence of data */
+}
+
+struct Curl_cftype Curl_cft_ssl = {
   "SSL",
   CF_TYPE_SSL,
+  CURL_LOG_DEFAULT,
   ssl_cf_destroy,
-  Curl_cf_def_setup,
   ssl_cf_connect,
   ssl_cf_close,
   Curl_cf_def_get_host,
@@ -1621,15 +1692,17 @@
   ssl_cf_data_pending,
   ssl_cf_send,
   ssl_cf_recv,
-  ssl_cf_attach_data,
-  ssl_cf_detach_data,
+  ssl_cf_cntrl,
+  cf_ssl_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  ssl_cf_query,
 };
 
-static const struct Curl_cftype cft_ssl_proxy = {
+struct Curl_cftype Curl_cft_ssl_proxy = {
   "SSL-PROXY",
   CF_TYPE_SSL,
+  CURL_LOG_DEFAULT,
   ssl_cf_destroy,
-  Curl_cf_def_setup,
   ssl_cf_connect,
   ssl_cf_close,
   Curl_cf_def_get_host,
@@ -1637,65 +1710,107 @@
   ssl_cf_data_pending,
   ssl_cf_send,
   ssl_cf_recv,
-  ssl_cf_attach_data,
-  ssl_cf_detach_data,
+  ssl_cf_cntrl,
+  cf_ssl_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  Curl_cf_def_query,
 };
 
+static CURLcode cf_ssl_create(struct Curl_cfilter **pcf,
+                              struct Curl_easy *data,
+                              struct connectdata *conn)
+{
+  struct Curl_cfilter *cf = NULL;
+  struct ssl_connect_data *ctx;
+  CURLcode result;
+
+  DEBUGASSERT(data->conn);
+
+  ctx = cf_ctx_new(data, Curl_alpn_get_spec(data, conn));
+  if(!ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+
+  result = Curl_cf_create(&cf, &Curl_cft_ssl, ctx);
+
+out:
+  if(result)
+    cf_ctx_free(ctx);
+  *pcf = result? NULL : cf;
+  return result;
+}
+
 CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data,
                               struct connectdata *conn,
                               int sockindex)
 {
   struct Curl_cfilter *cf;
-  struct ssl_connect_data *ctx;
   CURLcode result;
 
-  DEBUGASSERT(data->conn);
-  ctx = cf_ctx_new(data);
-  if(!ctx) {
-    result = CURLE_OUT_OF_MEMORY;
-    goto out;
-  }
+  result = cf_ssl_create(&cf, data, conn);
+  if(!result)
+    Curl_conn_cf_add(data, conn, sockindex, cf);
+  return result;
+}
 
-  result = Curl_cf_create(&cf, &cft_ssl, ctx);
-  if(result)
-    goto out;
+CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at,
+                                  struct Curl_easy *data)
+{
+  struct Curl_cfilter *cf;
+  CURLcode result;
 
-  Curl_conn_cf_add(data, conn, sockindex, cf);
-
-  result = CURLE_OK;
-
-out:
-  if(result)
-    cf_ctx_free(ctx);
+  result = cf_ssl_create(&cf, data, cf_at->conn);
+  if(!result)
+    Curl_conn_cf_insert_after(cf_at, cf);
   return result;
 }
 
 #ifndef CURL_DISABLE_PROXY
+static CURLcode cf_ssl_proxy_create(struct Curl_cfilter **pcf,
+                                    struct Curl_easy *data,
+                                    struct connectdata *conn)
+{
+  struct Curl_cfilter *cf = NULL;
+  struct ssl_connect_data *ctx;
+  CURLcode result;
+
+  ctx = cf_ctx_new(data, Curl_alpn_get_proxy_spec(data, conn));
+  if(!ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+  result = Curl_cf_create(&cf, &Curl_cft_ssl_proxy, ctx);
+
+out:
+  if(result)
+    cf_ctx_free(ctx);
+  *pcf = result? NULL : cf;
+  return result;
+}
+
 CURLcode Curl_ssl_cfilter_proxy_add(struct Curl_easy *data,
                                     struct connectdata *conn,
                                     int sockindex)
 {
   struct Curl_cfilter *cf;
-  struct ssl_connect_data *ctx;
   CURLcode result;
 
-  ctx = cf_ctx_new(data);
-  if(!ctx) {
-    result = CURLE_OUT_OF_MEMORY;
-    goto out;
-  }
+  result = cf_ssl_proxy_create(&cf, data, conn);
+  if(!result)
+    Curl_conn_cf_add(data, conn, sockindex, cf);
+  return result;
+}
 
-  result = Curl_cf_create(&cf, &cft_ssl_proxy, ctx);
-  if(result)
-    goto out;
+CURLcode Curl_cf_ssl_proxy_insert_after(struct Curl_cfilter *cf_at,
+                                        struct Curl_easy *data)
+{
+  struct Curl_cfilter *cf;
+  CURLcode result;
 
-  Curl_conn_cf_add(data, conn, sockindex, cf);
-
-  result = CURLE_OK;
-
-out:
-  if(result)
-    cf_ctx_free(ctx);
+  result = cf_ssl_proxy_create(&cf, data, cf_at->conn);
+  if(!result)
+    Curl_conn_cf_insert_after(cf_at, cf);
   return result;
 }
 
@@ -1717,9 +1832,10 @@
     /* get first filter in chain, if any is present */
     cf = Curl_ssl_cf_get_ssl(data->conn->cfilter[sockindex]);
     if(cf) {
-      cf_ctx_set_data(cf, data);
+      struct cf_call_data save;
+      CF_DATA_SAVE(save, cf, data);
       result = Curl_ssl->get_internals(cf->ctx, info);
-      cf_ctx_set_data(cf, NULL);
+      CF_DATA_RESTORE(cf, save);
     }
   }
   return result;
@@ -1733,7 +1849,7 @@
 
   (void)data;
   for(; cf; cf = cf->next) {
-    if(cf->cft == &cft_ssl) {
+    if(cf->cft == &Curl_cft_ssl) {
       if(Curl_ssl->shut_down(cf, data))
         result = CURLE_SSL_SHUTDOWN_FAILED;
       Curl_conn_cf_discard(cf, data);
@@ -1749,7 +1865,7 @@
   struct Curl_cfilter *cf, *lowest_ssl_cf = NULL;
 
   for(cf = conn->cfilter[sockindex]; cf; cf = cf->next) {
-    if(cf->cft == &cft_ssl || cf->cft == &cft_ssl_proxy) {
+    if(cf->cft == &Curl_cft_ssl || cf->cft == &Curl_cft_ssl_proxy) {
       lowest_ssl_cf = cf;
       if(cf->connected || (cf->next && cf->next->connected)) {
         /* connected or about to start */
@@ -1762,7 +1878,7 @@
 
 bool Curl_ssl_cf_is_proxy(struct Curl_cfilter *cf)
 {
-  return (cf->cft == &cft_ssl_proxy);
+  return (cf->cft == &Curl_cft_ssl_proxy);
 }
 
 struct ssl_config_data *
@@ -1814,10 +1930,142 @@
 struct Curl_cfilter *Curl_ssl_cf_get_ssl(struct Curl_cfilter *cf)
 {
   for(; cf; cf = cf->next) {
-    if(cf->cft == &cft_ssl || cf->cft == &cft_ssl_proxy)
+    if(cf->cft == &Curl_cft_ssl || cf->cft == &Curl_cft_ssl_proxy)
       return cf;
   }
   return NULL;
 }
 
+static const struct alpn_spec ALPN_SPEC_H10 = {
+  { ALPN_HTTP_1_0 }, 1
+};
+static const struct alpn_spec ALPN_SPEC_H11 = {
+  { ALPN_HTTP_1_1 }, 1
+};
+#ifdef USE_HTTP2
+static const struct alpn_spec ALPN_SPEC_H2_H11 = {
+  { ALPN_H2, ALPN_HTTP_1_1 }, 2
+};
+#endif
+
+const struct alpn_spec *
+Curl_alpn_get_spec(struct Curl_easy *data, struct connectdata *conn)
+{
+  if(!conn->bits.tls_enable_alpn)
+    return NULL;
+  if(data->state.httpwant == CURL_HTTP_VERSION_1_0)
+    return &ALPN_SPEC_H10;
+#ifdef USE_HTTP2
+  if(data->state.httpwant >= CURL_HTTP_VERSION_2)
+    return &ALPN_SPEC_H2_H11;
+#endif
+  return &ALPN_SPEC_H11;
+}
+
+const struct alpn_spec *
+Curl_alpn_get_proxy_spec(struct Curl_easy *data, struct connectdata *conn)
+{
+  if(!conn->bits.tls_enable_alpn)
+    return NULL;
+  if(data->state.httpwant == CURL_HTTP_VERSION_1_0)
+    return &ALPN_SPEC_H10;
+  return &ALPN_SPEC_H11;
+}
+
+CURLcode Curl_alpn_to_proto_buf(struct alpn_proto_buf *buf,
+                                const struct alpn_spec *spec)
+{
+  size_t i, len;
+  int off = 0;
+  unsigned char blen;
+
+  memset(buf, 0, sizeof(*buf));
+  for(i = 0; spec && i < spec->count; ++i) {
+    len = strlen(spec->entries[i]);
+    if(len >= ALPN_NAME_MAX)
+      return CURLE_FAILED_INIT;
+    blen = (unsigned  char)len;
+    if(off + blen + 1 >= (int)sizeof(buf->data))
+      return CURLE_FAILED_INIT;
+    buf->data[off++] = blen;
+    memcpy(buf->data + off, spec->entries[i], blen);
+    off += blen;
+  }
+  buf->len = off;
+  return CURLE_OK;
+}
+
+CURLcode Curl_alpn_to_proto_str(struct alpn_proto_buf *buf,
+                                const struct alpn_spec *spec)
+{
+  size_t i, len;
+  size_t off = 0;
+
+  memset(buf, 0, sizeof(*buf));
+  for(i = 0; spec && i < spec->count; ++i) {
+    len = strlen(spec->entries[i]);
+    if(len >= ALPN_NAME_MAX)
+      return CURLE_FAILED_INIT;
+    if(off + len + 2 >= (int)sizeof(buf->data))
+      return CURLE_FAILED_INIT;
+    if(off)
+      buf->data[off++] = ',';
+    memcpy(buf->data + off, spec->entries[i], len);
+    off += len;
+  }
+  buf->data[off] = '\0';
+  buf->len = (int)off;
+  return CURLE_OK;
+}
+
+CURLcode Curl_alpn_set_negotiated(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  const unsigned char *proto,
+                                  size_t proto_len)
+{
+  int can_multi = 0;
+
+  if(proto && proto_len) {
+    if(proto_len == ALPN_HTTP_1_1_LENGTH &&
+            !memcmp(ALPN_HTTP_1_1, proto, ALPN_HTTP_1_1_LENGTH)) {
+      cf->conn->alpn = CURL_HTTP_VERSION_1_1;
+    }
+    else if(proto_len == ALPN_HTTP_1_0_LENGTH &&
+            !memcmp(ALPN_HTTP_1_0, proto, ALPN_HTTP_1_0_LENGTH)) {
+      cf->conn->alpn = CURL_HTTP_VERSION_1_0;
+    }
+#ifdef USE_HTTP2
+    else if(proto_len == ALPN_H2_LENGTH &&
+            !memcmp(ALPN_H2, proto, ALPN_H2_LENGTH)) {
+      cf->conn->alpn = CURL_HTTP_VERSION_2;
+      can_multi = 1;
+    }
+#endif
+#ifdef USE_HTTP3
+    else if(proto_len == ALPN_H3_LENGTH &&
+       !memcmp(ALPN_H3, proto, ALPN_H3_LENGTH)) {
+      cf->conn->alpn = CURL_HTTP_VERSION_3;
+      can_multi = 1;
+    }
+#endif
+    else {
+      cf->conn->alpn = CURL_HTTP_VERSION_NONE;
+      failf(data, "unsupported ALPN protocol: '%.*s'", (int)proto_len, proto);
+      /* TODO: do we want to fail this? Previous code just ignored it and
+       * some vtls backends even ignore the return code of this function. */
+      /* return CURLE_NOT_BUILT_IN; */
+      goto out;
+    }
+    infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, (int)proto_len, proto);
+  }
+  else {
+    cf->conn->alpn = CURL_HTTP_VERSION_NONE;
+    infof(data, VTLS_INFOF_NO_ALPN);
+  }
+
+out:
+  Curl_multiuse_state(data, can_multi? BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
+  return CURLE_OK;
+}
+
 #endif /* USE_SSL */
diff --git a/Utilities/cmcurl/lib/vtls/vtls.h b/Utilities/cmcurl/lib/vtls/vtls.h
index 5ad64fc..0d9e74a 100644
--- a/Utilities/cmcurl/lib/vtls/vtls.h
+++ b/Utilities/cmcurl/lib/vtls/vtls.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -27,7 +27,6 @@
 
 struct connectdata;
 struct ssl_config_data;
-struct ssl_connect_data;
 struct ssl_primary_config;
 struct Curl_ssl_session;
 
@@ -53,6 +52,7 @@
 /* Curl_multi SSL backend-specific data; declared differently by each SSL
    backend */
 struct multi_ssl_backend_data;
+struct Curl_cfilter;
 
 CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name,
                                    const curl_ssl_backend ***avail);
@@ -68,8 +68,53 @@
 /* see https://www.iana.org/assignments/tls-extensiontype-values/ */
 #define ALPN_HTTP_1_1_LENGTH 8
 #define ALPN_HTTP_1_1 "http/1.1"
+#define ALPN_HTTP_1_0_LENGTH 8
+#define ALPN_HTTP_1_0 "http/1.0"
 #define ALPN_H2_LENGTH 2
 #define ALPN_H2 "h2"
+#define ALPN_H3_LENGTH 2
+#define ALPN_H3 "h3"
+
+/* conservative sizes on the ALPN entries and count we are handling,
+ * we can increase these if we ever feel the need or have to accommodate
+ * ALPN strings from the "outside". */
+#define ALPN_NAME_MAX     10
+#define ALPN_ENTRIES_MAX  3
+#define ALPN_PROTO_BUF_MAX   (ALPN_ENTRIES_MAX * (ALPN_NAME_MAX + 1))
+
+struct alpn_spec {
+  const char entries[ALPN_ENTRIES_MAX][ALPN_NAME_MAX];
+  size_t count; /* number of entries */
+};
+
+struct alpn_proto_buf {
+  unsigned char data[ALPN_PROTO_BUF_MAX];
+  int len;
+};
+
+CURLcode Curl_alpn_to_proto_buf(struct alpn_proto_buf *buf,
+                                const struct alpn_spec *spec);
+CURLcode Curl_alpn_to_proto_str(struct alpn_proto_buf *buf,
+                                const struct alpn_spec *spec);
+
+CURLcode Curl_alpn_set_negotiated(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  const unsigned char *proto,
+                                  size_t proto_len);
+
+/**
+ * Get the ALPN specification to use for talking to remote host.
+ * May return NULL if ALPN is disabled on the connection.
+ */
+const struct alpn_spec *
+Curl_alpn_get_spec(struct Curl_easy *data, struct connectdata *conn);
+
+/**
+ * Get the ALPN specification to use for talking to the proxy.
+ * May return NULL if ALPN is disabled on the connection.
+ */
+const struct alpn_spec *
+Curl_alpn_get_proxy_spec(struct Curl_easy *data, struct connectdata *conn);
 
 
 char *Curl_ssl_snihost(struct Curl_easy *data, const char *host, size_t *olen);
@@ -95,7 +140,6 @@
 /* init the SSL session ID cache */
 CURLcode Curl_ssl_initsessions(struct Curl_easy *, size_t);
 void Curl_ssl_version(char *buffer, size_t size);
-int Curl_ssl_check_cxn(struct Curl_easy *data, struct connectdata *conn);
 
 /* Certificate information list handling. */
 
@@ -156,6 +200,9 @@
                               struct connectdata *conn,
                               int sockindex);
 
+CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at,
+                                  struct Curl_easy *data);
+
 CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data,
                                  int sockindex);
 
@@ -163,6 +210,8 @@
 CURLcode Curl_ssl_cfilter_proxy_add(struct Curl_easy *data,
                                     struct connectdata *conn,
                                     int sockindex);
+CURLcode Curl_cf_ssl_proxy_insert_after(struct Curl_cfilter *cf_at,
+                                        struct Curl_easy *data);
 #endif /* !CURL_DISABLE_PROXY */
 
 /**
@@ -208,6 +257,9 @@
 void *Curl_ssl_get_internals(struct Curl_easy *data, int sockindex,
                              CURLINFO info, int n);
 
+extern struct Curl_cftype Curl_cft_ssl;
+extern struct Curl_cftype Curl_cft_ssl_proxy;
+
 #else /* if not USE_SSL */
 
 /* When SSL support is not present, just define away these function calls */
@@ -218,7 +270,6 @@
 #define Curl_ssl_set_engine_default(x) CURLE_NOT_BUILT_IN
 #define Curl_ssl_engines_list(x) NULL
 #define Curl_ssl_initsessions(x,y) CURLE_OK
-#define Curl_ssl_check_cxn(d,x) 0
 #define Curl_ssl_free_certinfo(x) Curl_nop_stmt
 #define Curl_ssl_kill_session(x) Curl_nop_stmt
 #define Curl_ssl_random(x,y,z) ((void)x, CURLE_NOT_BUILT_IN)
diff --git a/Utilities/cmcurl/lib/vtls/vtls_int.h b/Utilities/cmcurl/lib/vtls/vtls_int.h
index 6710a2b..a20ca7d 100644
--- a/Utilities/cmcurl/lib/vtls/vtls_int.h
+++ b/Utilities/cmcurl/lib/vtls/vtls_int.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -33,18 +33,20 @@
 struct ssl_connect_data {
   ssl_connection_state state;
   ssl_connect_state connecting_state;
-  const char *hostname;             /* hostnaem for verification */
-  const char *dispname;             /* display version of hostname */
+  char *hostname;                   /* hostname for verification */
+  char *dispname;                   /* display version of hostname */
   int port;                         /* remote port at origin */
+  const struct alpn_spec *alpn;     /* ALPN to use or NULL for none */
   struct ssl_backend_data *backend; /* vtls backend specific props */
-  struct Curl_easy *call_data;      /* data handle used in current call,
-                                     * same as parameter passed, but available
-                                     * here for backend internal callbacks
-                                     * that need it. NULLed after at the
-                                     * end of each vtls filter invcocation. */
+  struct cf_call_data call_data;    /* data handle used in current call */
+  struct curltime handshake_done;   /* time when handshake finished */
 };
 
 
+#define CF_CTX_CALL_DATA(cf)  \
+  ((struct ssl_connect_data *)(cf)->ctx)->call_data
+
+
 /* Definitions for SSL Implementations */
 
 struct Curl_ssl {
diff --git a/Utilities/cmcurl/lib/vtls/wolfssl.c b/Utilities/cmcurl/lib/vtls/wolfssl.c
index 7cc4774..ac68eab 100644
--- a/Utilities/cmcurl/lib/vtls/wolfssl.c
+++ b/Utilities/cmcurl/lib/vtls/wolfssl.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -94,6 +94,7 @@
 struct ssl_backend_data {
   SSL_CTX* ctx;
   SSL*     handle;
+  CURLcode io_result;       /* result of last BIO cfilter operation */
 };
 
 #ifdef OPENSSL_EXTRA
@@ -218,29 +219,9 @@
   { WOLFSSL_KYBER_LEVEL1, "KYBER_LEVEL1" },
   { WOLFSSL_KYBER_LEVEL3, "KYBER_LEVEL3" },
   { WOLFSSL_KYBER_LEVEL5, "KYBER_LEVEL5" },
-  { WOLFSSL_NTRU_HPS_LEVEL1, "NTRU_HPS_LEVEL1" },
-  { WOLFSSL_NTRU_HPS_LEVEL3, "NTRU_HPS_LEVEL3" },
-  { WOLFSSL_NTRU_HPS_LEVEL5, "NTRU_HPS_LEVEL5" },
-  { WOLFSSL_NTRU_HRSS_LEVEL3, "NTRU_HRSS_LEVEL3" },
-  { WOLFSSL_SABER_LEVEL1, "SABER_LEVEL1" },
-  { WOLFSSL_SABER_LEVEL3, "SABER_LEVEL3" },
-  { WOLFSSL_SABER_LEVEL5, "SABER_LEVEL5" },
-  { WOLFSSL_KYBER_90S_LEVEL1, "KYBER_90S_LEVEL1" },
-  { WOLFSSL_KYBER_90S_LEVEL3, "KYBER_90S_LEVEL3" },
-  { WOLFSSL_KYBER_90S_LEVEL5, "KYBER_90S_LEVEL5" },
-  { WOLFSSL_P256_NTRU_HPS_LEVEL1, "P256_NTRU_HPS_LEVEL1" },
-  { WOLFSSL_P384_NTRU_HPS_LEVEL3, "P384_NTRU_HPS_LEVEL3" },
-  { WOLFSSL_P521_NTRU_HPS_LEVEL5, "P521_NTRU_HPS_LEVEL5" },
-  { WOLFSSL_P384_NTRU_HRSS_LEVEL3, "P384_NTRU_HRSS_LEVEL3" },
-  { WOLFSSL_P256_SABER_LEVEL1, "P256_SABER_LEVEL1" },
-  { WOLFSSL_P384_SABER_LEVEL3, "P384_SABER_LEVEL3" },
-  { WOLFSSL_P521_SABER_LEVEL5, "P521_SABER_LEVEL5" },
   { WOLFSSL_P256_KYBER_LEVEL1, "P256_KYBER_LEVEL1" },
   { WOLFSSL_P384_KYBER_LEVEL3, "P384_KYBER_LEVEL3" },
   { WOLFSSL_P521_KYBER_LEVEL5, "P521_KYBER_LEVEL5" },
-  { WOLFSSL_P256_KYBER_90S_LEVEL1, "P256_KYBER_90S_LEVEL1" },
-  { WOLFSSL_P384_KYBER_90S_LEVEL3, "P384_KYBER_90S_LEVEL3" },
-  { WOLFSSL_P521_KYBER_90S_LEVEL5, "P521_KYBER_90S_LEVEL5" },
   { 0, NULL }
 };
 #endif
@@ -300,12 +281,15 @@
 {
   struct Curl_cfilter *cf = wolfSSL_BIO_get_data(bio);
   struct ssl_connect_data *connssl = cf->ctx;
-  struct Curl_easy *data = connssl->call_data;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
   ssize_t nwritten;
   CURLcode result = CURLE_OK;
 
   DEBUGASSERT(data);
   nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, &result);
+  connssl->backend->io_result = result;
+  DEBUGF(LOG_CF(data, cf, "bio_write(len=%d) -> %zd, %d",
+                blen, nwritten, result));
   wolfSSL_BIO_clear_retry_flags(bio);
   if(nwritten < 0 && CURLE_AGAIN == result)
     BIO_set_retry_read(bio);
@@ -316,7 +300,7 @@
 {
   struct Curl_cfilter *cf = wolfSSL_BIO_get_data(bio);
   struct ssl_connect_data *connssl = cf->ctx;
-  struct Curl_easy *data = connssl->call_data;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
   ssize_t nread;
   CURLcode result = CURLE_OK;
 
@@ -326,6 +310,9 @@
     return 0;
 
   nread = Curl_conn_cf_recv(cf->next, data, buf, blen, &result);
+  connssl->backend->io_result = result;
+  DEBUGF(LOG_CF(data, cf, "bio_read(len=%d) -> %zd, %d",
+                blen, nread, result));
   wolfSSL_BIO_clear_retry_flags(bio);
   if(nread < 0 && CURLE_AGAIN == result)
     BIO_set_retry_read(bio);
@@ -633,29 +620,18 @@
 #endif
 
 #ifdef HAVE_ALPN
-  if(cf->conn->bits.tls_enable_alpn) {
-    char protocols[128];
-    *protocols = '\0';
+  if(connssl->alpn) {
+    struct alpn_proto_buf proto;
+    CURLcode result;
 
-    /* wolfSSL's ALPN protocol name list format is a comma separated string of
-       protocols in descending order of preference, eg: "h2,http/1.1" */
-
-#ifdef USE_HTTP2
-    if(data->state.httpwant >= CURL_HTTP_VERSION_2) {
-      strcpy(protocols + strlen(protocols), ALPN_H2 ",");
-      infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
-    }
-#endif
-
-    strcpy(protocols + strlen(protocols), ALPN_HTTP_1_1);
-    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
-
-    if(wolfSSL_UseALPN(backend->handle, protocols,
-                       (unsigned)strlen(protocols),
+    result = Curl_alpn_to_proto_str(&proto, connssl->alpn);
+    if(result ||
+       wolfSSL_UseALPN(backend->handle, (char *)proto.data, proto.len,
                        WOLFSSL_ALPN_CONTINUE_ON_MISMATCH) != SSL_SUCCESS) {
       failf(data, "SSL: failed setting ALPN protocols");
       return CURLE_SSL_CONNECT_ERROR;
     }
+    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data);
   }
 #endif /* HAVE_ALPN */
 
@@ -707,7 +683,7 @@
   }
 #else /* USE_BIO_CHAIN */
   /* pass the raw socket into the SSL layer */
-  if(!SSL_set_fd(backend->handle, (int)cf->conn->sock[cf->sockindex])) {
+  if(!SSL_set_fd(backend->handle, (int)Curl_conn_cf_get_socket(cf, data))) {
     failf(data, "SSL: SSL_set_fd failed");
     return CURLE_SSL_CONNECT_ERROR;
   }
@@ -822,6 +798,9 @@
       }
     }
 #endif
+    else if(backend->io_result == CURLE_AGAIN) {
+      return CURLE_OK;
+    }
     else {
       failf(data, "SSL_connect failed with error %d: %s", detail,
           ERR_error_string(detail, error_buffer));
@@ -883,25 +862,11 @@
     rc = wolfSSL_ALPN_GetProtocol(backend->handle, &protocol, &protocol_len);
 
     if(rc == SSL_SUCCESS) {
-      infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, protocol_len, protocol);
-
-      if(protocol_len == ALPN_HTTP_1_1_LENGTH &&
-         !memcmp(protocol, ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH))
-        cf->conn->alpn = CURL_HTTP_VERSION_1_1;
-#ifdef USE_HTTP2
-      else if(data->state.httpwant >= CURL_HTTP_VERSION_2 &&
-              protocol_len == ALPN_H2_LENGTH &&
-              !memcmp(protocol, ALPN_H2, ALPN_H2_LENGTH))
-        cf->conn->alpn = CURL_HTTP_VERSION_2;
-#endif
-      else
-        infof(data, "ALPN, unrecognized protocol %.*s", protocol_len,
-              protocol);
-      Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
-                          BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
+      Curl_alpn_set_negotiated(cf, data, (const unsigned char *)protocol,
+                               protocol_len);
     }
     else if(rc == SSL_ALPN_NOT_FOUND)
-      infof(data, VTLS_INFOF_NO_ALPN);
+      Curl_alpn_set_negotiated(cf, data, NULL, 0);
     else {
       failf(data, "ALPN, failure getting protocol, error %d", rc);
       return CURLE_SSL_CONNECT_ERROR;
@@ -995,7 +960,6 @@
   ERR_clear_error();
 
   rc = SSL_write(backend->handle, mem, memlen);
-
   if(rc <= 0) {
     int err = SSL_get_error(backend->handle, rc);
 
@@ -1003,9 +967,17 @@
     case SSL_ERROR_WANT_READ:
     case SSL_ERROR_WANT_WRITE:
       /* there's data pending, re-invoke SSL_write() */
+      DEBUGF(LOG_CF(data, cf, "wolfssl_send(len=%zu) -> AGAIN", len));
       *curlcode = CURLE_AGAIN;
       return -1;
     default:
+      if(backend->io_result == CURLE_AGAIN) {
+        DEBUGF(LOG_CF(data, cf, "wolfssl_send(len=%zu) -> AGAIN", len));
+        *curlcode = CURLE_AGAIN;
+        return -1;
+      }
+      DEBUGF(LOG_CF(data, cf, "wolfssl_send(len=%zu) -> %d, %d",
+                    len, rc, err));
       failf(data, "SSL write: %s, errno %d",
             ERR_error_string(err, error_buffer),
             SOCKERRNO);
@@ -1013,6 +985,7 @@
       return -1;
     }
   }
+  DEBUGF(LOG_CF(data, cf, "wolfssl_send(len=%zu) -> %d", len, rc));
   return rc;
 }
 
@@ -1042,19 +1015,19 @@
 
 static ssize_t wolfssl_recv(struct Curl_cfilter *cf,
                             struct Curl_easy *data,
-                            char *buf,
-                            size_t buffersize,
+                            char *buf, size_t blen,
                             CURLcode *curlcode)
 {
   struct ssl_connect_data *connssl = cf->ctx;
   struct ssl_backend_data *backend = connssl->backend;
   char error_buffer[WOLFSSL_MAX_ERROR_SZ];
-  int buffsize = (buffersize > (size_t)INT_MAX) ? INT_MAX : (int)buffersize;
+  int buffsize = (blen > (size_t)INT_MAX) ? INT_MAX : (int)blen;
   int nread;
 
   DEBUGASSERT(backend);
 
   ERR_clear_error();
+  *curlcode = CURLE_OK;
 
   nread = SSL_read(backend->handle, buf, buffsize);
 
@@ -1063,22 +1036,31 @@
 
     switch(err) {
     case SSL_ERROR_ZERO_RETURN: /* no more data */
-      break;
+      DEBUGF(LOG_CF(data, cf, "wolfssl_recv(len=%zu) -> CLOSED", blen));
+      *curlcode = CURLE_OK;
+      return 0;
     case SSL_ERROR_NONE:
       /* FALLTHROUGH */
     case SSL_ERROR_WANT_READ:
       /* FALLTHROUGH */
     case SSL_ERROR_WANT_WRITE:
       /* there's data pending, re-invoke SSL_read() */
+      DEBUGF(LOG_CF(data, cf, "wolfssl_recv(len=%zu) -> AGAIN", blen));
       *curlcode = CURLE_AGAIN;
       return -1;
     default:
+      if(backend->io_result == CURLE_AGAIN) {
+        DEBUGF(LOG_CF(data, cf, "wolfssl_recv(len=%zu) -> AGAIN", blen));
+        *curlcode = CURLE_AGAIN;
+        return -1;
+      }
       failf(data, "SSL read: %s, errno %d",
             ERR_error_string(err, error_buffer), SOCKERRNO);
       *curlcode = CURLE_RECV_ERROR;
       return -1;
     }
   }
+  DEBUGF(LOG_CF(data, cf, "wolfssl_recv(len=%zu) -> %d", blen, nread));
   return nread;
 }
 
@@ -1166,7 +1148,7 @@
 {
   CURLcode result;
   struct ssl_connect_data *connssl = cf->ctx;
-  curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+  curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data);
   int what;
 
   /* check if the connection has already been established */
diff --git a/Utilities/cmcurl/lib/vtls/wolfssl.h b/Utilities/cmcurl/lib/vtls/wolfssl.h
index b2e7c3f..a5ed848 100644
--- a/Utilities/cmcurl/lib/vtls/wolfssl.h
+++ b/Utilities/cmcurl/lib/vtls/wolfssl.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/vtls/x509asn1.c b/Utilities/cmcurl/lib/vtls/x509asn1.c
index 4c1c9a8..c298200 100644
--- a/Utilities/cmcurl/lib/vtls/x509asn1.c
+++ b/Utilities/cmcurl/lib/vtls/x509asn1.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -48,6 +48,7 @@
 #include "curl_ctype.h"
 #include "hostcheck.h"
 #include "vtls/vtls.h"
+#include "vtls/vtls_int.h"
 #include "sendf.h"
 #include "inet_pton.h"
 #include "curl_base64.h"
@@ -1117,7 +1118,7 @@
   for(ccp = cert.version.beg; ccp < cert.version.end; ccp++)
     version = (version << 8) | *(const unsigned char *) ccp;
   if(data->set.ssl.certinfo) {
-    ccp = curl_maprintf("%lx", version);
+    ccp = curl_maprintf("%x", version);
     if(!ccp)
       return CURLE_OUT_OF_MEMORY;
     result = Curl_ssl_push_certinfo(data, certnum, "Version", ccp);
@@ -1126,7 +1127,7 @@
       return result;
   }
   if(!certnum)
-    infof(data, "   Version: %lu (0x%lx)", version + 1, version);
+    infof(data, "   Version: %u (0x%x)", version + 1, version);
 
   /* Serial number. */
   ccp = ASN1tostr(&cert.serialNumber, 0);
@@ -1313,7 +1314,8 @@
 
   /* Get the server IP address. */
 #ifdef ENABLE_IPV6
-  if(conn->bits.ipv6_ip && Curl_inet_pton(AF_INET6, connssl->hostname, &addr))
+  if(cf->conn->bits.ipv6_ip &&
+     Curl_inet_pton(AF_INET6, connssl->hostname, &addr))
     addrlen = sizeof(struct in6_addr);
   else
 #endif
@@ -1348,19 +1350,18 @@
           break;
         switch(name.tag) {
         case 2: /* DNS name. */
-          matched = 0;
           len = utf8asn1str(&dnsname, CURL_ASN1_IA5_STRING,
                             name.beg, name.end);
-          if(len > 0) {
-            if(size_t)len == strlen(dnsname)
-              matched = Curl_cert_hostcheck(dnsname, (size_t)len,
-                                            connssl->hostname, hostlen);
-            free(dnsname);
-          }
+          if(len > 0 && (size_t)len == strlen(dnsname))
+            matched = Curl_cert_hostcheck(dnsname, (size_t)len,
+                                          connssl->hostname, hostlen);
+          else
+            matched = 0;
+          free(dnsname);
           break;
 
         case 7: /* IP address. */
-          matched = (name.end - name.beg) == addrlen &&
+          matched = (size_t)(name.end - name.beg) == addrlen &&
             !memcmp(&addr, name.beg, addrlen);
           break;
         }
@@ -1406,8 +1407,10 @@
     failf(data, "SSL: unable to obtain common name from peer certificate");
   else {
     len = utf8asn1str(&dnsname, elem.tag, elem.beg, elem.end);
-    if(len < 0)
+    if(len < 0) {
+      free(dnsname);
       return CURLE_OUT_OF_MEMORY;
+    }
     if(strlen(dnsname) != (size_t) len)         /* Nul byte in string ? */
       failf(data, "SSL: illegal cert name field");
     else if(Curl_cert_hostcheck((const char *) dnsname,
diff --git a/Utilities/cmcurl/lib/vtls/x509asn1.h b/Utilities/cmcurl/lib/vtls/x509asn1.h
index eb8e959..5496de4 100644
--- a/Utilities/cmcurl/lib/vtls/x509asn1.h
+++ b/Utilities/cmcurl/lib/vtls/x509asn1.h
@@ -8,7 +8,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/warnless.c b/Utilities/cmcurl/lib/warnless.c
index b00d7a5..10c91fb 100644
--- a/Utilities/cmcurl/lib/warnless.c
+++ b/Utilities/cmcurl/lib/warnless.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/warnless.h b/Utilities/cmcurl/lib/warnless.h
index 4367099..99b2433 100644
--- a/Utilities/cmcurl/lib/warnless.h
+++ b/Utilities/cmcurl/lib/warnless.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/Utilities/cmcurl/lib/wildcard.c b/Utilities/cmcurl/lib/wildcard.c
deleted file mode 100644
index a3e24b6..0000000
--- a/Utilities/cmcurl/lib/wildcard.c
+++ /dev/null
@@ -1,75 +0,0 @@
-/***************************************************************************
- *                                  _   _ ____  _
- *  Project                     ___| | | |  _ \| |
- *                             / __| | | | |_) | |
- *                            | (__| |_| |  _ <| |___
- *                             \___|\___/|_| \_\_____|
- *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- *
- * This software is licensed as described in the file COPYING, which
- * you should have received as part of this distribution. The terms
- * are also available at https://curl.se/docs/copyright.html.
- *
- * You may opt to use, copy, modify, merge, publish, distribute and/or sell
- * copies of the Software, and permit persons to whom the Software is
- * furnished to do so, under the terms of the COPYING file.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- * SPDX-License-Identifier: curl
- *
- ***************************************************************************/
-
-#include "curl_setup.h"
-
-#ifndef CURL_DISABLE_FTP
-
-#include "wildcard.h"
-#include "llist.h"
-#include "fileinfo.h"
-/* The last 3 #include files should be in this order */
-#include "curl_printf.h"
-#include "curl_memory.h"
-#include "memdebug.h"
-
-static void fileinfo_dtor(void *user, void *element)
-{
-  (void)user;
-  Curl_fileinfo_cleanup(element);
-}
-
-CURLcode Curl_wildcard_init(struct WildcardData *wc)
-{
-  Curl_llist_init(&wc->filelist, fileinfo_dtor);
-  wc->state = CURLWC_INIT;
-
-  return CURLE_OK;
-}
-
-void Curl_wildcard_dtor(struct WildcardData *wc)
-{
-  if(!wc)
-    return;
-
-  if(wc->dtor) {
-    wc->dtor(wc->protdata);
-    wc->dtor = ZERO_NULL;
-    wc->protdata = NULL;
-  }
-  DEBUGASSERT(wc->protdata == NULL);
-
-  Curl_llist_destroy(&wc->filelist, NULL);
-
-
-  free(wc->path);
-  wc->path = NULL;
-  free(wc->pattern);
-  wc->pattern = NULL;
-
-  wc->customptr = NULL;
-  wc->state = CURLWC_INIT;
-}
-
-#endif /* if disabled */
diff --git a/Utilities/cmcurl/lib/wildcard.h b/Utilities/cmcurl/lib/wildcard.h
deleted file mode 100644
index 21e933b..0000000
--- a/Utilities/cmcurl/lib/wildcard.h
+++ /dev/null
@@ -1,70 +0,0 @@
-#ifndef HEADER_CURL_WILDCARD_H
-#define HEADER_CURL_WILDCARD_H
-/***************************************************************************
- *                                  _   _ ____  _
- *  Project                     ___| | | |  _ \| |
- *                             / __| | | | |_) | |
- *                            | (__| |_| |  _ <| |___
- *                             \___|\___/|_| \_\_____|
- *
- * Copyright (C) 2010 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- *
- * This software is licensed as described in the file COPYING, which
- * you should have received as part of this distribution. The terms
- * are also available at https://curl.se/docs/copyright.html.
- *
- * You may opt to use, copy, modify, merge, publish, distribute and/or sell
- * copies of the Software, and permit persons to whom the Software is
- * furnished to do so, under the terms of the COPYING file.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- * SPDX-License-Identifier: curl
- *
- ***************************************************************************/
-
-#include "curl_setup.h"
-
-#ifndef CURL_DISABLE_FTP
-#include <curl/curl.h>
-#include "llist.h"
-
-/* list of wildcard process states */
-typedef enum {
-  CURLWC_CLEAR = 0,
-  CURLWC_INIT = 1,
-  CURLWC_MATCHING, /* library is trying to get list of addresses for
-                      downloading */
-  CURLWC_DOWNLOADING,
-  CURLWC_CLEAN, /* deallocate resources and reset settings */
-  CURLWC_SKIP,  /* skip over concrete file */
-  CURLWC_ERROR, /* error cases */
-  CURLWC_DONE   /* if is wildcard->state == CURLWC_DONE wildcard loop
-                   will end */
-} wildcard_states;
-
-typedef void (*wildcard_dtor)(void *ptr);
-
-/* struct keeping information about wildcard download process */
-struct WildcardData {
-  wildcard_states state;
-  char *path; /* path to the directory, where we trying wildcard-match */
-  char *pattern; /* wildcard pattern */
-  struct Curl_llist filelist; /* llist with struct Curl_fileinfo */
-  void *protdata; /* pointer to protocol specific temporary data */
-  wildcard_dtor dtor;
-  void *customptr;  /* for CURLOPT_CHUNK_DATA pointer */
-};
-
-CURLcode Curl_wildcard_init(struct WildcardData *wc);
-void Curl_wildcard_dtor(struct WildcardData *wc);
-
-struct Curl_easy;
-
-#else
-/* FTP is disabled */
-#define Curl_wildcard_dtor(x)
-#endif
-
-#endif /* HEADER_CURL_WILDCARD_H */
diff --git a/Utilities/cmcurl/lib/ws.c b/Utilities/cmcurl/lib/ws.c
index c1b2622..e8495dc 100644
--- a/Utilities/cmcurl/lib/ws.c
+++ b/Utilities/cmcurl/lib/ws.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -115,12 +115,18 @@
   return result;
 }
 
-CURLcode Curl_ws_accept(struct Curl_easy *data)
+/*
+ * 'nread' is number of bytes of websocket data already in the buffer at
+ * 'mem'.
+ */
+CURLcode Curl_ws_accept(struct Curl_easy *data,
+                        const char *mem, size_t nread)
 {
   struct SingleRequest *k = &data->req;
   struct HTTP *ws = data->req.p.http;
   struct connectdata *conn = data->conn;
   struct websocket *wsp = &data->req.p.http->ws;
+  struct ws_conn *wsc = &conn->proto.ws;
   CURLcode result;
 
   /* Verify the Sec-WebSocket-Accept response.
@@ -149,13 +155,17 @@
 
   infof(data, "Received 101, switch to WebSocket; mask %02x%02x%02x%02x",
         ws->ws.mask[0], ws->ws.mask[1], ws->ws.mask[2], ws->ws.mask[3]);
+  Curl_dyn_init(&wsc->early, data->set.buffer_size);
+  if(nread) {
+    result = Curl_dyn_addn(&wsc->early, mem, nread);
+    if(result)
+      return result;
+    infof(data, "%zu bytes websocket payload", nread);
+    wsp->stillb = Curl_dyn_ptr(&wsc->early);
+    wsp->stillblen = Curl_dyn_len(&wsc->early);
+  }
   k->upgr101 = UPGR101_RECEIVED;
 
-  if(data->set.connect_only)
-    /* switch off non-blocking sockets */
-    (void)curlx_nonblock(conn->sock[FIRSTSOCKET], FALSE);
-
-  wsp->oleft = 0;
   return result;
 }
 
@@ -183,12 +193,12 @@
 
 /* ws_decode() decodes a binary frame into structured WebSocket data,
 
-   wpkt - the incoming raw data. If NULL, work on the already buffered data.
-   ilen - the size of the provided data, perhaps too little, perhaps too much
-   out - stored pointed to extracted data
+   data - the transfer
+   inbuf - incoming raw data. If NULL, work on the already buffered data.
+   inlen - size of the provided data, perhaps too little, perhaps too much
+   headlen - stored length of the frame header
    olen - stored length of the extracted data
    oleft - number of unread bytes pending to that belongs to this frame
-   more - if there is more data in there
    flags - stored bitmask about the frame
 
    Returns CURLE_AGAIN if there is only a partial frame in the buffer. Then it
@@ -246,6 +256,9 @@
     infof(data, "WS: received OPCODE PONG");
     *flags |= CURLWS_PONG;
     break;
+  default:
+    failf(data, "WS: unknown opcode: %x", opcode);
+    return CURLE_RECV_ERROR;
   }
 
   if(inbuf[1] & WSBIT_MASK) {
@@ -296,7 +309,7 @@
     *oleft = 0;             /* bytes yet to come (for this frame) */
   }
 
-  infof(data, "WS: received %zu bytes payload (%zu left, buflen was %zu)",
+  infof(data, "WS: received %Ou bytes payload (%Ou left, buflen was %zu)",
         payloadsize, *oleft, inlen);
   return CURLE_OK;
 }
@@ -339,9 +352,6 @@
 
         result = ws_decode(data, wsbuf, buflen,
                            &headlen, &write_len, &fb_left, &recvflags);
-        consumed += headlen;
-        wsbuf += headlen;
-        buflen -= headlen;
         if(result == CURLE_AGAIN)
           /* insufficient amount of data, keep it for later.
            * we pretend to have written all since we have a copy */
@@ -350,6 +360,10 @@
           infof(data, "WS: decode error %d", (int)result);
           return nitems - 1;
         }
+        consumed += headlen;
+        wsbuf += headlen;
+        buflen -= headlen;
+
         /* New frame. store details about the frame to be reachable with
            curl_ws_meta() from within the write callback */
         ws->ws.frame.age = 0;
@@ -392,7 +406,6 @@
   return nitems;
 }
 
-
 CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer,
                                   size_t buflen, size_t *nread,
                                   struct curl_ws_frame **metap)
@@ -409,7 +422,7 @@
     return result;
 
   while(!done) {
-    size_t write_len;
+    size_t datalen;
     unsigned int recvflags;
 
     if(!wsp->stillblen) {
@@ -419,26 +432,32 @@
                               data->set.buffer_size, &n);
       if(result)
         return result;
-      if(!n)
+      if(!n) {
         /* connection closed */
+        infof(data, "connection expectedly closed?");
         return CURLE_GOT_NOTHING;
+      }
       wsp->stillb = data->state.buffer;
       wsp->stillblen = n;
     }
 
-    infof(data, "WS: got %u websocket bytes to decode",
-          (int)wsp->stillblen);
+    infof(data, "WS: %u bytes left to decode", (int)wsp->stillblen);
     if(!wsp->frame.bytesleft) {
       size_t headlen;
       curl_off_t oleft;
       /* detect new frame */
       result = ws_decode(data, (unsigned char *)wsp->stillb, wsp->stillblen,
-                         &headlen, &write_len, &oleft, &recvflags);
+                         &headlen, &datalen, &oleft, &recvflags);
       if(result == CURLE_AGAIN)
         /* a packet fragment only */
         break;
       else if(result)
         return result;
+      if(datalen > buflen) {
+        size_t diff = datalen - buflen;
+        datalen = buflen;
+        oleft += diff;
+      }
       wsp->stillb += headlen;
       wsp->stillblen -= headlen;
       wsp->frame.offset = 0;
@@ -447,40 +466,45 @@
     }
     else {
       /* existing frame, remaining payload handling */
-      write_len = wsp->frame.bytesleft;
-      if(write_len > wsp->stillblen)
-        write_len = wsp->stillblen;
+      datalen = wsp->frame.bytesleft;
+      if(datalen > wsp->stillblen)
+        datalen = wsp->stillblen;
+      if(datalen > buflen)
+        datalen = buflen;
+
+      wsp->frame.offset += wsp->frame.len;
+      wsp->frame.bytesleft -= datalen;
     }
+    wsp->frame.len = datalen;
 
     /* auto-respond to PINGs */
     if((wsp->frame.flags & CURLWS_PING) && !wsp->frame.bytesleft) {
-      infof(data, "WS: auto-respond to PING with a PONG");
+      size_t nsent = 0;
+      infof(data, "WS: auto-respond to PING with a PONG, %zu bytes payload",
+            datalen);
       /* send back the exact same content as a PONG */
-      result = curl_ws_send(data, wsp->stillb, write_len,
-                            &write_len, 0, CURLWS_PONG);
+      result = curl_ws_send(data, wsp->stillb, datalen, &nsent, 0,
+                            CURLWS_PONG);
       if(result)
         return result;
+      infof(data, "WS: bytesleft %zu datalen %zu",
+            wsp->frame.bytesleft, datalen);
+      /* we handled the data part of the PING, advance over that */
+      wsp->stillb += nsent;
+      wsp->stillblen -= nsent;
     }
-    else if(write_len || !wsp->frame.bytesleft) {
-      if(write_len > buflen)
-        write_len = buflen;
+    else if(datalen) {
       /* copy the payload to the user buffer */
-      memcpy(buffer, wsp->stillb, write_len);
-      *nread = write_len;
+      memcpy(buffer, wsp->stillb, datalen);
+      *nread = datalen;
       done = TRUE;
-    }
-    if(write_len) {
-      /* update buffer and frame info */
-      wsp->frame.offset += write_len;
-      DEBUGASSERT(wsp->frame.bytesleft >= (curl_off_t)write_len);
-      if(wsp->frame.bytesleft)
-        wsp->frame.bytesleft -= write_len;
-      DEBUGASSERT(write_len <= wsp->stillblen);
-      wsp->stillblen -= write_len;
+
+      wsp->stillblen -= datalen;
       if(wsp->stillblen)
-        wsp->stillb += write_len;
-      else
+        wsp->stillb += datalen;
+      else {
         wsp->stillb = NULL;
+      }
     }
   }
   *metap = &wsp->frame;
@@ -555,17 +579,27 @@
   }
 
   if(!(flags & CURLWS_CONT)) {
-    /* if not marked as continuing, assume this is the final fragment */
-    firstbyte |= WSBIT_FIN | opcode;
+    if(!ws->ws.contfragment)
+      /* not marked as continuing, this is the final fragment */
+      firstbyte |= WSBIT_FIN | opcode;
+    else
+      /* marked as continuing, this is the final fragment; set CONT
+         opcode and FIN bit */
+      firstbyte |= WSBIT_FIN | WSBIT_OPCODE_CONT;
+
     ws->ws.contfragment = FALSE;
+    infof(data, "WS: set FIN");
   }
   else if(ws->ws.contfragment) {
     /* the previous fragment was not a final one and this isn't either, keep a
        CONT opcode and no FIN bit */
     firstbyte |= WSBIT_OPCODE_CONT;
+    infof(data, "WS: keep CONT, no FIN");
   }
   else {
+    firstbyte = opcode;
     ws->ws.contfragment = TRUE;
+    infof(data, "WS: set CONT, no FIN");
   }
   out[0] = firstbyte;
   if(len > 65535) {
@@ -686,8 +720,14 @@
 
   infof(data, "WS: wanted to send %zu bytes, sent %zu bytes",
         headlen + buflen, written);
-  *sent = written;
 
+  if(!result) {
+    /* the *sent number only counts "payload", excluding the header */
+    if((size_t)written > headlen)
+      *sent = written - headlen;
+    else
+      *sent = 0;
+  }
   return result;
 }
 
@@ -698,6 +738,17 @@
   Curl_dyn_free(&wsp->buf);
 }
 
+CURLcode Curl_ws_disconnect(struct Curl_easy *data,
+                            struct connectdata *conn,
+                            bool dead_connection)
+{
+  struct ws_conn *wsc = &conn->proto.ws;
+  (void)data;
+  (void)dead_connection;
+  Curl_dyn_free(&wsc->early);
+  return CURLE_OK;
+}
+
 CURL_EXTERN struct curl_ws_frame *curl_ws_meta(struct Curl_easy *data)
 {
   /* we only return something for websocket, called from within the callback
diff --git a/Utilities/cmcurl/lib/ws.h b/Utilities/cmcurl/lib/ws.h
index 2f3ed2d..176dda4 100644
--- a/Utilities/cmcurl/lib/ws.h
+++ b/Utilities/cmcurl/lib/ws.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -46,24 +46,28 @@
   size_t usedbuf; /* number of leading bytes in 'buf' the most recent complete
                      websocket frame uses */
   struct curl_ws_frame frame; /* the struct used for frame state */
-  curl_off_t oleft; /* outstanding number of payload bytes left from the
-                       server */
   size_t stillblen; /* number of bytes left in the buffer to deliver in
                          the next curl_ws_recv() call */
-  char *stillb; /* the stillblen pending bytes are here */
+  const char *stillb; /* the stillblen pending bytes are here */
   curl_off_t sleft; /* outstanding number of payload bytes left to send */
   unsigned int xori; /* xor index */
 };
 
-CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req);
-CURLcode Curl_ws_accept(struct Curl_easy *data);
+struct ws_conn {
+  struct dynbuf early; /* data already read when switching to ws */
+};
 
+CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req);
+CURLcode Curl_ws_accept(struct Curl_easy *data, const char *mem, size_t len);
 size_t Curl_ws_writecb(char *buffer, size_t size, size_t nitems, void *userp);
 void Curl_ws_done(struct Curl_easy *data);
-
+CURLcode Curl_ws_disconnect(struct Curl_easy *data,
+                            struct connectdata *conn,
+                            bool dead_connection);
 #else
 #define Curl_ws_request(x,y) CURLE_OK
 #define Curl_ws_done(x) Curl_nop_stmt
+#define Curl_ws_free(x) Curl_nop_stmt
 #endif
 
 #endif /* HEADER_CURL_WS_H */
diff --git a/Utilities/cmlibarchive/CMakeLists.txt b/Utilities/cmlibarchive/CMakeLists.txt
index b38e653..027de5c 100644
--- a/Utilities/cmlibarchive/CMakeLists.txt
+++ b/Utilities/cmlibarchive/CMakeLists.txt
@@ -238,7 +238,7 @@
 OPTION(ENABLE_LIBXML2 "Enable the use of the system libxml2 library if found" ON)
 OPTION(ENABLE_EXPAT "Enable the use of the system EXPAT library if found" ON)
 OPTION(ENABLE_PCREPOSIX "Enable the use of the system PCREPOSIX library if found" ON)
-OPTION(ENABLE_LibGCC "Enable the use of the system LibGCC library if found" ON)
+OPTION(ENABLE_LIBGCC "Enable the use of the system LibGCC library if found" ON)
 # CNG is used for encrypt/decrypt Zip archives on Windows.
 OPTION(ENABLE_CNG "Enable the use of CNG(Crypto Next Generation)" ON)
 
@@ -257,7 +257,7 @@
 
 SET(POSIX_REGEX_LIB "AUTO" CACHE STRING "Choose what library should provide POSIX regular expression support")
 SET(ENABLE_SAFESEH "AUTO" CACHE STRING "Enable use of /SAFESEH linker flag (MSVC only)")
-SET(WINDOWS_VERSION "WIN7" CACHE STRING "Set Windows version to use (Windows only)")
+SET(WINDOWS_VERSION "WIN10" CACHE STRING "Set Windows version to use (Windows only)")
 
 IF(ENABLE_COVERAGE)
        include(LibarchiveCodeCoverage)
@@ -268,7 +268,11 @@
 ENDIF(ENABLE_TEST)
 
 IF(WIN32)
-  IF(WINDOWS_VERSION STREQUAL "WIN8")
+  IF(WINDOWS_VERSION STREQUAL "WIN10")
+    SET(NTDDI_VERSION 0x0A000000)
+    SET(_WIN32_WINNT 0x0A00)
+    SET(WINVER 0x0A00)
+  ELSEIF(WINDOWS_VERSION STREQUAL "WIN8")
     SET(NTDDI_VERSION 0x06020000)
     SET(_WIN32_WINNT 0x0602)
     SET(WINVER 0x0602)
@@ -292,12 +296,12 @@
     SET(NTDDI_VERSION 0x05010000)
     SET(_WIN32_WINNT 0x0501)
     SET(WINVER 0x0501)
-  ELSE(WINDOWS_VERSION STREQUAL "WIN8")
+  ELSE(WINDOWS_VERSION STREQUAL "WIN10")
     # Default to Windows Server 2003 API if we don't recognize the specifier
     SET(NTDDI_VERSION 0x05020000)
     SET(_WIN32_WINNT 0x0502)
     SET(WINVER 0x0502)
-  ENDIF(WINDOWS_VERSION STREQUAL "WIN8")
+  ENDIF(WINDOWS_VERSION STREQUAL "WIN10")
 ENDIF(WIN32)
 
 IF(MSVC)
@@ -632,8 +636,15 @@
     SET(ZSTD_FIND_QUIETLY TRUE)
   ENDIF (ZSTD_INCLUDE_DIR)
 
-  FIND_PATH(ZSTD_INCLUDE_DIR zstd.h)
-  FIND_LIBRARY(ZSTD_LIBRARY NAMES zstd libzstd)
+  IF(0) # CMake does not let pkg-config override its search paths.
+  IF(UNIX)
+    FIND_PACKAGE(PkgConfig QUIET)
+    PKG_SEARCH_MODULE(PC_ZSTD libzstd)
+  ENDIF()
+  ENDIF()
+
+  FIND_PATH(ZSTD_INCLUDE_DIR zstd.h HINTS ${PC_ZSTD_INCLUDEDIR} ${PC_ZSTD_INCLUDE_DIRS})
+  FIND_LIBRARY(ZSTD_LIBRARY NAMES zstd libzstd HINTS ${PC_ZSTD_LIBDIR} ${PC_ZSTD_LIBRARY_DIRS})
   INCLUDE(FindPackageHandleStandardArgs)
   FIND_PACKAGE_HANDLE_STANDARD_ARGS(ZSTD DEFAULT_MSG ZSTD_LIBRARY ZSTD_INCLUDE_DIR)
 ELSE(ENABLE_ZSTD)
@@ -1284,9 +1295,10 @@
   #
   # If requested, try finding library for PCREPOSIX
   #
-  IF(ENABLE_LibGCC)
-    FIND_PACKAGE(LibGCC)
+  IF(ENABLE_LIBGCC)
+    FIND_PACKAGE(LIBGCC)
   ELSE()
+    MESSAGE(FATAL_ERROR "libgcc not found.")
     SET(LIBGCC_FOUND FALSE) # Override cached value
   ENDIF()
   IF(ENABLE_PCREPOSIX)
@@ -1996,6 +2008,19 @@
 
 CHECK_CRYPTO_WIN("MD5;SHA1;SHA256;SHA384;SHA512")
 
+IF(0) # CMake does not build libarchive's shared library.
+# Check visibility annotations
+SET(OLD_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}")
+SET(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -fvisibility=hidden -Werror")
+CHECK_C_SOURCE_COMPILES("void __attribute__((visibility(\"default\"))) foo(void);
+int main() { return 0; }" HAVE_VISIBILITY_ATTR)
+IF (HAVE_VISIBILITY_ATTR)
+  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden")
+  ADD_DEFINITIONS(-D__LIBARCHIVE_ENABLE_VISIBILITY)
+ENDIF(HAVE_VISIBILITY_ATTR)
+SET(CMAKE_REQUIRED_FLAGS "${OLD_CMAKE_REQUIRED_FLAGS}")
+ENDIF()
+
 # Generate "config.h" from "build/cmake/config.h.in"
 CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/build/cmake/config.h.in
 	${CMAKE_CURRENT_BINARY_DIR}/config.h)
diff --git a/Utilities/cmlibarchive/build/cmake/FindLibGCC.cmake b/Utilities/cmlibarchive/build/cmake/FindLIBGCC.cmake
similarity index 100%
rename from Utilities/cmlibarchive/build/cmake/FindLibGCC.cmake
rename to Utilities/cmlibarchive/build/cmake/FindLIBGCC.cmake
diff --git a/Utilities/cmlibarchive/build/cmake/config.h.in b/Utilities/cmlibarchive/build/cmake/config.h.in
index 72467a5..e44a514 100644
--- a/Utilities/cmlibarchive/build/cmake/config.h.in
+++ b/Utilities/cmlibarchive/build/cmake/config.h.in
@@ -147,13 +147,13 @@
 #cmakedefine ARCHIVE_XATTR_LINUX 1
 
 /* Version number of bsdcpio */
-#cmakedefine BSDCPIO_VERSION_STRING "${BSDCPIO_VERSION_STRING}"
+#cmakedefine BSDCPIO_VERSION_STRING "@BSDCPIO_VERSION_STRING@"
 
 /* Version number of bsdtar */
-#cmakedefine BSDTAR_VERSION_STRING "${BSDTAR_VERSION_STRING}"
+#cmakedefine BSDTAR_VERSION_STRING "@BSDTAR_VERSION_STRING@"
 
 /* Version number of bsdcat */
-#cmakedefine BSDCAT_VERSION_STRING "${BSDCAT_VERSION_STRING}"
+#cmakedefine BSDCAT_VERSION_STRING "@BSDCAT_VERSION_STRING@"
 
 /* Define to 1 if you have the `acl_create_entry' function. */
 #cmakedefine HAVE_ACL_CREATE_ENTRY 1
@@ -1012,13 +1012,13 @@
 #cmakedefine HAVE__MKGMTIME64 1
 
 /* Define as const if the declaration of iconv() needs const. */
-#define ICONV_CONST ${ICONV_CONST}
+#define ICONV_CONST @ICONV_CONST@
 
 /* Version number of libarchive as a single integer */
-#cmakedefine LIBARCHIVE_VERSION_NUMBER "${LIBARCHIVE_VERSION_NUMBER}"
+#cmakedefine LIBARCHIVE_VERSION_NUMBER "@LIBARCHIVE_VERSION_NUMBER@"
 
 /* Version number of libarchive */
-#cmakedefine LIBARCHIVE_VERSION_STRING "${LIBARCHIVE_VERSION_STRING}"
+#cmakedefine LIBARCHIVE_VERSION_STRING "@LIBARCHIVE_VERSION_STRING@"
 
 /* Define to 1 if `lstat' dereferences a symlink specified with a trailing
    slash. */
@@ -1036,7 +1036,7 @@
 #cmakedefine NO_MINUS_C_MINUS_O 1
 
 /* The size of `wchar_t', as computed by sizeof. */
-#cmakedefine SIZEOF_WCHAR_T ${SIZEOF_WCHAR_T}
+#cmakedefine SIZEOF_WCHAR_T @SIZEOF_WCHAR_T@
 
 /* Define to 1 if strerror_r returns char *. */
 #cmakedefine STRERROR_R_CHAR_P 1
@@ -1072,56 +1072,56 @@
 #endif /* SAFE_TO_DEFINE_EXTENSIONS */
 
 /* Version number of package */
-#cmakedefine VERSION "${VERSION}"
+#cmakedefine VERSION "@VERSION@"
 
 /* Number of bits in a file offset, on hosts where this is settable. */
-#cmakedefine _FILE_OFFSET_BITS ${_FILE_OFFSET_BITS}
+#cmakedefine _FILE_OFFSET_BITS @_FILE_OFFSET_BITS@
 
 /* Define to 1 to make fseeko visible on some hosts (e.g. glibc 2.2). */
 #cmakedefine _LARGEFILE_SOURCE 1
 
 /* Define for large files, on AIX-style hosts. */
-#cmakedefine _LARGE_FILES ${_LARGE_FILES}
+#cmakedefine _LARGE_FILES @_LARGE_FILES@
 
 /* Define to control Windows SDK version */
 #ifndef NTDDI_VERSION
-#cmakedefine NTDDI_VERSION ${NTDDI_VERSION}
+#cmakedefine NTDDI_VERSION @NTDDI_VERSION@
 #endif // NTDDI_VERSION
 
 #ifndef _WIN32_WINNT
-#cmakedefine _WIN32_WINNT ${_WIN32_WINNT}
+#cmakedefine _WIN32_WINNT @_WIN32_WINNT@
 #endif // _WIN32_WINNT
 
 #ifndef WINVER
-#cmakedefine WINVER ${WINVER}
+#cmakedefine WINVER @WINVER@
 #endif // WINVER
 
 /* Define to empty if `const' does not conform to ANSI C. */
-#cmakedefine const ${const}
+#cmakedefine const @const@
 
 /* Define to `int' if <sys/types.h> doesn't define. */
-#cmakedefine gid_t ${gid_t}
+#cmakedefine gid_t @gid_t@
 
 /* Define to `unsigned long' if <sys/types.h> does not define. */
-#cmakedefine id_t ${id_t}
+#cmakedefine id_t @id_t@
 
 /* Define to `int' if <sys/types.h> does not define. */
-#cmakedefine mode_t ${mode_t}
+#cmakedefine mode_t @mode_t@
 
 /* Define to `long long' if <sys/types.h> does not define. */
-#cmakedefine off_t ${off_t}
+#cmakedefine off_t @off_t@
 
 /* Define to `int' if <sys/types.h> doesn't define. */
-#cmakedefine pid_t ${pid_t}
+#cmakedefine pid_t @pid_t@
 
 /* Define to `unsigned int' if <sys/types.h> does not define. */
-#cmakedefine size_t ${size_t}
+#cmakedefine size_t @size_t@
 
 /* Define to `int' if <sys/types.h> does not define. */
-#cmakedefine ssize_t ${ssize_t}
+#cmakedefine ssize_t @ssize_t@
 
 /* Define to `int' if <sys/types.h> doesn't define. */
-#cmakedefine uid_t ${uid_t}
+#cmakedefine uid_t @uid_t@
 
 #include <cm3p/kwiml/int.h>
 
diff --git a/Utilities/cmlibarchive/build/pkgconfig/libarchive.pc.in b/Utilities/cmlibarchive/build/pkgconfig/libarchive.pc.in
index 4b631e6..1f51e77 100644
--- a/Utilities/cmlibarchive/build/pkgconfig/libarchive.pc.in
+++ b/Utilities/cmlibarchive/build/pkgconfig/libarchive.pc.in
@@ -10,3 +10,4 @@
 Cflags.private: -DLIBARCHIVE_STATIC
 Libs: -L${libdir} -larchive
 Libs.private: @LIBS@
+Requires.private: @LIBSREQUIRED@
diff --git a/Utilities/cmlibarchive/build/version b/Utilities/cmlibarchive/build/version
index 102ec29..1af1bec 100644
--- a/Utilities/cmlibarchive/build/version
+++ b/Utilities/cmlibarchive/build/version
@@ -1 +1 @@
-3006000
+3006002
diff --git a/Utilities/cmlibarchive/libarchive/CMakeLists.txt b/Utilities/cmlibarchive/libarchive/CMakeLists.txt
index feb8697..bee69c2 100644
--- a/Utilities/cmlibarchive/libarchive/CMakeLists.txt
+++ b/Utilities/cmlibarchive/libarchive/CMakeLists.txt
@@ -5,6 +5,10 @@
 #
 ############################################
 
+if (ANDROID)
+  include_directories(${PROJECT_SOURCE_DIR}/contrib/android/include)
+endif()
+
 # Public headers
 SET(include_HEADERS
   archive.h
@@ -78,6 +82,7 @@
   archive_read_set_format.c
   archive_read_set_options.c
   archive_read_support_filter_all.c
+  archive_read_support_filter_by_code.c
   archive_read_support_filter_bzip2.c
   archive_read_support_filter_compress.c
   archive_read_support_filter_gzip.c
diff --git a/Utilities/cmlibarchive/libarchive/archive.h b/Utilities/cmlibarchive/libarchive/archive.h
index ac01738..180f3e4 100644
--- a/Utilities/cmlibarchive/libarchive/archive.h
+++ b/Utilities/cmlibarchive/libarchive/archive.h
@@ -36,7 +36,7 @@
  * assert that ARCHIVE_VERSION_NUMBER >= 2012108.
  */
 /* Note: Compiler will complain if this does not match archive_entry.h! */
-#define	ARCHIVE_VERSION_NUMBER 3006000
+#define	ARCHIVE_VERSION_NUMBER 3006002
 
 #include <sys/stat.h>
 #include <stddef.h>  /* for wchar_t */
@@ -120,6 +120,8 @@
 #   define __LA_DECL	__declspec(dllimport)
 #  endif
 # endif
+#elif defined __LIBARCHIVE_ENABLE_VISIBILITY
+#  define __LA_DECL __attribute__((visibility("default")))
 #else
 /* Static libraries or non-Windows needs no special declaration. */
 # define __LA_DECL
@@ -152,7 +154,7 @@
 /*
  * Textual name/version of the library, useful for version displays.
  */
-#define	ARCHIVE_VERSION_ONLY_STRING "3.6.0"
+#define	ARCHIVE_VERSION_ONLY_STRING "3.6.2"
 #define	ARCHIVE_VERSION_STRING "libarchive " ARCHIVE_VERSION_ONLY_STRING
 __LA_DECL const char *	archive_version_string(void);
 
diff --git a/Utilities/cmlibarchive/libarchive/archive_acl.c b/Utilities/cmlibarchive/libarchive/archive_acl.c
index ead7e36..da471a5 100644
--- a/Utilities/cmlibarchive/libarchive/archive_acl.c
+++ b/Utilities/cmlibarchive/libarchive/archive_acl.c
@@ -37,6 +37,10 @@
 #include <wchar.h>
 #endif
 
+#ifdef __clang_analyzer__
+#include <assert.h>
+#endif
+
 #include "archive_acl_private.h"
 #include "archive_entry.h"
 #include "archive_private.h"
@@ -1209,6 +1213,9 @@
 			 * to "user::rwx", etc. valid only for first field
 			 */
 			s = field[0].start;
+			#ifdef __clang_analyzer__
+			assert(s);
+			#endif
 			len = field[0].end - field[0].start;
 			if (*s == L'd' && (len == 1 || (len >= 7
 			    && wmemcmp((s + 1), L"efault", 6) == 0))) {
@@ -1692,6 +1699,9 @@
 			 * to "user::rwx", etc. valid only for first field
 			 */
 			s = field[0].start;
+			#ifdef __clang_analyzer__
+			assert(s);
+			#endif
 			len = field[0].end - field[0].start;
 			if (*s == 'd' && (len == 1 || (len >= 7
 			    && memcmp((s + 1), "efault", 6) == 0))) {
diff --git a/Utilities/cmlibarchive/libarchive/archive_digest.c b/Utilities/cmlibarchive/libarchive/archive_digest.c
index 410df01..3361b19 100644
--- a/Utilities/cmlibarchive/libarchive/archive_digest.c
+++ b/Utilities/cmlibarchive/libarchive/archive_digest.c
@@ -49,16 +49,16 @@
  * Initialize a Message digest.
  */
 static int
-win_crypto_init(Digest_CTX *ctx, ALG_ID algId)
+win_crypto_init(Digest_CTX *ctx, DWORD prov, ALG_ID algId)
 {
 
 	ctx->valid = 0;
 	if (!CryptAcquireContext(&ctx->cryptProv, NULL, NULL,
-	    PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
+	    prov, CRYPT_VERIFYCONTEXT)) {
 		if (GetLastError() != (DWORD)NTE_BAD_KEYSET)
 			return (ARCHIVE_FAILED);
 		if (!CryptAcquireContext(&ctx->cryptProv, NULL, NULL,
-		    PROV_RSA_FULL, CRYPT_NEWKEYSET))
+		    prov, CRYPT_NEWKEYSET))
 			return (ARCHIVE_FAILED);
 	}
 
@@ -243,7 +243,8 @@
 {
   if ((*ctx = EVP_MD_CTX_new()) == NULL)
 	return (ARCHIVE_FAILED);
-  EVP_DigestInit(*ctx, EVP_md5());
+  if (!EVP_DigestInit(*ctx, EVP_md5()))
+	return (ARCHIVE_FAILED);
   return (ARCHIVE_OK);
 }
 
@@ -275,7 +276,7 @@
 static int
 __archive_md5init(archive_md5_ctx *ctx)
 {
-  return (win_crypto_init(ctx, CALG_MD5));
+  return (win_crypto_init(ctx, PROV_RSA_FULL, CALG_MD5));
 }
 
 static int
@@ -434,7 +435,8 @@
 {
   if ((*ctx = EVP_MD_CTX_new()) == NULL)
 	return (ARCHIVE_FAILED);
-  EVP_DigestInit(*ctx, EVP_ripemd160());
+  if (!EVP_DigestInit(*ctx, EVP_ripemd160()))
+	return (ARCHIVE_FAILED);
   return (ARCHIVE_OK);
 }
 
@@ -624,7 +626,8 @@
 {
   if ((*ctx = EVP_MD_CTX_new()) == NULL)
 	return (ARCHIVE_FAILED);
-  EVP_DigestInit(*ctx, EVP_sha1());
+  if (!EVP_DigestInit(*ctx, EVP_sha1()))
+	return (ARCHIVE_FAILED);
   return (ARCHIVE_OK);
 }
 
@@ -656,7 +659,7 @@
 static int
 __archive_sha1init(archive_sha1_ctx *ctx)
 {
-  return (win_crypto_init(ctx, CALG_SHA1));
+  return (win_crypto_init(ctx, PROV_RSA_FULL, CALG_SHA1));
 }
 
 static int
@@ -887,7 +890,8 @@
 {
   if ((*ctx = EVP_MD_CTX_new()) == NULL)
 	return (ARCHIVE_FAILED);
-  EVP_DigestInit(*ctx, EVP_sha256());
+  if (!EVP_DigestInit(*ctx, EVP_sha256()))
+	return (ARCHIVE_FAILED);
   return (ARCHIVE_OK);
 }
 
@@ -915,7 +919,7 @@
 static int
 __archive_sha256init(archive_sha256_ctx *ctx)
 {
-  return (win_crypto_init(ctx, CALG_SHA_256));
+  return (win_crypto_init(ctx, PROV_RSA_AES, CALG_SHA_256));
 }
 
 static int
@@ -1122,7 +1126,8 @@
 {
   if ((*ctx = EVP_MD_CTX_new()) == NULL)
 	return (ARCHIVE_FAILED);
-  EVP_DigestInit(*ctx, EVP_sha384());
+  if (!EVP_DigestInit(*ctx, EVP_sha384()))
+	return (ARCHIVE_FAILED);
   return (ARCHIVE_OK);
 }
 
@@ -1150,7 +1155,7 @@
 static int
 __archive_sha384init(archive_sha384_ctx *ctx)
 {
-  return (win_crypto_init(ctx, CALG_SHA_384));
+  return (win_crypto_init(ctx, PROV_RSA_AES, CALG_SHA_384));
 }
 
 static int
@@ -1381,7 +1386,8 @@
 {
   if ((*ctx = EVP_MD_CTX_new()) == NULL)
 	return (ARCHIVE_FAILED);
-  EVP_DigestInit(*ctx, EVP_sha512());
+  if (!EVP_DigestInit(*ctx, EVP_sha512()))
+	return (ARCHIVE_FAILED);
   return (ARCHIVE_OK);
 }
 
@@ -1409,7 +1415,7 @@
 static int
 __archive_sha512init(archive_sha512_ctx *ctx)
 {
-  return (win_crypto_init(ctx, CALG_SHA_512));
+  return (win_crypto_init(ctx, PROV_RSA_AES, CALG_SHA_512));
 }
 
 static int
diff --git a/Utilities/cmlibarchive/libarchive/archive_entry.c b/Utilities/cmlibarchive/libarchive/archive_entry.c
index ca7a4bd..ae6dc33 100644
--- a/Utilities/cmlibarchive/libarchive/archive_entry.c
+++ b/Utilities/cmlibarchive/libarchive/archive_entry.c
@@ -568,6 +568,13 @@
 	return (entry->ae_stat.aest_nlink);
 }
 
+/* Instead, our caller could have chosen a specific encoding
+ * (archive_mstring_get_mbs, archive_mstring_get_utf8,
+ * archive_mstring_get_wcs).  So we should try multiple
+ * encodings.  Try mbs first because of history, even though
+ * utf8 might be better for pathname portability.
+ * Also omit wcs because of type mismatch (char * versus wchar *)
+ */
 const char *
 archive_entry_pathname(struct archive_entry *entry)
 {
@@ -575,6 +582,13 @@
 	if (archive_mstring_get_mbs(
 	    entry->archive, &entry->ae_pathname, &p) == 0)
 		return (p);
+#if HAVE_EILSEQ  /*{*/
+    if (errno == EILSEQ) {
+	    if (archive_mstring_get_utf8(
+	        entry->archive, &entry->ae_pathname, &p) == 0)
+		    return (p);
+    }
+#endif  /*}*/
 	if (errno == ENOMEM)
 		__archive_errx(1, "No memory");
 	return (NULL);
diff --git a/Utilities/cmlibarchive/libarchive/archive_entry.h b/Utilities/cmlibarchive/libarchive/archive_entry.h
index 221ef80..91ef0c9 100644
--- a/Utilities/cmlibarchive/libarchive/archive_entry.h
+++ b/Utilities/cmlibarchive/libarchive/archive_entry.h
@@ -30,7 +30,7 @@
 #define	ARCHIVE_ENTRY_H_INCLUDED
 
 /* Note: Compiler will complain if this does not match archive.h! */
-#define	ARCHIVE_VERSION_NUMBER 3006000
+#define	ARCHIVE_VERSION_NUMBER 3006002
 
 /*
  * Note: archive_entry.h is for use outside of libarchive; the
@@ -122,6 +122,8 @@
 #   define __LA_DECL	__declspec(dllimport)
 #  endif
 # endif
+#elif defined __LIBARCHIVE_ENABLE_VISIBILITY
+#  define __LA_DECL __attribute__((visibility("default")))
 #else
 /* Static libraries on all platforms and shared libraries on non-Windows. */
 # define __LA_DECL
diff --git a/Utilities/cmlibarchive/libarchive/archive_hmac.c b/Utilities/cmlibarchive/libarchive/archive_hmac.c
index 2a9d04c..012fe15 100644
--- a/Utilities/cmlibarchive/libarchive/archive_hmac.c
+++ b/Utilities/cmlibarchive/libarchive/archive_hmac.c
@@ -230,10 +230,23 @@
 static int
 __hmac_sha1_init(archive_hmac_sha1_ctx *ctx, const uint8_t *key, size_t key_len)
 {
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+	OSSL_PARAM params[2];
+
+	EVP_MAC *mac = EVP_MAC_fetch(NULL, "HMAC", NULL);
+	*ctx = EVP_MAC_CTX_new(mac);
+	if (*ctx == NULL)
+		return -1;
+	EVP_MAC_free(mac);
+	params[0] = OSSL_PARAM_construct_utf8_string("digest", "SHA1", 0);
+	params[1] = OSSL_PARAM_construct_end();
+	EVP_MAC_init(*ctx, key, key_len, params);
+#else
 	*ctx = HMAC_CTX_new();
 	if (*ctx == NULL)
 		return -1;
 	HMAC_Init_ex(*ctx, key, key_len, EVP_sha1(), NULL);
+#endif
 	return 0;
 }
 
@@ -241,22 +254,38 @@
 __hmac_sha1_update(archive_hmac_sha1_ctx *ctx, const uint8_t *data,
     size_t data_len)
 {
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+	EVP_MAC_update(*ctx, data, data_len);
+#else
 	HMAC_Update(*ctx, data, data_len);
+#endif
 }
 
 static void
 __hmac_sha1_final(archive_hmac_sha1_ctx *ctx, uint8_t *out, size_t *out_len)
 {
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+	size_t len = *out_len;
+#else
 	unsigned int len = (unsigned int)*out_len;
+#endif
 
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+	EVP_MAC_final(*ctx, out, &len, *out_len);
+#else
 	HMAC_Final(*ctx, out, &len);
+#endif
 	*out_len = len;
 }
 
 static void
 __hmac_sha1_cleanup(archive_hmac_sha1_ctx *ctx)
 {
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+	EVP_MAC_CTX_free(*ctx);
+#else
 	HMAC_CTX_free(*ctx);
+#endif
 	*ctx = NULL;
 }
 
diff --git a/Utilities/cmlibarchive/libarchive/archive_hmac_private.h b/Utilities/cmlibarchive/libarchive/archive_hmac_private.h
index 13a67d4..50044a0 100644
--- a/Utilities/cmlibarchive/libarchive/archive_hmac_private.h
+++ b/Utilities/cmlibarchive/libarchive/archive_hmac_private.h
@@ -74,9 +74,16 @@
 typedef	struct hmac_sha1_ctx archive_hmac_sha1_ctx;
 
 #elif defined(HAVE_LIBCRYPTO)
+#include <openssl/opensslv.h>
+#include <openssl/hmac.h>
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+typedef EVP_MAC_CTX *archive_hmac_sha1_ctx;
+
+#else
 #include "archive_openssl_hmac_private.h"
 
 typedef	HMAC_CTX* archive_hmac_sha1_ctx;
+#endif
 
 #else
 
diff --git a/Utilities/cmlibarchive/libarchive/archive_match.c b/Utilities/cmlibarchive/libarchive/archive_match.c
index 04747b1..2de0045 100644
--- a/Utilities/cmlibarchive/libarchive/archive_match.c
+++ b/Utilities/cmlibarchive/libarchive/archive_match.c
@@ -606,6 +606,10 @@
 		return (ARCHIVE_FATAL);
 	}
 	r = archive_read_support_format_raw(ar);
+#ifdef __clang_analyzer__
+	/* Tolerate deadcode.DeadStores to avoid modifying upstream.  */
+	(void)r;
+#endif
 	r = archive_read_support_format_empty(ar);
 	if (r != ARCHIVE_OK) {
 		archive_copy_error(&(a->archive), ar);
diff --git a/Utilities/cmlibarchive/libarchive/archive_platform.h b/Utilities/cmlibarchive/libarchive/archive_platform.h
index f87b423..63b255e 100644
--- a/Utilities/cmlibarchive/libarchive/archive_platform.h
+++ b/Utilities/cmlibarchive/libarchive/archive_platform.h
@@ -188,8 +188,9 @@
 
 /*
  * glibc 2.24 deprecates readdir_r
+ * bionic c deprecates readdir_r too
  */
-#if defined(HAVE_READDIR_R) && (!defined(__GLIBC__) || !defined(__GLIBC_MINOR__) || __GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ < 24))
+#if defined(HAVE_READDIR_R) && (!defined(__GLIBC__) || !defined(__GLIBC_MINOR__) || __GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ < 24)) && (!defined(__ANDROID__))
 #define	USE_READDIR_R	1
 #else
 #undef	USE_READDIR_R
diff --git a/Utilities/cmlibarchive/libarchive/archive_ppmd8.c b/Utilities/cmlibarchive/libarchive/archive_ppmd8.c
index d177939..272ca4c 100644
--- a/Utilities/cmlibarchive/libarchive/archive_ppmd8.c
+++ b/Utilities/cmlibarchive/libarchive/archive_ppmd8.c
@@ -4,6 +4,10 @@
 
 #include "archive_platform.h"
 
+#ifdef __clang_analyzer__
+#include <assert.h>
+#endif
+
 #include <string.h>
 
 #include "archive_ppmd8_private.h"
@@ -337,6 +341,9 @@
 
 static void SetSuccessor(CPpmd_State *p, CPpmd_Void_Ref v)
 {
+  #ifdef __clang_analyzer__
+  assert(p);
+  #endif
   (p)->SuccessorLow = (UInt16)((UInt32)(v) & 0xFFFF);
   (p)->SuccessorHigh = (UInt16)(((UInt32)(v) >> 16) & 0xFFFF);
 }
@@ -616,6 +623,11 @@
   /* fixed over Shkarin's code. Maybe it could work without + 1 too. */
   CPpmd_State *ps[PPMD8_MAX_ORDER + 1];
   unsigned numPs = 0;
+
+#ifdef __clang_analyzer__
+  memset(ps, 0, sizeof(ps));
+#endif
+
   
   if (!skip)
     ps[numPs++] = p->FoundState;
diff --git a/Utilities/cmlibarchive/libarchive/archive_read_disk_posix.c b/Utilities/cmlibarchive/libarchive/archive_read_disk_posix.c
index d0e1f35..c964d3f 100644
--- a/Utilities/cmlibarchive/libarchive/archive_read_disk_posix.c
+++ b/Utilities/cmlibarchive/libarchive/archive_read_disk_posix.c
@@ -34,9 +34,6 @@
 #ifdef HAVE_SYS_PARAM_H
 #include <sys/param.h>
 #endif
-#ifdef HAVE_SYS_MOUNT_H
-#include <sys/mount.h>
-#endif
 #ifdef HAVE_SYS_STAT_H
 #include <sys/stat.h>
 #endif
@@ -54,6 +51,8 @@
 #endif
 #ifdef HAVE_LINUX_FS_H
 #include <linux/fs.h>
+#elif HAVE_SYS_MOUNT_H
+#include <sys/mount.h>
 #endif
 /*
  * Some Linux distributions have both linux/ext2_fs.h and ext2fs/ext2_fs.h.
@@ -93,6 +92,10 @@
 #include <sys/ioctl.h>
 #endif
 
+#ifdef __clang_analyzer__
+#include <assert.h>
+#endif
+
 #include "archive.h"
 #include "archive_string.h"
 #include "archive_entry.h"
@@ -109,6 +112,11 @@
 #define O_CLOEXEC	0
 #endif
 
+#if defined(__hpux) && !defined(HAVE_DIRFD)
+#define dirfd(x) ((x)->__dd_fd)
+#define HAVE_DIRFD
+#endif
+
 /*-
  * This is a new directory-walking system that addresses a number
  * of problems I've had with fts(3).  In particular, it has no
@@ -738,6 +746,10 @@
 			else if (errno == EPERM)
 				flags &= ~O_NOATIME;
 		}
+#ifdef __clang_analyzer__
+		/* Tolerate deadcode.DeadStores to avoid modifying upstream. */
+		(void)flags;
+#endif
 #endif
 		if (t->entry_fd < 0) {
 			archive_set_error(&a->archive, errno,
@@ -2098,6 +2110,8 @@
 	struct tree_entry *te;
 
 	te = calloc(1, sizeof(*te));
+	if (te == NULL)
+		__archive_errx(1, "Out of memory");
 	te->next = t->stack;
 	te->parent = t->current;
 	if (te->parent)
@@ -2341,6 +2355,9 @@
 	if (t->stack == t->current && t->current != NULL)
 		t->current = t->current->parent;
 	te = t->stack;
+	#ifdef __clang_analyzer__
+	assert(te);
+	#endif
 	t->stack = te->next;
 	t->dirname_length = te->dirname_length;
 	t->basename = t->path.s + t->dirname_length;
@@ -2428,7 +2445,7 @@
 #else /* HAVE_FDOPENDIR */
 		if (tree_enter_working_dir(t) == 0) {
 			t->d = opendir(".");
-#if HAVE_DIRFD || defined(dirfd)
+#ifdef HAVE_DIRFD
 			__archive_ensure_cloexec_flag(dirfd(t->d));
 #endif
 		}
diff --git a/Utilities/cmlibarchive/libarchive/archive_read_disk_windows.c b/Utilities/cmlibarchive/libarchive/archive_read_disk_windows.c
index ea32e2a..f9d1395 100644
--- a/Utilities/cmlibarchive/libarchive/archive_read_disk_windows.c
+++ b/Utilities/cmlibarchive/libarchive/archive_read_disk_windows.c
@@ -418,8 +418,9 @@
 	    FILE_FLAG_OPEN_REPARSE_POINT;
 	int ret;
 
-	h = CreateFileW(path, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, flag,
-	    NULL);
+	h = CreateFileW(path, 0,
+	    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
+	    OPEN_EXISTING, flag, NULL);
 	if (h == INVALID_HANDLE_VALUE) {
 		la_dosmaperr(GetLastError());
 		return (-1);
@@ -1073,7 +1074,9 @@
 		else
 			flags |= FILE_FLAG_SEQUENTIAL_SCAN;
 		t->entry_fh = CreateFileW(tree_current_access_path(t),
-		    GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, flags, NULL);
+		    GENERIC_READ,
+		    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+		    NULL, OPEN_EXISTING, flags, NULL);
 		if (t->entry_fh == INVALID_HANDLE_VALUE) {
 			la_dosmaperr(GetLastError());
 			archive_set_error(&a->archive, errno,
@@ -2046,7 +2049,8 @@
 	
 	if (sim_lstat && tree_current_is_physical_link(t))
 		flag |= FILE_FLAG_OPEN_REPARSE_POINT;
-	h = CreateFileW(tree_current_access_path(t), 0, FILE_SHARE_READ, NULL,
+	h = CreateFileW(tree_current_access_path(t), 0,
+	    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
 	    OPEN_EXISTING, flag, NULL);
 	if (h == INVALID_HANDLE_VALUE) {
 		la_dosmaperr(GetLastError());
@@ -2275,7 +2279,8 @@
 			} else
 				desiredAccess = GENERIC_READ;
 
-			h = CreateFileW(path, desiredAccess, FILE_SHARE_READ, NULL,
+			h = CreateFileW(path, desiredAccess,
+			    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
 			    OPEN_EXISTING, flag, NULL);
 			if (h == INVALID_HANDLE_VALUE) {
 				la_dosmaperr(GetLastError());
@@ -2337,7 +2342,8 @@
 		if (fd >= 0) {
 			h = (HANDLE)_get_osfhandle(fd);
 		} else {
-			h = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, NULL,
+			h = CreateFileW(path, GENERIC_READ,
+			    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
 			    OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
 			if (h == INVALID_HANDLE_VALUE) {
 				la_dosmaperr(GetLastError());
diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_lz4.c b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_lz4.c
index ae0b080..1e99542 100644
--- a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_lz4.c
+++ b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_lz4.c
@@ -450,7 +450,9 @@
 	chsum = (chsum >> 8) & 0xff;
 	chsum_verifier = read_buf[descriptor_bytes-1] & 0xff;
 	if (chsum != chsum_verifier)
+#ifndef DONT_FAIL_ON_CRC_ERROR
 		goto malformed_error;
+#endif
 
 	__archive_read_filter_consume(self->upstream, descriptor_bytes);
 
@@ -521,7 +523,9 @@
 		unsigned int chsum_block =
 		    archive_le32dec(read_buf + 4 + compressed_size);
 		if (chsum != chsum_block)
+#ifndef DONT_FAIL_ON_CRC_ERROR
 			goto malformed_error;
+#endif
 	}
 
 
@@ -652,10 +656,12 @@
 			    state->xxh32_state);
 			state->xxh32_state = NULL;
 			if (checksum != checksum_stream) {
+#ifndef DONT_FAIL_ON_CRC_ERROR
 				archive_set_error(&self->archive->archive,
 				    ARCHIVE_ERRNO_MISC,
 				    "lz4 stream checksum error");
 				return (ARCHIVE_FATAL);
+#endif
 			}
 		} else if (ret > 0)
 			__archive_xxhash.XXH32_update(state->xxh32_state,
diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_lzop.c b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_lzop.c
index 42e2636..cc1572f 100644
--- a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_lzop.c
+++ b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_lzop.c
@@ -283,7 +283,9 @@
 	else
 		checksum = adler32(adler32(0, NULL, 0), p, len);
 	if (archive_be32dec(p + len) != checksum)
+#ifndef DONT_FAIL_ON_CRC_ERROR
 		goto corrupted;
+#endif
 	__archive_read_filter_consume(self->upstream, len + 4);
 	if (flags & EXTRA_FIELD) {
 		/* Skip extra field */
diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_uu.c b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_uu.c
index 209b2a1..c66c247 100644
--- a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_uu.c
+++ b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_uu.c
@@ -36,6 +36,10 @@
 #include <string.h>
 #endif
 
+#ifdef __clang_analyzer__
+#include <assert.h>
+#endif
+
 #include "archive.h"
 #include "archive_private.h"
 #include "archive_read_private.h"
@@ -467,6 +471,9 @@
 		if (ensure_in_buff_size(self, uudecode,
 		    avail_in + uudecode->in_cnt) != ARCHIVE_OK)
 			return (ARCHIVE_FATAL);
+		#ifdef __clang_analyzer__
+		assert(d);
+		#endif
 		memcpy(uudecode->in_buff + uudecode->in_cnt,
 		    d, avail_in);
 		d = uudecode->in_buff;
diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_xz.c b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_xz.c
index b978eb0..90b0da2 100644
--- a/Utilities/cmlibarchive/libarchive/archive_read_support_filter_xz.c
+++ b/Utilities/cmlibarchive/libarchive/archive_read_support_filter_xz.c
@@ -612,9 +612,11 @@
 	/* Check the crc32 value of the uncompressed data of the current
 	 * member */
 	if (state->crc32 != archive_le32dec(f)) {
+#ifndef DONT_FAIL_ON_CRC_ERROR
 		archive_set_error(&self->archive->archive, ARCHIVE_ERRNO_MISC,
 		    "Lzip: CRC32 error");
 		return (ARCHIVE_FAILED);
+#endif
 	}
 
 	/* Check the uncompressed size of the current member */
diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_7zip.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_7zip.c
index 7d7e702..a4d9dcf 100644
--- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_7zip.c
+++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_7zip.c
@@ -42,6 +42,10 @@
 #include <cm3p/zlib.h>
 #endif
 
+#ifdef __clang_analyzer__
+#include <assert.h>
+#endif
+
 #include "archive.h"
 #include "archive_entry.h"
 #include "archive_entry_locale.h"
@@ -287,6 +291,7 @@
 		const unsigned char	*next_in;
 		int64_t			 avail_in;
 		int64_t			 total_in;
+		int64_t			 stream_in;
 		unsigned char		*next_out;
 		int64_t			 avail_out;
 		int64_t			 total_out;
@@ -756,6 +761,9 @@
 				return (ARCHIVE_FATAL);
 			}
 			symname = mem;
+			#ifdef __clang_analyzer__
+			assert(buff);
+			#endif
 			memcpy(symname+symsize, buff, size);
 			symsize += size;
 		}
@@ -775,7 +783,7 @@
 	}
 
 	/* Set up a more descriptive format name. */
-	sprintf(zip->format_name, "7-Zip");
+	snprintf(zip->format_name, sizeof(zip->format_name), "7-Zip");
 	a->archive.archive_format_name = zip->format_name;
 
 	return (ret);
@@ -986,15 +994,30 @@
 	struct _7zip *zip = (struct _7zip *)(a->format->data);
 	Byte b;
 
-	if (zip->ppstream.avail_in == 0) {
-		archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
-		    "Truncated RAR file data");
-		zip->ppstream.overconsumed = 1;
-		return (0);
+	if (zip->ppstream.avail_in <= 0) {
+		/*
+		 * Ppmd7_DecodeSymbol might require reading multiple bytes
+		 * and we are on boundary;
+		 * last resort to read using __archive_read_ahead.
+		 */
+		ssize_t bytes_avail = 0;
+		const uint8_t* data = __archive_read_ahead(a,
+		    zip->ppstream.stream_in+1, &bytes_avail);
+		if(bytes_avail < zip->ppstream.stream_in+1) {
+			archive_set_error(&a->archive,
+			    ARCHIVE_ERRNO_FILE_FORMAT,
+			    "Truncated 7z file data");
+			zip->ppstream.overconsumed = 1;
+			return (0);
+		}
+		zip->ppstream.next_in++;
+		b = data[zip->ppstream.stream_in];
+	} else {
+		b = *zip->ppstream.next_in++;
 	}
-	b = *zip->ppstream.next_in++;
 	zip->ppstream.avail_in--;
 	zip->ppstream.total_in++;
+	zip->ppstream.stream_in++;
 	return (b);
 }
 
@@ -1485,6 +1508,7 @@
 		}
 		zip->ppstream.next_in = t_next_in;
 		zip->ppstream.avail_in = t_avail_in;
+		zip->ppstream.stream_in = 0;
 		zip->ppstream.next_out = t_next_out;
 		zip->ppstream.avail_out = t_avail_out;
 		if (zip->ppmd7_stat == 0) {
@@ -2483,6 +2507,9 @@
 			if ((p = header_bytes(a, 1)) == NULL)
 				return (-1);
 			ll--;
+			#ifdef __clang_analyzer__
+			(void)*p;
+			#endif
 
 			if ((ll & 1) || ll < zip->numFiles * 4)
 				return (-1);
@@ -2840,8 +2867,10 @@
 	/* CRC check. */
 	if (crc32(0, (const unsigned char *)p + 12, 20)
 	    != archive_le32dec(p + 8)) {
+#ifdef DONT_FAIL_ON_CRC_ERROR
 		archive_set_error(&a->archive, -1, "Header CRC error");
 		return (ARCHIVE_FATAL);
+#endif
 	}
 
 	next_header_offset = archive_le64dec(p + 12);
@@ -2891,8 +2920,10 @@
 		/* Check the EncodedHeader CRC.*/
 		if (r == 0 && zip->header_crc32 != next_header_crc) {
 			archive_set_error(&a->archive, -1,
+#ifndef DONT_FAIL_ON_CRC_ERROR
 			    "Damaged 7-Zip archive");
 			r = -1;
+#endif
 		}
 		if (r == 0) {
 			if (zip->si.ci.folders[0].digest_defined)
@@ -2943,9 +2974,11 @@
 
 		/* Check the Header CRC.*/
 		if (check_header_crc && zip->header_crc32 != next_header_crc) {
+#ifndef DONT_FAIL_ON_CRC_ERROR
 			archive_set_error(&a->archive, -1,
 			    "Malformed 7-Zip archive");
 			return (ARCHIVE_FATAL);
+#endif
 		}
 		break;
 	default:
diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_cab.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_cab.c
index 8742378..6fcfbfc 100644
--- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_cab.c
+++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_cab.c
@@ -996,7 +996,7 @@
 		cab->end_of_entry_cleanup = cab->end_of_entry = 1;
 
 	/* Set up a more descriptive format name. */
-	sprintf(cab->format_name, "CAB %d.%d (%s)",
+	snprintf(cab->format_name, sizeof(cab->format_name), "CAB %d.%d (%s)",
 	    hd->major, hd->minor, cab->entry_cffolder->compname);
 	a->archive.archive_format_name = cab->format_name;
 
@@ -1134,7 +1134,7 @@
 	}
 	if (sumbytes) {
 		int odd = sumbytes & 3;
-		if (sumbytes - odd > 0)
+		if ((int)(sumbytes - odd) > 0)
 			cfdata->sum_calculated = cab_checksum_cfdata_4(
 			    p, sumbytes - odd, cfdata->sum_calculated);
 		if (odd)
@@ -1171,12 +1171,14 @@
 	cfdata->sum_calculated = cab_checksum_cfdata(
 	    cfdata->memimage + CFDATA_cbData, l, cfdata->sum_calculated);
 	if (cfdata->sum_calculated != cfdata->sum) {
+#ifndef DONT_FAIL_ON_CRC_ERROR
 		archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
 		    "Checksum error CFDATA[%d] %" PRIx32 ":%" PRIx32 " in %d bytes",
 		    cab->entry_cffolder->cfdata_index -1,
 		    cfdata->sum, cfdata->sum_calculated,
 		    cfdata->compressed_size);
 		return (ARCHIVE_FAILED);
+#endif
 	}
 	return (ARCHIVE_OK);
 }
diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_iso9660.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_iso9660.c
index 9121166..91b9187 100644
--- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_iso9660.c
+++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_iso9660.c
@@ -1007,7 +1007,8 @@
 		p = b;
 		b += iso9660->logical_block_size;
 		step -= iso9660->logical_block_size;
-		for (; *p != 0 && p < b && p + *p <= b; p += *p) {
+		for (; *p != 0 && p + DR_name_offset < b && p + *p <= b;
+			p += *p) {
 			struct file_info *child;
 
 			/* N.B.: these special directory identifiers
@@ -1756,7 +1757,7 @@
 	size_t name_len;
 	const unsigned char *rr_start, *rr_end;
 	const unsigned char *p;
-	size_t dr_len;
+	size_t dr_len = 0;
 	uint64_t fsize, offset;
 	int32_t location;
 	int flags;
@@ -3014,6 +3015,7 @@
 	uint64_t file_key, parent_key;
 	int hole, parent;
 
+#ifndef __clang_analyzer__ /* It cannot see heap->files remains populated.  */
 	/* Expand our pending files list as necessary. */
 	if (heap->used >= heap->allocated) {
 		struct file_info **new_pending_files;
@@ -3041,6 +3043,7 @@
 		heap->files = new_pending_files;
 		heap->allocated = new_size;
 	}
+#endif
 
 	file_key = file->key = key;
 
diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_lha.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_lha.c
index 1357f9a..8b7bf66 100644
--- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_lha.c
+++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_lha.c
@@ -739,7 +739,7 @@
 	if (lha->directory || lha->compsize == 0)
 		lha->end_of_entry = 1;
 
-	sprintf(lha->format_name, "lha -%c%c%c-",
+	snprintf(lha->format_name, sizeof(lha->format_name), "lha -%c%c%c-",
 	    lha->method[0], lha->method[1], lha->method[2]);
 	a->archive.archive_format_name = lha->format_name;
 
@@ -1039,9 +1039,11 @@
 	}
 
 	if (header_crc != lha->header_crc) {
+#ifndef DONT_FAIL_ON_CRC_ERROR
 		archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
 		    "LHa header CRC error");
 		return (ARCHIVE_FATAL);
+#endif
 	}
 	return (err);
 }
@@ -1107,9 +1109,11 @@
 		return (err);
 
 	if (header_crc != lha->header_crc) {
+#ifndef DONT_FAIL_ON_CRC_ERROR
 		archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
 		    "LHa header CRC error");
 		return (ARCHIVE_FATAL);
+#endif
 	}
 	return (err);
 invalid:
diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_mtree.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_mtree.c
index 88bca76..2bc3ba0 100644
--- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_mtree.c
+++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_mtree.c
@@ -692,7 +692,7 @@
 {
 	const char *p;
 	ssize_t avail, ravail;
-	ssize_t detected_bytes = 0, len, nl;
+	ssize_t len, nl;
 	int entry_cnt = 0, multiline = 0;
 	int form_D = 0;/* The archive is generated by `NetBSD mtree -D'
 			* (In this source we call it `form D') . */
@@ -728,8 +728,6 @@
 			 * character of previous line was '\' character. */
 			if (bid_keyword_list(p, len, 0, 0) <= 0)
 				break;
-			if (multiline == 1)
-				detected_bytes += len;
 			if (p[len-nl-1] != '\\') {
 				if (multiline == 1 &&
 				    ++entry_cnt >= MAX_BID_ENTRY)
@@ -745,7 +743,6 @@
 
 			keywords = bid_entry(p, len, nl, &last_is_path);
 			if (keywords >= 0) {
-				detected_bytes += len;
 				if (form_D == 0) {
 					if (last_is_path)
 						form_D = 1;
@@ -997,9 +994,11 @@
 			struct mtree_entry *alt;
 			alt = (struct mtree_entry *)__archive_rb_tree_find_node(
 			    &mtree->rbtree, entry->name);
-			while (alt->next_dup)
-				alt = alt->next_dup;
-			alt->next_dup = entry;
+			if (alt != NULL) {
+				while (alt->next_dup)
+					alt = alt->next_dup;
+				alt->next_dup = entry;
+			}
 		}
 	}
 
@@ -1074,7 +1073,7 @@
 			continue;
 		/* Non-printable characters are not allowed */
 		for (s = p;s < p + len - 1; s++) {
-			if (!isprint((unsigned char)*s)) {
+			if (!isprint((unsigned char)*s) && *s != '\t') {
 				r = ARCHIVE_FATAL;
 				break;
 			}
@@ -1253,9 +1252,17 @@
 				archive_entry_filetype(entry) == AE_IFDIR) {
 			mtree->fd = open(path, O_RDONLY | O_BINARY | O_CLOEXEC);
 			__archive_ensure_cloexec_flag(mtree->fd);
-			if (mtree->fd == -1 &&
-				(errno != ENOENT ||
-				 archive_strlen(&mtree->contents_name) > 0)) {
+			if (mtree->fd == -1 && (
+#if defined(_WIN32) && !defined(__CYGWIN__)
+        /*
+         * On Windows, attempting to open a file with an
+         * invalid name result in EINVAL (Error 22)
+         */
+				(errno != ENOENT && errno != EINVAL)
+#else
+				errno != ENOENT
+#endif
+        || archive_strlen(&mtree->contents_name) > 0)) {
 				archive_set_error(&a->archive, errno,
 						"Can't open %s", path);
 				r = ARCHIVE_WARN;
diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_rar.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_rar.c
index 5c02a25..41d6cb2 100644
--- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_rar.c
+++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_rar.c
@@ -35,6 +35,8 @@
 #include <cm3p/zlib.h> /* crc32 */
 #endif
 
+#include <assert.h>
+
 #include "archive.h"
 #ifndef HAVE_ZLIB_H
 #include "archive_crc32.h"
@@ -430,7 +432,7 @@
 static int make_table(struct archive_read *, struct huffman_code *);
 static int make_table_recurse(struct archive_read *, struct huffman_code *, int,
                               struct huffman_table_entry *, int, int);
-static int64_t expand(struct archive_read *, int64_t);
+static int expand(struct archive_read *, int64_t *);
 static int copy_from_lzss_window_to_unp(struct archive_read *, const void **,
                                         int64_t, int);
 static const void *rar_read_ahead(struct archive_read *, size_t, ssize_t *);
@@ -1007,9 +1009,11 @@
 
       crc32_val = crc32(0, (const unsigned char *)p + 2, (unsigned)skip - 2);
       if ((crc32_val & 0xffff) != archive_le16dec(p)) {
+#ifndef DONT_FAIL_ON_CRC_ERROR
         archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
           "Header CRC error");
         return (ARCHIVE_FATAL);
+#endif
       }
       __archive_read_consume(a, skip);
       break;
@@ -1065,9 +1069,11 @@
 	      skip -= to_read;
       }
       if ((crc32_val & 0xffff) != crc32_expected) {
+#ifndef DONT_FAIL_ON_CRC_ERROR
 	      archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
 		  "Header CRC error");
 	      return (ARCHIVE_FATAL);
+#endif
       }
       if (head_type == ENDARC_HEAD)
 	      return (ARCHIVE_EOF);
@@ -1432,9 +1438,11 @@
   /* File Header CRC check. */
   crc32_val = crc32(crc32_val, h, (unsigned)(header_size - 7));
   if ((crc32_val & 0xffff) != archive_le16dec(rar_header.crc)) {
+#ifndef DONT_FAIL_ON_CRC_ERROR
     archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
       "Header CRC error");
     return (ARCHIVE_FATAL);
+#endif
   }
   /* If no CRC error, Go on parsing File Header. */
   p = h;
@@ -1952,9 +1960,11 @@
     *size = 0;
     *offset = rar->offset;
     if (rar->file_crc != rar->crc_calculated) {
+#ifndef DONT_FAIL_ON_CRC_ERROR
       archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
                         "File CRC error");
       return (ARCHIVE_FATAL);
+#endif
     }
     rar->entry_eof = 1;
     return (ARCHIVE_EOF);
@@ -1988,7 +1998,7 @@
     return (ARCHIVE_FATAL);
 
   struct rar *rar;
-  int64_t start, end, actualend;
+  int64_t start, end;
   size_t bs;
   int ret = (ARCHIVE_OK), sym, code, lzss_offset, length, i;
 
@@ -2045,9 +2055,11 @@
       *size = 0;
       *offset = rar->offset;
       if (rar->file_crc != rar->crc_calculated) {
+#ifndef DONT_FAIL_ON_CRC_ERROR
         archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
                           "File CRC error");
         return (ARCHIVE_FATAL);
+#endif
       }
       rar->entry_eof = 1;
       return (ARCHIVE_EOF);
@@ -2179,11 +2191,12 @@
         end = rar->filters.filterstart;
       }
 
-      if ((actualend = expand(a, end)) < 0)
-        return ((int)actualend);
+      ret = expand(a, &end);
+      if (ret != ARCHIVE_OK)
+	      return (ret);
 
-      rar->bytes_uncopied = actualend - start;
-      rar->filters.lastend = actualend;
+      rar->bytes_uncopied = end - start;
+      rar->filters.lastend = end;
       if (rar->filters.lastend != rar->filters.filterstart && rar->bytes_uncopied == 0) {
           /* Broken RAR files cause this case.
           * NOTE: If this case were possible on a normal RAR file
@@ -2825,8 +2838,8 @@
   return ret;
 }
 
-static int64_t
-expand(struct archive_read *a, int64_t end)
+static int
+expand(struct archive_read *a, int64_t *end)
 {
   static const unsigned char lengthbases[] =
     {   0,   1,   2,   3,   4,   5,   6,
@@ -2873,16 +2886,19 @@
   struct rar *rar = (struct rar *)(a->format->data);
   struct rar_br *br = &(rar->br);
 
-  if (rar->filters.filterstart < end)
-    end = rar->filters.filterstart;
+  if (rar->filters.filterstart < *end)
+    *end = rar->filters.filterstart;
 
   while (1)
   {
-    if(lzss_position(&rar->lzss) >= end)
-      return end;
+    if(lzss_position(&rar->lzss) >= *end) {
+      return (ARCHIVE_OK);
+    }
 
-    if(rar->is_ppmd_block)
-      return lzss_position(&rar->lzss);
+    if(rar->is_ppmd_block) {
+      *end = lzss_position(&rar->lzss);
+      return (ARCHIVE_OK);
+    }
 
     if ((symbol = read_next_symbol(a, &rar->maincode)) < 0)
       return (ARCHIVE_FATAL);
@@ -2906,7 +2922,8 @@
           goto truncated_data;
         rar->start_new_table = rar_br_bits(br, 1);
         rar_br_consume(br, 1);
-        return lzss_position(&rar->lzss);
+        *end = lzss_position(&rar->lzss);
+        return (ARCHIVE_OK);
       }
       else
       {
@@ -2917,7 +2934,7 @@
     }
     else if(symbol==257)
     {
-      if (!read_filter(a, &end))
+      if (!read_filter(a, end))
           return (ARCHIVE_FATAL);
       continue;
     }
@@ -3200,6 +3217,7 @@
     num = filters->lastfilternum;
 
   prog = filters->progs;
+  assert(num <= numprogs);
   for (i = 0; i < num; i++)
     prog = prog->next;
   if (prog)
@@ -3305,8 +3323,10 @@
   filter->prog = prog;
   filter->globaldatalen = globaldatalen > PROGRAM_SYSTEM_GLOBAL_SIZE ? globaldatalen : PROGRAM_SYSTEM_GLOBAL_SIZE;
   filter->globaldata = calloc(1, filter->globaldatalen);
-  if (!filter->globaldata)
+  if (!filter->globaldata) {
+    free(filter);
     return NULL;
+  }
   if (globaldata)
     memcpy(filter->globaldata, globaldata, globaldatalen);
   if (registers)
@@ -3323,14 +3343,43 @@
   struct rar *rar = (struct rar *)(a->format->data);
   struct rar_filters *filters = &rar->filters;
   struct rar_filter *filter = filters->stack;
-  size_t start = filters->filterstart;
-  size_t end = start + filter->blocklength;
+  struct rar_filter *f;
+  size_t start, end;
+  int64_t tend;
   uint32_t lastfilteraddress;
   uint32_t lastfilterlength;
   int ret;
 
+  if (filters == NULL || filter == NULL)
+    return (0);
+
+  start = filters->filterstart;
+  end = start + filter->blocklength;
+
   filters->filterstart = INT64_MAX;
-  end = (size_t)expand(a, end);
+  tend = (int64_t)end;
+  ret = expand(a, &tend);
+  if (ret != ARCHIVE_OK)
+    return 0;
+
+  /* Check if filter stack was modified in expand() */
+  ret = ARCHIVE_FATAL;
+  f = filters->stack;
+  while (f)
+  {
+    if (f == filter)
+    {
+      ret = ARCHIVE_OK;
+      break;
+    }
+    f = f->next;
+  }
+  if (ret != ARCHIVE_OK)
+    return 0;
+
+  if (tend < 0)
+    return 0;
+  end = (size_t)tend;
   if (end != start + filter->blocklength)
     return 0;
 
diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_rar5.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_rar5.c
index 8850c93..aa7b861 100644
--- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_rar5.c
+++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_rar5.c
@@ -388,7 +388,7 @@
 		return CDE_PARAM;
 
 	cdeque_clear(d);
-	d->arr = malloc(sizeof(void*) * max_capacity_power_of_2);
+	d->arr = malloc(sizeof(*d->arr) * max_capacity_power_of_2);
 
 	return d->arr ? CDE_OK : CDE_ALLOC;
 }
@@ -2821,11 +2821,13 @@
 	    ^ (uint8_t) (*block_size >> 16);
 
 	if(calculated_cksum != hdr->block_cksum) {
+#ifndef DONT_FAIL_ON_CRC_ERROR
 		archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
 		    "Block checksum error: got 0x%x, expected 0x%x",
 		    hdr->block_cksum, calculated_cksum);
 
 		return ARCHIVE_FATAL;
+#endif
 	}
 
 	return ARCHIVE_OK;
@@ -2940,12 +2942,23 @@
 	if(filter_type == FILTER_DELTA) {
 		int channels;
 
-		if(ARCHIVE_OK != (ret = read_consume_bits(ar, rar, p, 5, &channels)))
+		if(ARCHIVE_OK != (ret = read_consume_bits(ar, rar, p, 5, &channels))) {
+			#ifdef __clang_analyzer__
+			/* Tell clang-analyzer that 'filt' does not leak.
+			   add_new_filter passes off ownership.  */
+			free(filt);
+			#endif
 			return ret;
+		}
 
 		filt->channels = channels + 1;
 	}
 
+	#ifdef __clang_analyzer__
+	/* Tell clang-analyzer that 'filt' does not leak.
+	   add_new_filter passes off ownership.  */
+	free(filt);
+	#endif
 	return ARCHIVE_OK;
 }
 
@@ -3911,6 +3924,13 @@
 			case GOOD:
 				/* fallthrough */
 			case BEST:
+				/* No data is returned here. But because a sparse-file aware
+				 * caller (like archive_read_data_into_fd) may treat zero-size
+				 * as a sparse file block, we need to update the offset
+				 * accordingly. At this point the decoder doesn't have any
+				 * pending uncompressed data blocks, so the current position in
+				 * the output file should be last_write_ptr. */
+				if (offset) *offset = rar->cstate.last_write_ptr;
 				return uncompress_file(a);
 			default:
 				archive_set_error(&a->archive,
diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_tar.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_tar.c
index bfdad7f..93c3fd5 100644
--- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_tar.c
+++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_tar.c
@@ -407,14 +407,13 @@
 	/*
 	 * Check format of mode/uid/gid/mtime/size/rdevmajor/rdevminor fields.
 	 */
-	if (bid > 0 && (
-	    validate_number_field(header->mode, sizeof(header->mode)) == 0
+	if (validate_number_field(header->mode, sizeof(header->mode)) == 0
 	    || validate_number_field(header->uid, sizeof(header->uid)) == 0
 	    || validate_number_field(header->gid, sizeof(header->gid)) == 0
 	    || validate_number_field(header->mtime, sizeof(header->mtime)) == 0
 	    || validate_number_field(header->size, sizeof(header->size)) == 0
 	    || validate_number_field(header->rdevmajor, sizeof(header->rdevmajor)) == 0
-	    || validate_number_field(header->rdevminor, sizeof(header->rdevminor)) == 0)) {
+	    || validate_number_field(header->rdevminor, sizeof(header->rdevminor)) == 0) {
 		bid = 0;
 	}
 
@@ -2108,6 +2107,21 @@
 			/* "size" is the size of the data in the entry. */
 			tar->entry_bytes_remaining
 			    = tar_atol10(value, strlen(value));
+			if (tar->entry_bytes_remaining < 0) {
+				tar->entry_bytes_remaining = 0;
+				archive_set_error(&a->archive,
+				    ARCHIVE_ERRNO_MISC,
+				    "Tar size attribute is negative");
+				return (ARCHIVE_FATAL);
+			}
+			if (tar->entry_bytes_remaining == INT64_MAX) {
+				/* Note: tar_atol returns INT64_MAX on overflow */
+				tar->entry_bytes_remaining = 0;
+				archive_set_error(&a->archive,
+				    ARCHIVE_ERRNO_MISC,
+				    "Tar size attribute overflow");
+				return (ARCHIVE_FATAL);
+			}
 			/*
 			 * The "size" pax header keyword always overrides the
 			 * "size" field in the tar header.
diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_xar.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_xar.c
index 2e60cf7..330df58 100644
--- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_xar.c
+++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_xar.c
@@ -624,7 +624,9 @@
 		__archive_read_consume(a, xar->toc_chksum_size);
 		xar->offset += xar->toc_chksum_size;
 		if (r != ARCHIVE_OK)
+#ifndef DONT_FAIL_ON_CRC_ERROR
 			return (ARCHIVE_FATAL);
+#endif
 	}
 
 	/*
@@ -827,10 +829,12 @@
 		    xattr->a_sum.val, xattr->a_sum.len,
 		    xattr->e_sum.val, xattr->e_sum.len);
 		if (r != ARCHIVE_OK) {
+#ifndef DONT_FAIL_ON_CRC_ERROR
 			archive_set_error(&(a->archive), ARCHIVE_ERRNO_MISC,
 			    "Xattr checksum error");
 			r = ARCHIVE_WARN;
 			break;
+#endif
 		}
 		if (xattr->name.s == NULL) {
 			archive_set_error(&(a->archive), ARCHIVE_ERRNO_MISC,
diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_zip.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_zip.c
index 8ad73b6..e126ae3 100644
--- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_zip.c
+++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_zip.c
@@ -1667,7 +1667,7 @@
 	 */
 
 	/* Read magic1,magic2,lzma_params from the ZIPX stream. */
-	if((p = __archive_read_ahead(a, 9, NULL)) == NULL) {
+	if(zip->entry_bytes_remaining < 9 || (p = __archive_read_ahead(a, 9, NULL)) == NULL) {
 		archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
 		    "Truncated lzma data");
 		return (ARCHIVE_FATAL);
diff --git a/Utilities/cmlibarchive/libarchive/archive_string.c b/Utilities/cmlibarchive/libarchive/archive_string.c
index d7f2c46..69458e1 100644
--- a/Utilities/cmlibarchive/libarchive/archive_string.c
+++ b/Utilities/cmlibarchive/libarchive/archive_string.c
@@ -3988,10 +3988,10 @@
 archive_mstring_get_mbs_l(struct archive *a, struct archive_mstring *aes,
     const char **p, size_t *length, struct archive_string_conv *sc)
 {
-	int r, ret = 0;
-
-	(void)r; /* UNUSED */
+	int ret = 0;
 #if defined(_WIN32) && !defined(__CYGWIN__)
+	int r;
+
 	/*
 	 * Internationalization programming on Windows must use Wide
 	 * characters because Windows platform cannot make locale UTF-8.
diff --git a/Utilities/cmlibarchive/libarchive/archive_write.c b/Utilities/cmlibarchive/libarchive/archive_write.c
index 66592e8..27626b5 100644
--- a/Utilities/cmlibarchive/libarchive/archive_write.c
+++ b/Utilities/cmlibarchive/libarchive/archive_write.c
@@ -201,6 +201,10 @@
 	struct archive_write_filter *f;
 
 	f = calloc(1, sizeof(*f));
+
+	if (f == NULL)
+		return (NULL);
+
 	f->archive = _a;
 	f->state = ARCHIVE_WRITE_FILTER_STATE_NEW;
 	if (a->filter_first == NULL)
@@ -548,6 +552,10 @@
 	a->client_data = client_data;
 
 	client_filter = __archive_write_allocate_filter(_a);
+
+	if (client_filter == NULL)
+		return (ARCHIVE_FATAL);
+
 	client_filter->open = archive_write_client_open;
 	client_filter->write = archive_write_client_write;
 	client_filter->close = archive_write_client_close;
diff --git a/Utilities/cmlibarchive/libarchive/archive_write_disk_posix.c b/Utilities/cmlibarchive/libarchive/archive_write_disk_posix.c
index b6d3d0a..bd5180e 100644
--- a/Utilities/cmlibarchive/libarchive/archive_write_disk_posix.c
+++ b/Utilities/cmlibarchive/libarchive/archive_write_disk_posix.c
@@ -1996,6 +1996,8 @@
 		free(a);
 		return (NULL);
 	}
+	a->path_safe.s[0] = 0;
+
 #ifdef HAVE_ZLIB_H
 	a->decmpfs_compression_level = 5;
 #endif
@@ -2793,7 +2795,7 @@
 	char *tail;
 	char *head;
 	int last;
-	char c;
+	char c = '\0';
 	int r;
 	struct stat st;
 	int chdir_fd;
diff --git a/Utilities/cmlibarchive/libarchive/archive_write_disk_windows.c b/Utilities/cmlibarchive/libarchive/archive_write_disk_windows.c
index 1b12a29..88df3ce 100644
--- a/Utilities/cmlibarchive/libarchive/archive_write_disk_windows.c
+++ b/Utilities/cmlibarchive/libarchive/archive_write_disk_windows.c
@@ -1370,6 +1370,7 @@
 		free(a);
 		return (NULL);
 	}
+	a->path_safe.s[0] = 0;
 	return (&a->archive);
 }
 
@@ -2154,6 +2155,8 @@
 				return (ARCHIVE_FAILED);
 			}
 		}
+		if (!c)
+			break;
 		pn[0] = c;
 		pn++;
 	}
@@ -2258,6 +2261,9 @@
 			return (ARCHIVE_FAILED);
 		} else
 			p += 4;
+    /* Network drive path like "\\<server-name>\<share-name>\file" */
+    } else if (p[0] == L'\\' && p[1] == L'\\') {
+        p += 2;
 	}
 
 	/* Skip leading drive letter from archives created
diff --git a/Utilities/cmlibarchive/libarchive/archive_write_open.3 b/Utilities/cmlibarchive/libarchive/archive_write_open.3
index 29bffe4..6bceb96 100644
--- a/Utilities/cmlibarchive/libarchive/archive_write_open.3
+++ b/Utilities/cmlibarchive/libarchive/archive_write_open.3
@@ -218,6 +218,7 @@
 .Fn archive_set_error
 to register an error code and message and
 return
+.Cm ARCHIVE_FATAL .
 .Bl -item -offset indent
 .It
 .Ft typedef int
diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_format_iso9660.c b/Utilities/cmlibarchive/libarchive/archive_write_set_format_iso9660.c
index 3190b46..ebd33c5 100644
--- a/Utilities/cmlibarchive/libarchive/archive_write_set_format_iso9660.c
+++ b/Utilities/cmlibarchive/libarchive/archive_write_set_format_iso9660.c
@@ -50,6 +50,10 @@
 #include <cm3p/zlib.h>
 #endif
 
+#ifdef __clang_analyzer__
+#include <assert.h>
+#endif
+
 #include "archive.h"
 #include "archive_endian.h"
 #include "archive_entry.h"
@@ -6626,6 +6630,11 @@
 		rootent = vdd->rootent;
 	np = rootent;
 	do {
+		#ifdef __clang_analyzer__
+		/* Tell clang-analyzer that pathtbl[depth] is in bounds.  */
+		assert(depth < vdd->max_depth);
+		#endif
+
 		/* Register current directory to pathtable. */
 		path_table_add_entry(&(vdd->pathtbl[depth]), np);
 
diff --git a/Utilities/cmlibarchive/libarchive/archive_write_set_format_pax.c b/Utilities/cmlibarchive/libarchive/archive_write_set_format_pax.c
index 5291149..cf1f477 100644
--- a/Utilities/cmlibarchive/libarchive/archive_write_set_format_pax.c
+++ b/Utilities/cmlibarchive/libarchive/archive_write_set_format_pax.c
@@ -1717,7 +1717,7 @@
 	 * to having clients override it.
 	 */
 #if HAVE_GETPID && 0  /* Disable this for now; see above comment. */
-	sprintf(buff, "PaxHeader.%d", getpid());
+	snprintf(buff, sizeof(buff), "PaxHeader.%d", getpid());
 #else
 	/* If the platform can't fetch the pid, don't include it. */
 	strcpy(buff, "PaxHeader");
diff --git a/Utilities/cmlibarchive/libarchive/cpio.5 b/Utilities/cmlibarchive/libarchive/cpio.5
index 837a456..c71018b 100644
--- a/Utilities/cmlibarchive/libarchive/cpio.5
+++ b/Utilities/cmlibarchive/libarchive/cpio.5
@@ -354,7 +354,7 @@
 It appeared in 1977 as part of PWB/UNIX 1.0, the
 .Dq Programmer's Work Bench
 derived from
-.At 6th Edition UNIX
+.At v6
 that was used internally at AT&T.
 Both the new binary and old character formats were in use
 by 1980, according to the System III source released
diff --git a/Utilities/cmlibarchive/libarchive/filter_fork_posix.c b/Utilities/cmlibarchive/libarchive/filter_fork_posix.c
index ac255c4..62085a7 100644
--- a/Utilities/cmlibarchive/libarchive/filter_fork_posix.c
+++ b/Utilities/cmlibarchive/libarchive/filter_fork_posix.c
@@ -76,7 +76,7 @@
 __archive_create_child(const char *cmd, int *child_stdin, int *child_stdout,
 		pid_t *out_child)
 {
-	pid_t child;
+	pid_t child = -1;
 	int stdin_pipe[2], stdout_pipe[2], tmp;
 #if HAVE_POSIX_SPAWNP
 	posix_spawn_file_actions_t actions;
diff --git a/Utilities/cmliblzma/liblzma/common/index.c b/Utilities/cmliblzma/liblzma/common/index.c
index a41e8f3..4c463ec 100644
--- a/Utilities/cmliblzma/liblzma/common/index.c
+++ b/Utilities/cmliblzma/liblzma/common/index.c
@@ -263,6 +263,9 @@
 		up = ctz32(tree->count) + 2;
 		do {
 			node = node->parent;
+			#ifdef __clang_analyzer__
+			assert(node);
+			#endif
 		} while (--up > 0);
 
 		// Rotate left using node as the rotation root.
diff --git a/Utilities/cmliblzma/liblzma/common/index_encoder.c b/Utilities/cmliblzma/liblzma/common/index_encoder.c
index ac97d0c..5e822cb 100644
--- a/Utilities/cmliblzma/liblzma/common/index_encoder.c
+++ b/Utilities/cmliblzma/liblzma/common/index_encoder.c
@@ -237,12 +237,15 @@
 
 	// Do the actual encoding. This should never fail, but store
 	// the original *out_pos just in case.
+#ifndef __clang_analyzer__ // Hide unreachable code from clang-analyzer.
 	const size_t out_start = *out_pos;
+#endif
 	lzma_ret ret = index_encode(&coder, NULL, NULL, NULL, 0,
 			out, out_pos, out_size, LZMA_RUN);
 
 	if (ret == LZMA_STREAM_END) {
 		ret = LZMA_OK;
+#ifndef __clang_analyzer__ // Hide unreachable code from clang-analyzer.
 	} else {
 		// We should never get here, but just in case, restore the
 		// output position and set the error accordingly if something
@@ -250,6 +253,7 @@
 		assert(0);
 		*out_pos = out_start;
 		ret = LZMA_PROG_ERROR;
+#endif
 	}
 
 	return ret;
diff --git a/Utilities/cmlibrhash/librhash/hex.c b/Utilities/cmlibrhash/librhash/hex.c
index f0bbf04..cfd5892 100644
--- a/Utilities/cmlibrhash/librhash/hex.c
+++ b/Utilities/cmlibrhash/librhash/hex.c
@@ -110,6 +110,9 @@
 {
 #define B64_CHUNK_SIZE 120
 	char buffer[164];
+	#ifdef __clang_analyzer__
+	memset(buffer, 0, sizeof(buffer));
+	#endif
 	assert((BASE64_LENGTH(B64_CHUNK_SIZE) + 4) <= sizeof(buffer));
 	assert((B64_CHUNK_SIZE % 6) == 0);
 	if (url_encode) {
diff --git a/Utilities/cmlibuv/src/unix/tty.c b/Utilities/cmlibuv/src/unix/tty.c
index 44fdb9c..d794bd5 100644
--- a/Utilities/cmlibuv/src/unix/tty.c
+++ b/Utilities/cmlibuv/src/unix/tty.c
@@ -354,6 +354,10 @@
   socklen_t len;
   int type;
 
+  #ifdef __clang_analyzer__
+  memset(&ss, 0, sizeof(ss));
+  #endif
+
   if (file < 0)
     return UV_UNKNOWN_HANDLE;
 
diff --git a/Utilities/cmlibuv/src/unix/udp.c b/Utilities/cmlibuv/src/unix/udp.c
index 4d985b8..83acf13 100644
--- a/Utilities/cmlibuv/src/unix/udp.c
+++ b/Utilities/cmlibuv/src/unix/udp.c
@@ -194,6 +194,12 @@
   int flags;
   size_t k;
 
+  #ifdef __clang_analyzer__
+  /* Tell clang-analyzer the array is initialized.
+     The part we use is initialized below.  */
+  memset(iov, 0, sizeof(iov));
+  #endif
+
   /* prepare structures for recvmmsg */
   chunks = buf->len / UV__UDP_DGRAM_MAXSIZE;
   if (chunks > ARRAY_SIZE(iov))
diff --git a/Utilities/cmnghttp2/lib/nghttp2_buf.c b/Utilities/cmnghttp2/lib/nghttp2_buf.c
index a328447..ce51251 100644
--- a/Utilities/cmnghttp2/lib/nghttp2_buf.c
+++ b/Utilities/cmnghttp2/lib/nghttp2_buf.c
@@ -26,6 +26,10 @@
 
 #include <stdio.h>
 
+#ifdef __clang_analyzer__
+#include <assert.h>
+#endif
+
 #include "nghttp2_helper.h"
 #include "nghttp2_debug.h"
 
@@ -386,6 +390,10 @@
     return rv;
   }
 
+#ifdef __clang_analyzer__
+  assert(bufs->cur->buf.last);
+#endif
+
   *bufs->cur->buf.last++ = b;
 
   return 0;
@@ -399,6 +407,10 @@
     return rv;
   }
 
+#ifdef __clang_analyzer__
+  assert(bufs->cur->buf.last);
+#endif
+
   *bufs->cur->buf.last = b;
 
   return 0;
@@ -412,6 +424,10 @@
     return rv;
   }
 
+#ifdef __clang_analyzer__
+  assert(bufs->cur->buf.last);
+#endif
+
   *bufs->cur->buf.last++ |= b;
 
   return 0;
diff --git a/Utilities/cmzlib/gzread.c b/Utilities/cmzlib/gzread.c
index 22052dd..e3519e6 100644
--- a/Utilities/cmzlib/gzread.c
+++ b/Utilities/cmzlib/gzread.c
@@ -434,6 +434,12 @@
         return 0;
     }
 
+#ifdef __clang_analyzer__
+    /* clang-analyzer does not see size==0 through len==0 below. */
+    if (!size)
+        return 0;
+#endif
+
     /* read len or fewer bytes to buf, return the number of full items read */
     return len ? gz_read(state, buf, len) / size : 0;
 }
diff --git a/Utilities/cmzlib/gzwrite.c b/Utilities/cmzlib/gzwrite.c
index a8ffc8f..33f4949 100644
--- a/Utilities/cmzlib/gzwrite.c
+++ b/Utilities/cmzlib/gzwrite.c
@@ -305,6 +305,12 @@
         return 0;
     }
 
+#ifdef __clang_analyzer__
+    /* clang-analyzer does not see size==0 through len==0 below. */
+    if (!size)
+        return 0;
+#endif
+
     /* write len bytes to buf, return the number of full items written */
     return len ? gz_write(state, buf, len) / size : 0;
 }
diff --git a/Utilities/cmzstd/lib/common/bitstream.h b/Utilities/cmzstd/lib/common/bitstream.h
index 2e5a933..136a188 100644
--- a/Utilities/cmzstd/lib/common/bitstream.h
+++ b/Utilities/cmzstd/lib/common/bitstream.h
@@ -14,6 +14,8 @@
 #ifndef BITSTREAM_H_MODULE
 #define BITSTREAM_H_MODULE
 
+#include <assert.h>
+
 #if defined (__cplusplus)
 extern "C" {
 #endif
diff --git a/Utilities/cmzstd/lib/compress/fse_compress.c b/Utilities/cmzstd/lib/compress/fse_compress.c
index b4297ec..1b6a076 100644
--- a/Utilities/cmzstd/lib/compress/fse_compress.c
+++ b/Utilities/cmzstd/lib/compress/fse_compress.c
@@ -646,6 +646,10 @@
     void* scratchBuffer = (void*)(CTable + CTableSize);
     size_t const scratchBufferSize = wkspSize - (CTableSize * sizeof(FSE_CTable));
 
+#ifdef __clang_analyzer__
+    memset(norm, 0, sizeof(norm));
+#endif
+
     /* init conditions */
     if (wkspSize < FSE_COMPRESS_WKSP_SIZE_U32(tableLog, maxSymbolValue)) return ERROR(tableLog_tooLarge);
     if (srcSize <= 1) return 0;  /* Not compressible */
diff --git a/Utilities/cmzstd/lib/dictBuilder/divsufsort.c b/Utilities/cmzstd/lib/dictBuilder/divsufsort.c
index a2870fb..8d52b18 100644
--- a/Utilities/cmzstd/lib/dictBuilder/divsufsort.c
+++ b/Utilities/cmzstd/lib/dictBuilder/divsufsort.c
@@ -40,6 +40,10 @@
 #include <stdio.h>
 #include <stdlib.h>
 
+#ifdef __clang_analyzer__
+#include <string.h>
+#endif
+
 #include "divsufsort.h"
 
 /*- Constants -*/
@@ -1119,6 +1123,9 @@
 
   v = b - SA - 1;
   for(c = first, d = a - 1; c <= d; ++c) {
+    #ifdef __clang_analyzer__
+    assert(c);
+    #endif
     if((0 <= (s = *c - depth)) && (ISA[s] == v)) {
       *++d = s;
       ISA[s] = d - SA;
@@ -1184,6 +1191,10 @@
   int limit, next;
   int ssize, trlink = -1;
 
+  #ifdef __clang_analyzer__
+  memset(stack, 0, sizeof(stack));
+  #endif
+
   for(ssize = 0, limit = tr_ilg(last - first);;) {
 
     if(limit < 0) {
diff --git a/bootstrap b/bootstrap
index ea9816c..109e450 100755
--- a/bootstrap
+++ b/bootstrap
@@ -80,6 +80,7 @@
 cmake_bootstrap_system_libs=""
 cmake_bootstrap_qt_gui=""
 cmake_bootstrap_qt_qmake=""
+cmake_bootstrap_debugger=""
 cmake_sphinx_info=""
 cmake_sphinx_man=""
 cmake_sphinx_html=""
@@ -307,6 +308,7 @@
   cmBuildCommand \
   cmCMakeLanguageCommand \
   cmCMakeMinimumRequired \
+  cmList \
   cmCMakePath \
   cmCMakePathCommand \
   cmCMakePolicyCommand \
@@ -337,6 +339,7 @@
   cmELF \
   cmEnableLanguageCommand \
   cmEnableTestingCommand \
+  cmEvaluatedTargetProperty \
   cmExecProgramCommand \
   cmExecuteProcessCommand \
   cmExpandedCommandArgument \
@@ -569,7 +572,6 @@
   RegularExpression.hxx \
   Status.hxx \
   String.h \
-  String.hxx \
   System.h \
   SystemTools.hxx \
   Terminal.h"
@@ -696,6 +698,9 @@
   --no-qt-gui             do not build the Qt-based GUI (default)
   --qt-qmake=<qmake>      use <qmake> as the qmake executable to find Qt
 
+  --debugger              enable debugger support (default if supported)
+  --no-debugger           disable debugger support
+
   --sphinx-info           build Info manual with Sphinx
   --sphinx-man            build man pages with Sphinx
   --sphinx-html           build html help with Sphinx
@@ -879,7 +884,7 @@
   echo "----------  file   -----------------------"
   cat "${TESTFILE}"
   echo "------------------------------------------"
-  "${COMPILER}" ${FLAGS} "${TESTFILE}" -o "${TMPFILE}"
+  ${COMPILER} ${FLAGS} "${TESTFILE}" -o "${TMPFILE}"
   RES=$?
   if test "${RES}" -ne "0"; then
     echo "Test failed to compile"
@@ -961,6 +966,8 @@
   --qt-gui) cmake_bootstrap_qt_gui="1" ;;
   --no-qt-gui) cmake_bootstrap_qt_gui="0" ;;
   --qt-qmake=*) cmake_bootstrap_qt_qmake=`cmake_arg "$1"` ;;
+  --debugger) cmake_bootstrap_debugger="1" ;;
+  --no-debugger) cmake_bootstrap_debugger="0" ;;
   --sphinx-info) cmake_sphinx_info="1" ;;
   --sphinx-man) cmake_sphinx_man="1" ;;
   --sphinx-html) cmake_sphinx_html="1" ;;
@@ -1205,15 +1212,18 @@
 # If CC is set, use that for compiler, otherwise use list of known compilers
 if test -n "${cmake_toolchain}"; then
   eval cmake_c_compilers="\${cmake_toolchain_${cmake_toolchain}_CC}"
-elif test -n "${CC}"; then
-  cmake_c_compilers="${CC}"
 else
   cmake_c_compilers="${CMAKE_KNOWN_C_COMPILERS}"
 fi
 
-# Check if C compiler works
-TMPFILE=`cmake_tmp_file`
-echo '
+cmake_c_compiler_try_set()
+{
+  test_compiler="$1"
+  test_thread_flags="$2"
+
+  # Check if C compiler works
+  TMPFILE=`cmake_tmp_file`
+  echo '
 #ifdef __cplusplus
 # error "The CMAKE_C_COMPILER is set to a C++ compiler"
 #endif
@@ -1238,23 +1248,34 @@
   return argc - 1;
 }
 ' > "${TMPFILE}.c"
-for std in 11 99 90; do
-  std_flags="`cmake_extract_standard_flags \"${cmake_toolchain}\" C \"${std}\"`"
-  for compiler in ${cmake_c_compilers}; do
+  for std in 11 99 90; do
+    std_flags="`cmake_extract_standard_flags \"${cmake_toolchain}\" C \"${std}\"`"
     for std_flag in '' $std_flags; do
-      for thread_flag in '' $thread_flags; do
-        echo "Checking whether '${compiler} ${cmake_c_flags} ${cmake_ld_flags} ${std_flag} ${thread_flag}' works." >> cmake_bootstrap.log 2>&1
-        if cmake_try_run "${compiler}" "${cmake_c_flags} ${cmake_ld_flags} ${std_flag} ${thread_flag}" \
+      for thread_flag in '' $test_thread_flags; do
+        echo "Checking whether '${test_compiler} ${cmake_c_flags} ${cmake_ld_flags} ${std_flag} ${thread_flag}' works." >> cmake_bootstrap.log 2>&1
+        if cmake_try_run "${test_compiler}" "${cmake_c_flags} ${cmake_ld_flags} ${std_flag} ${thread_flag}" \
           "${TMPFILE}.c" >> cmake_bootstrap.log 2>&1; then
-          cmake_c_compiler="${compiler}"
+          cmake_c_compiler="${test_compiler}"
           cmake_c_flags="${cmake_c_flags} ${std_flag} ${thread_flag}"
-          break 4
+          rm -f "${TMPFILE}.c"
+          return 0
         fi
       done
     done
   done
-done
-rm -f "${TMPFILE}.c"
+  rm -f "${TMPFILE}.c"
+  return 1
+}
+
+if test -n "${CC}"; then
+  cmake_c_compiler_try_set "${CC}" "${thread_flags}"
+else
+  for compiler in ${cmake_c_compilers}; do
+    if cmake_c_compiler_try_set "${compiler}" "${thread_flags}"; then
+      break
+    fi
+  done
+fi
 
 if test -z "${cmake_c_compiler}"; then
   cmake_error 6 "Cannot find appropriate C compiler on this system.
@@ -1273,14 +1294,17 @@
 # If CC is set, use that for compiler, otherwise use list of known compilers
 if test -n "${cmake_toolchain}"; then
   eval cmake_cxx_compilers="\${cmake_toolchain_${cmake_toolchain}_CXX}"
-elif test -n "${CXX}"; then
-  cmake_cxx_compilers="${CXX}"
 else
   cmake_cxx_compilers="${CMAKE_KNOWN_CXX_COMPILERS}"
 fi
 
 # Check if C++ compiler works
-TMPFILE=`cmake_tmp_file`
+cmake_cxx_compiler_try_set()
+{
+  test_compiler="$1"
+  test_thread_flags="$2"
+
+  TMPFILE=`cmake_tmp_file`
 echo '
 #include <iostream>
 #include <memory>
@@ -1359,23 +1383,34 @@
   return 0;
 }
 ' > "${TMPFILE}.cxx"
-for std in 17 14 11; do
-  std_flags="`cmake_extract_standard_flags \"${cmake_toolchain}\" CXX \"${std}\"`"
-  for compiler in ${cmake_cxx_compilers}; do
+  for std in 17 14 11; do
+    std_flags="`cmake_extract_standard_flags \"${cmake_toolchain}\" CXX \"${std}\"`"
     for std_flag in '' $std_flags; do
-      for thread_flag in '' $thread_flags; do
-        echo "Checking whether '${compiler} ${cmake_cxx_flags} ${cmake_ld_flags} ${std_flag} ${thread_flag}' works." >> cmake_bootstrap.log 2>&1
-        if cmake_try_run "${compiler}" "${cmake_cxx_flags} ${cmake_ld_flags} ${std_flag} ${thread_flag}" \
+      for thread_flag in '' $test_thread_flags; do
+        echo "Checking whether '${test_compiler} ${cmake_cxx_flags} ${cmake_ld_flags} ${std_flag} ${thread_flag}' works." >> cmake_bootstrap.log 2>&1
+        if cmake_try_run "${test_compiler}" "${cmake_cxx_flags} ${cmake_ld_flags} ${std_flag} ${thread_flag}" \
           "${TMPFILE}.cxx" >> cmake_bootstrap.log 2>&1; then
-          cmake_cxx_compiler="${compiler}"
+          cmake_cxx_compiler="${test_compiler}"
           cmake_cxx_flags="${cmake_cxx_flags} ${std_flag} ${thread_flag} "
-          break 4
+          rm -f "${TMPFILE}.cxx"
+          return 0
         fi
       done
     done
   done
-done
-rm -f "${TMPFILE}.cxx"
+  rm -f "${TMPFILE}.cxx"
+  return 1
+}
+
+if test -n "${CXX}"; then
+  cmake_cxx_compiler_try_set "${CXX}" "${thread_flags}"
+else
+  for compiler in ${cmake_cxx_compilers}; do
+    if cmake_cxx_compiler_try_set "${compiler}" "${thread_flags}"; then
+      break
+    fi
+  done
+fi
 
 if test -z "${cmake_cxx_compiler}"; then
 cmake_error 7 "Cannot find a C++ compiler that supports both C++11 and the specified C++ flags.
@@ -1434,13 +1469,13 @@
 if test "${cmake_bootstrap_generator}" = "Ninja"; then
   echo '
 rule cc
-  command = "'"${cmake_c_compiler}"'" '"${cmake_ld_flags} ${cmake_c_flags}"' -o $out $in
+  command = '"${cmake_c_compiler}"' '"${cmake_ld_flags} ${cmake_c_flags}"' -o $out $in
 build test: cc test.c
 '>"build.ninja"
 else
   echo '
 test: test.c
-	"'"${cmake_c_compiler}"'" '"${cmake_ld_flags} ${cmake_c_flags}"' -o test test.c
+	'"${cmake_c_compiler}"' '"${cmake_ld_flags} ${cmake_c_flags}"' -o test test.c
 '>"Makefile"
 fi
 echo '
@@ -1958,6 +1993,11 @@
 set (QT_QMAKE_EXECUTABLE "'"${cmake_bootstrap_qt_qmake}"'" CACHE FILEPATH "Location of Qt qmake" FORCE)
 ' >> "${cmake_bootstrap_dir}/InitialCacheFlags.cmake"
 fi
+if test "x${cmake_bootstrap_debugger}" != "x"; then
+  echo '
+set (CMake_ENABLE_DEBUGGER '"${cmake_bootstrap_debugger}"' CACHE BOOL "Enable CMake debugger support" FORCE)
+' >> "${cmake_bootstrap_dir}/InitialCacheFlags.cmake"
+fi
 if test "x${cmake_sphinx_info}" != "x"; then
   echo '
 set (SPHINX_INFO "'"${cmake_sphinx_info}"'" CACHE BOOL "Build Info manual with Sphinx" FORCE)
diff --git a/doxygen.config b/doxygen.config
deleted file mode 100644
index 82add73..0000000
--- a/doxygen.config
+++ /dev/null
@@ -1,697 +0,0 @@
-# Doxyfile 1.1.4-20000625
-
-# This file describes the settings to be used by doxygen for a project
-#
-# All text after a hash (#) is considered a comment and will be ignored
-# The format is:
-#       TAG = value [value, ...]
-# Values that contain spaces should be placed between quotes (" ")
-
-#---------------------------------------------------------------------------
-# General configuration options
-#---------------------------------------------------------------------------
-
-# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
-# by quotes) that should identify the project.
-
-PROJECT_NAME          = CMAKE
-
-# The PROJECT_NUMBER tag can be used to enter a project or revision number.
-# This could be handy for archiving the generated documentation or
-# if some version control system is used.
-
-PROJECT_NUMBER        = 0.0.1
-
-# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
-# base path where the generated documentation will be put.
-# If a relative path is entered, it will be relative to the location
-# where doxygen was started. If left blank the current directory will be used.
-
-OUTPUT_DIRECTORY      = ./Doxygen
-
-# The OUTPUT_LANGUAGE tag is used to specify the language in which all
-# documentation generated by doxygen is written. Doxygen will use this
-# information to generate all constant output in the proper language.
-# The default language is English, other supported languages are:
-# Dutch, French, Italian, Czech, Swedish, German, Finnish, Japanese,
-# Spanish and Russian
-
-OUTPUT_LANGUAGE       = English
-
-# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
-# top of each HTML page. The value NO (the default) enables the index and
-# the value YES disables it.
-
-DISABLE_INDEX         = NO
-
-# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
-# documentation are documented, even if no documentation was available.
-# Private class members and static file members will be hidden unless
-# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
-
-EXTRACT_ALL           = YES
-
-# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
-# will be included in the documentation.
-
-EXTRACT_PRIVATE       = NO
-
-# If the EXTRACT_STATIC tag is set to YES all static members of a file
-# will be included in the documentation.
-
-EXTRACT_STATIC        = YES
-
-# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
-# undocumented members of documented classes, files or namespaces.
-# If set to NO (the default) these members will be included in the
-# various overviews, but no documentation section is generated.
-# This option has no effect if EXTRACT_ALL is enabled.
-
-HIDE_UNDOC_MEMBERS    = NO
-
-# If the HIDE_UNDOC_CLASSESS tag is set to YES, Doxygen will hide all
-# undocumented classes that are normally visible in the class hierarchy.
-# If set to NO (the default) these class will be included in the various
-# overviews. This option has no effect if EXTRACT_ALL is enabled.
-
-HIDE_UNDOC_CLASSES    = NO
-
-# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
-# include brief member descriptions after the members that are listed in
-# the file and class documentation (similar to JavaDoc).
-# Set to NO to disable this.
-
-BRIEF_MEMBER_DESC     = YES
-
-# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
-# the brief description of a member or function before the detailed description.
-# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
-# brief descriptions will be completely suppressed.
-
-REPEAT_BRIEF          = YES
-
-# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
-# Doxygen will generate a detailed section even if there is only a brief
-# description.
-
-ALWAYS_DETAILED_SEC   = NO
-
-# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
-# path before files name in the file list and in the header files. If set
-# to NO the shortest path that makes the file name unique will be used.
-
-FULL_PATH_NAMES       = NO
-
-# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
-# can be used to strip a user defined part of the path. Stripping is
-# only done if one of the specified strings matches the left-hand part of
-# the path.
-
-STRIP_FROM_PATH       =
-
-# The INTERNAL_DOCS tag determines if documentation
-# that is typed after a \internal command is included. If the tag is set
-# to NO (the default) then the documentation will be excluded.
-# Set it to YES to include the internal documentation.
-
-INTERNAL_DOCS         = NO
-
-# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
-# generate a class diagram (in Html and LaTeX) for classes with base or
-# super classes. Setting the tag to NO turns the diagrams off.
-
-CLASS_DIAGRAMS        = YES
-
-# If the SOURCE_BROWSER tag is set to YES then a list of source files will
-# be generated. Documented entities will be cross-referenced with these sources.
-
-SOURCE_BROWSER        = YES
-
-# Setting the INLINE_SOURCES tag to YES will include the body
-# of functions and classes directly in the documentation.
-
-INLINE_SOURCES        = NO
-
-# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
-# doxygen to hide any special comment blocks from generated source code
-# fragments. Normal C and C++ comments will always remain visible.
-
-STRIP_CODE_COMMENTS   = YES
-
-# If the CASE_SENSE_NAMES tag is set to NO (the default) then Doxygen
-# will only generate file names in lower case letters. If set to
-# YES upper case letters are also allowed. This is useful if you have
-# classes or files whose names only differ in case and if your file system
-# supports case sensitive file names.
-
-CASE_SENSE_NAMES      = YES
-
-# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
-# will show members with their full class and namespace scopes in the
-# documentation. If set to YES the scope will be hidden.
-
-HIDE_SCOPE_NAMES      = NO
-
-# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
-# will generate a verbatim copy of the header file for each class for
-# which an include is specified. Set to NO to disable this.
-
-VERBATIM_HEADERS      = YES
-
-# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
-# will put list of the files that are included by a file in the documentation
-# of that file.
-
-SHOW_INCLUDE_FILES    = YES
-
-# If the JAVADOC_AUTOBRIEF tag is set to YES (the default) then Doxygen
-# will interpret the first line (until the first dot) of a JavaDoc-style
-# comment as the brief description. If set to NO, the Javadoc-style will
-# behave just like the Qt-style comments.
-
-JAVADOC_AUTOBRIEF     = NO
-
-# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
-# member inherits the documentation from any documented member that it
-# reimplements.
-
-INHERIT_DOCS          = YES
-
-# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
-# is inserted in the documentation for inline members.
-
-INLINE_INFO           = YES
-
-# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
-# will sort the (detailed) documentation of file and class members
-# alphabetically by member name. If set to NO the members will appear in
-# declaration order.
-
-SORT_MEMBER_DOCS      = YES
-
-# The TAB_SIZE tag can be used to set the number of spaces in a tab.
-# Doxygen uses this value to replace tabs by spaces in code fragments.
-
-TAB_SIZE              = 8
-
-# The ENABLE_SECTIONS tag can be used to enable conditional
-# documentation sections, marked by \if sectionname ... \endif.
-
-ENABLED_SECTIONS      =
-
-#---------------------------------------------------------------------------
-# configuration options related to warning and progress messages
-#---------------------------------------------------------------------------
-
-# The QUIET tag can be used to turn on/off the messages that are generated
-# by doxygen. Possible values are YES and NO. If left blank NO is used.
-
-QUIET                 = NO
-
-# The WARNINGS tag can be used to turn on/off the warning messages that are
-# generated by doxygen. Possible values are YES and NO. If left blank
-# NO is used.
-
-WARNINGS              = YES
-
-# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
-# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
-# automatically be disabled.
-
-WARN_IF_UNDOCUMENTED  = YES
-
-# The WARN_FORMAT tag determines the format of the warning messages that
-# doxygen can produce. The string should contain the $file, $line, and $text
-# tags, which will be replaced by the file and line number from which the
-# warning originated and the warning text.
-
-WARN_FORMAT           = "$file:$line: $text"
-
-#---------------------------------------------------------------------------
-# configuration options related to the input files
-#---------------------------------------------------------------------------
-
-# The INPUT tag can be used to specify the files and/or directories that contain
-# documented source files. You may enter file names like "myfile.cpp" or
-# directories like "/usr/src/myproject". Separate the files or directories
-# with spaces.
-
-INPUT                 = "Source"
-
-# If the value of the INPUT tag contains directories, you can use the
-# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
-# and *.h) to filter out the source-files in the directories. If left
-# blank all files are included.
-
-FILE_PATTERNS         = *.h *.txx *.cxx
-
-# The RECURSIVE tag can be used to turn specify whether or not subdirectories
-# should be searched for input files as well. Possible values are YES and NO.
-# If left blank NO is used.
-
-RECURSIVE             = YES
-
-# The EXCLUDE tag can be used to specify files and/or directories that should
-# excluded from the INPUT source files. This way you can easily exclude a
-# subdirectory from a directory tree whose root is specified with the INPUT tag.
-
-EXCLUDE               =
-
-# If the value of the INPUT tag contains directories, you can use the
-# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
-# certain files from those directories.
-
-EXCLUDE_PATTERNS      =
-
-# The EXAMPLE_PATH tag can be used to specify one or more files or
-# directories that contain example code fragments that are included (see
-# the \include command).
-
-EXAMPLE_PATH          =
-
-# If the value of the EXAMPLE_PATH tag contains directories, you can use the
-# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
-# and *.h) to filter out the source-files in the directories. If left
-# blank all files are included.
-
-EXAMPLE_PATTERNS      =
-
-# The IMAGE_PATH tag can be used to specify one or more files or
-# directories that contain image that are included in the documentation (see
-# the \image command).
-
-IMAGE_PATH            =
-
-# The INPUT_FILTER tag can be used to specify a program that doxygen should
-# invoke to filter for each input file. Doxygen will invoke the filter program
-# by executing (via popen()) the command <filter> <input-file>, where <filter>
-# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
-# input file. Doxygen will then use the output that the filter program writes
-# to standard output.
-
-INPUT_FILTER          =
-
-#---------------------------------------------------------------------------
-# configuration options related to the alphabetical class index
-#---------------------------------------------------------------------------
-
-# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
-# of all compounds will be generated. Enable this if the project
-# contains a lot of classes, structs, unions or interfaces.
-
-ALPHABETICAL_INDEX    = YES
-
-# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
-# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
-# in which this list will be split (can be a number in the range [1..20])
-
-COLS_IN_ALPHA_INDEX   = 3
-
-# In case all classes in a project start with a common prefix, all
-# classes will be put under the same header in the alphabetical index.
-# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
-# should be ignored while generating the index headers.
-
-IGNORE_PREFIX         =
-
-#---------------------------------------------------------------------------
-# configuration options related to the HTML output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
-# generate HTML output.
-
-GENERATE_HTML         = YES
-
-# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `html' will be used as the default path.
-
-HTML_OUTPUT           = html
-
-# The HTML_HEADER tag can be used to specify a personal HTML header for
-# each generated HTML page. If it is left blank doxygen will generate a
-# standard header.
-
-HTML_HEADER           =
-
-# The HTML_FOOTER tag can be used to specify a personal HTML footer for
-# each generated HTML page. If it is left blank doxygen will generate a
-# standard footer.
-
-HTML_FOOTER           =
-
-# The HTML_STYLESHEET tag can be used to specify a user defined cascading
-# style sheet that is used by each HTML page. It can be used to
-# fine-tune the look of the HTML output. If the tag is left blank doxygen
-# will generate a default style sheet
-
-HTML_STYLESHEET       =
-
-# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
-# files or namespaces will be aligned in HTML using tables. If set to
-# NO a bullet list will be used.
-
-HTML_ALIGN_MEMBERS    = YES
-
-# If the GENERATE_HTMLHELP tag is set to YES, additional index files
-# will be generated that can be used as input for tools like the
-# Microsoft HTML help workshop to generate a compressed HTML help file (.chm)
-# of the generated HTML documentation.
-
-GENERATE_HTMLHELP     = NO
-
-#---------------------------------------------------------------------------
-# configuration options related to the LaTeX output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
-# generate Latex output.
-
-GENERATE_LATEX        = YES
-
-# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `latex' will be used as the default path.
-
-LATEX_OUTPUT          = latex
-
-# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
-# LaTeX documents. This may be useful for small projects and may help to
-# save some trees in general.
-
-COMPACT_LATEX         = NO
-
-# The PAPER_TYPE tag can be used to set the paper type that is used
-# by the printer. Possible values are: a4, a4wide, letter, legal and
-# executive. If left blank a4wide will be used.
-
-PAPER_TYPE            = a4wide
-
-# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
-# packages that should be included in the LaTeX output.
-
-EXTRA_PACKAGES        =
-
-# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
-# the generated latex document. The header should contain everything until
-# the first chapter. If it is left blank doxygen will generate a
-# standard header. Notice: only use this tag if you know what you are doing!
-
-LATEX_HEADER          =
-
-# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
-# is prepared for conversion to pdf (using ps2pdf). The pdf file will
-# contain links (just like the HTML output) instead of page references
-# This makes the output suitable for online browsing using a pdf viewer.
-
-PDF_HYPERLINKS        = NO
-
-# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
-# command to the generated LaTeX files. This will instruct LaTeX to keep
-# running if errors occur, instead of asking the user for help.
-# This option is also used when generating formulas in HTML.
-
-LATEX_BATCHMODE       = NO
-
-#---------------------------------------------------------------------------
-# configuration options related to the RTF output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
-# For now this is experimental and is disabled by default. The RTF output
-# is optimised for Word 97 and may not look too pretty with other readers
-# or editors.
-
-GENERATE_RTF          = YES
-
-# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `rtf' will be used as the default path.
-
-RTF_OUTPUT            = rtf
-
-# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
-# RTF documents. This may be useful for small projects and may help to
-# save some trees in general.
-
-COMPACT_RTF           = NO
-
-# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
-# will contain hyperlink fields. The RTF file will
-# contain links (just like the HTML output) instead of page references.
-# This makes the output suitable for online browsing using a WORD or other.
-# programs which support those fields.
-# Note: wordpad (write) and others do not support links.
-
-RTF_HYPERLINKS        = NO
-
-#---------------------------------------------------------------------------
-# configuration options related to the man page output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
-# generate man pages
-
-GENERATE_MAN          = YES
-
-# The MAN_OUTPUT tag is used to specify where the man pages will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `man' will be used as the default path.
-
-MAN_OUTPUT            = man
-
-# The MAN_EXTENSION tag determines the extension that is added to
-# the generated man pages (default is the subroutine's section .3)
-
-MAN_EXTENSION         = .3
-
-#---------------------------------------------------------------------------
-# Configuration options related to the preprocessor
-#---------------------------------------------------------------------------
-
-# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
-# evaluate all C-preprocessor directives found in the sources and include
-# files.
-
-ENABLE_PREPROCESSING  = YES
-
-# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
-# names in the source code. If set to NO (the default) only conditional
-# compilation will be performed.
-
-MACRO_EXPANSION       = YES
-
-# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
-# in the INCLUDE_PATH (see below) will be search if a #include is found.
-
-SEARCH_INCLUDES       = NO
-
-# The INCLUDE_PATH tag can be used to specify one or more directories that
-# contain include files that are not input files but should be processed by
-# the preprocessor.
-
-INCLUDE_PATH          =
-
-# The PREDEFINED tag can be used to specify one or more macro names that
-# are defined before the preprocessor is started (similar to the -D option of
-# gcc). The argument of the tag is a list of macros of the form: name
-# or name=definition (no spaces). If the definition and the = are
-# omitted =1 is assumed.
-
-PREDEFINED  =  "itkNotUsed(x)="\
-               "itkSetMacro(name,type)= \
-                  virtual void Set##name (type _arg);" \
-               "itkGetMacro(name,type)= \
-                  virtual type Get##name ();" \
-               "itkGetConstMacro(name,type)= \
-                  virtual type Get##name () const;" \
-               "itkSetStringMacro(name)= \
-                  virtual void Set##name (const char* _arg);" \
-               "itkGetStringMacro(name)= \
-                  virtual const char* Get##name () const;" \
-               "itkSetClampMacro(name,type,min,max)= \
-                  virtual void Set##name (type _arg);" \
-               "itkSetObjectMacro(name,type)= \
-                  virtual void Set##name (type* _arg);" \
-               "itkGetObjectMacro(name,type)= \
-                  virtual type* Get##name ();" \
-               "itkBooleanMacro(name)= \
-                  virtual void name##On (); \
-                  virtual void name##Off ();" \
-               "itkSetVector2Macro(name,type)= \
-                  virtual void Set##name (type _arg1, type _arg2) \
-                  virtual void Set##name (type _arg[2]);" \
-               "itkGetVector2Macro(name,type)= \
-                  virtual type* Get##name () const; \
-                  virtual void Get##name (type& _arg1, type& _arg2) const; \
-                  virtual void Get##name (type _arg[2]) const;" \
-               "itkSetVector3Macro(name,type)= \
-                  virtual void Set##name (type _arg1, type _arg2, type _arg3) \
-                  virtual void Set##name (type _arg[3]);" \
-               "itkGetVector3Macro(name,type)= \
-                  virtual type* Get##name () const; \
-                  virtual void Get##name (type& _arg1, type& _arg2, type& _arg3) const; \
-                  virtual void Get##name (type _arg[3]) const;" \
-               "itkSetVector4Macro(name,type)= \
-                  virtual void Set##name (type _arg1, type _arg2, type _arg3, type _arg4) \
-                  virtual void Set##name (type _arg[4]);" \
-               "itkGetVector4Macro(name,type)= \
-                  virtual type* Get##name () const; \
-                  virtual void Get##name (type& _arg1, type& _arg2, type& _arg3, type& _arg4) const; \
-                  virtual void Get##name (type _arg[4]) const;" \
-               "itkSetVector6Macro(name,type)= \
-                  virtual void Set##name (type _arg1, type _arg2, type _arg3, type _arg4, type _arg5, type _arg6) \
-                  virtual void Set##name (type _arg[6]);" \
-               "itkGetVector6Macro(name,type)= \
-                  virtual type* Get##name () const; \
-                  virtual void Get##name (type& _arg1, type& _arg2, type& _arg3, type& _arg4, type& _arg5, type& _arg6) const; \
-                  virtual void Get##name (type _arg[6]) const;" \
-               "itkSetVectorMacro(name,type,count)= \
-                  virtual void Set##name(type data[]);" \
-               "itkGetVectorMacro(name,type,count)= \
-                  virtual type* Get##name () const;" \
-               "itkNewMacro(type)= \
-                  static Pointer New();" \
-               "itkTypeMacro(thisClass,superclass)= \
-                  virtual const char *GetClassName() const;" \
-               "ITK_NUMERIC_LIMITS= \
-                  std::numeric_limits"
-
-# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
-# then the macro expansion is limited to the macros specified with the
-# PREDEFINED tag.
-
-EXPAND_ONLY_PREDEF    = YES
-
-#---------------------------------------------------------------------------
-# Configuration::addtions related to external references
-#---------------------------------------------------------------------------
-
-# The TAGFILES tag can be used to specify one or more tagfiles.
-
-TAGFILES              =
-
-# When a file name is specified after GENERATE_TAGFILE, doxygen will create
-# a tag file that is based on the input files it reads.
-
-GENERATE_TAGFILE      =
-
-# If the ALLEXTERNALS tag is set to YES all external classes will be listed
-# in the class index. If set to NO only the inherited external classes
-# will be listed.
-
-ALLEXTERNALS          = NO
-
-# The PERL_PATH should be the absolute path and name of the perl script
-# interpreter (i.e. the result of `which perl').
-
-PERL_PATH             = /usr/bin/perl
-
-#---------------------------------------------------------------------------
-# Configuration options related to the dot tool
-#---------------------------------------------------------------------------
-
-# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
-# available from the path. This tool is part of Graphviz, a graph visualization
-# toolkit from AT&T and Lucent Bell Labs. The other options in this section
-# have no effect if this option is set to NO (the default)
-
-HAVE_DOT              = YES
-
-# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for each documented class showing the direct and
-# indirect inheritance relations. Setting this tag to YES will force the
-# the CLASS_DIAGRAMS tag to NO.
-
-CLASS_GRAPH           = YES
-
-# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for each documented class showing the direct and
-# indirect implementation dependencies (inheritance, containment, and
-# class references variables) of the class with other documented classes.
-
-COLLABORATION_GRAPH   = YES
-
-# If the ENABLE_PREPROCESSING, INCLUDE_GRAPH, and HAVE_DOT tags are set to
-# YES then doxygen will generate a graph for each documented file showing
-# the direct and indirect include dependencies of the file with other
-# documented files.
-
-INCLUDE_GRAPH         = YES
-
-# If the ENABLE_PREPROCESSING, INCLUDED_BY_GRAPH, and HAVE_DOT tags are set to
-# YES then doxygen will generate a graph for each documented header file showing
-# the documented files that directly or indirectly include this file
-
-INCLUDED_BY_GRAPH     = YES
-
-# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
-# will graphical hierarchy of all classes instead of a textual one.
-
-GRAPHICAL_HIERARCHY   = YES
-
-# The tag DOT_PATH can be used to specify the path where the dot tool can be
-# found. If left blank, it is assumed the dot tool can be found on the path.
-
-DOT_PATH              =
-
-# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width
-# (in pixels) of the graphs generated by dot. If a graph becomes larger than
-# this value, doxygen will try to truncate the graph, so that it fits within
-# the specified constraint. Beware that most browsers cannot cope with very
-# large images.
-
-MAX_DOT_GRAPH_WIDTH   = 1024
-
-# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height
-# (in pixels) of the graphs generated by dot. If a graph becomes larger than
-# this value, doxygen will try to truncate the graph, so that it fits within
-# the specified constraint. Beware that most browsers cannot cope with very
-# large images.
-
-MAX_DOT_GRAPH_HEIGHT  = 1024
-
-#---------------------------------------------------------------------------
-# Configuration::addtions related to the search engine
-#---------------------------------------------------------------------------
-
-# The SEARCHENGINE tag specifies whether or not a search engine should be
-# used. If set to NO the values of all tags below this one will be ignored.
-
-SEARCHENGINE          = NO
-
-# The CGI_NAME tag should be the name of the CGI script that
-# starts the search engine (doxysearch) with the correct parameters.
-# A script with this name will be generated by doxygen.
-
-CGI_NAME              = search.cgi
-
-# The CGI_URL tag should be the absolute URL to the directory where the
-# cgi binaries are located. See the documentation of your http daemon for
-# details.
-
-CGI_URL               =
-
-# The DOC_URL tag should be the absolute URL to the directory where the
-# documentation is located. If left blank the absolute path to the
-# documentation, with file:// prepended to it, will be used.
-
-DOC_URL               =
-
-# The DOC_ABSPATH tag should be the absolute path to the directory where the
-# documentation is located. If left blank the directory on the local machine
-# will be used.
-
-DOC_ABSPATH           =
-
-# The BIN_ABSPATH tag must point to the directory where the doxysearch binary
-# is installed.
-
-BIN_ABSPATH           = /usr/local/bin/
-
-# The EXT_DOC_PATHS tag can be used to specify one or more paths to
-# documentation generated for other projects. This allows doxysearch to search
-# the documentation for these projects as well.
-
-EXT_DOC_PATHS         =