Merge topic 'orkun_24848_19_05_2023'

2bb3d9b644 Autogen: Fix multi-config generated file issue
3bd605f3d0 Autogen: Optimize cmake_autogen execution for CROSS_CONFIG usage
f2f21c5752 Improve Const Correctness

Acked-by: Kitware Robot <kwrobot@kitware.com>
Tested-by: buildbot <buildbot@kitware.com>
Merge-request: !8507
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 0abd94e..00c6c52 100644
--- a/.codespellrc
+++ b/.codespellrc
@@ -4,5 +4,5 @@
 # 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/.gitlab-ci.yml b/.gitlab-ci.yml
index 305c8d2..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,16 +103,16 @@
         - .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
@@ -121,9 +121,9 @@
         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
@@ -195,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
@@ -205,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
@@ -215,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
@@ -233,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:
@@ -334,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
@@ -391,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:
@@ -791,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
@@ -801,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"
 
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_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_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_asan.cmake b/.gitlab/ci/configure_fedora38_asan.cmake
similarity index 65%
rename from .gitlab/ci/configure_fedora37_asan.cmake
rename to .gitlab/ci/configure_fedora38_asan.cmake
index 363e953..8eae500 100644
--- a/.gitlab/ci/configure_fedora37_asan.cmake
+++ b/.gitlab/ci/configure_fedora38_asan.cmake
@@ -1,4 +1,4 @@
 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")
+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_fedora37_makefiles.cmake b/.gitlab/ci/configure_fedora38_makefiles.cmake
similarity index 100%
rename from .gitlab/ci/configure_fedora37_makefiles.cmake
rename to .gitlab/ci/configure_fedora38_makefiles.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_fedora37_ninja.cmake b/.gitlab/ci/configure_fedora38_ninja.cmake
similarity index 88%
rename from .gitlab/ci/configure_fedora37_ninja.cmake
rename to .gitlab/ci/configure_fedora38_ninja.cmake
index 5b40677..ac6b9f6 100644
--- a/.gitlab/ci/configure_fedora37_ninja.cmake
+++ b/.gitlab/ci/configure_fedora38_ninja.cmake
@@ -11,4 +11,4 @@
 # 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")
+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_fedora38_ninja_clang.cmake
similarity index 76%
rename from .gitlab/ci/configure_linux_clang_cxx_modules_ninja.cmake
rename to .gitlab/ci/configure_fedora38_ninja_clang.cmake
index 671c625..214a123 100644
--- a/.gitlab/ci/configure_linux_clang_cxx_modules_ninja.cmake
+++ b/.gitlab/ci/configure_fedora38_ninja_clang.cmake
@@ -1,4 +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_external_test.cmake")
+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_linux_clang_cxx_modules_ninja.cmake b/.gitlab/ci/configure_fedora38_ninja_multi_clang.cmake
similarity index 76%
copy from .gitlab/ci/configure_linux_clang_cxx_modules_ninja.cmake
copy to .gitlab/ci/configure_fedora38_ninja_multi_clang.cmake
index 671c625..214a123 100644
--- a/.gitlab/ci/configure_linux_clang_cxx_modules_ninja.cmake
+++ b/.gitlab/ci/configure_fedora38_ninja_multi_clang.cmake
@@ -1,4 +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_external_test.cmake")
+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_fedora37_tidy.cmake b/.gitlab/ci/configure_fedora38_tidy.cmake
similarity index 78%
rename from .gitlab/ci/configure_fedora37_tidy.cmake
rename to .gitlab/ci/configure_fedora38_tidy.cmake
index f8eb9ab..5b062da 100644
--- a/.gitlab/ci/configure_fedora37_tidy.cmake
+++ b/.gitlab/ci/configure_fedora38_tidy.cmake
@@ -2,4 +2,4 @@
 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")
+include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora38_common.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 671c625..0000000
--- a/.gitlab/ci/configure_linux_clang_cxx_modules_ninja_multi.cmake
+++ /dev/null
@@ -1,4 +0,0 @@
-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_external_test.cmake")
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/fedora37/Dockerfile b/.gitlab/ci/docker/fedora38/Dockerfile
similarity index 98%
rename from .gitlab/ci/docker/fedora37/Dockerfile
rename to .gitlab/ci/docker/fedora38/Dockerfile
index 5439e9d..4918693 100644
--- a/.gitlab/ci/docker/fedora37/Dockerfile
+++ b/.gitlab/ci/docker/fedora38/Dockerfile
@@ -1,6 +1,6 @@
 # syntax=docker/dockerfile:1
 
-ARG BASE_IMAGE=fedora:37
+ARG BASE_IMAGE=fedora:38
 
 FROM ${BASE_IMAGE} AS dnf-cache
 # Populate DNF cache w/ the fresh metadata and prefetch packages.
diff --git a/.gitlab/ci/docker/fedora37/deps_packages.lst b/.gitlab/ci/docker/fedora38/deps_packages.lst
similarity index 99%
rename from .gitlab/ci/docker/fedora37/deps_packages.lst
rename to .gitlab/ci/docker/fedora38/deps_packages.lst
index 68777f0..c7c1385 100644
--- a/.gitlab/ci/docker/fedora37/deps_packages.lst
+++ b/.gitlab/ci/docker/fedora38/deps_packages.lst
@@ -19,7 +19,6 @@
 bzip2-devel
 expat-devel
 jsoncpp-devel
-json-devel
 libarchive-devel
 libcurl-devel
 libuv-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/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_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_fedora37_makefiles.cmake b/.gitlab/ci/env_fedora38_makefiles.cmake
similarity index 100%
rename from .gitlab/ci/env_fedora37_makefiles.cmake
rename to .gitlab/ci/env_fedora38_makefiles.cmake
diff --git a/.gitlab/ci/env_fedora37_makefiles.sh b/.gitlab/ci/env_fedora38_makefiles.sh
similarity index 100%
rename from .gitlab/ci/env_fedora37_makefiles.sh
rename to .gitlab/ci/env_fedora38_makefiles.sh
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/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/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/os-linux.yml b/.gitlab/os-linux.yml
index 4ac7772..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"
@@ -68,8 +68,8 @@
 
 ### Fedora
 
-.fedora37:
-    image: "kitware/cmake:ci-fedora37-x86_64-2023-05-17"
+.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/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/CMakeLists.txt b/CMakeLists.txt
index 6322aa6..d559c08 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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/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/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/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/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/manual/cmake-env-variables.7.rst b/Help/manual/cmake-env-variables.7.rst
index f7ae94d..197e56e 100644
--- a/Help/manual/cmake-env-variables.7.rst
+++ b/Help/manual/cmake-env-variables.7.rst
@@ -50,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.1.rst b/Help/manual/cmake.1.rst
index 1ea7626..b5848f7 100644
--- a/Help/manual/cmake.1.rst
+++ b/Help/manual/cmake.1.rst
@@ -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
@@ -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
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/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/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/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/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/Modules/CMakeASMCompiler.cmake.in b/Modules/CMakeASMCompiler.cmake.in
index 8a1718b..1efd9f5 100644
--- a/Modules/CMakeASMCompiler.cmake.in
+++ b/Modules/CMakeASMCompiler.cmake.in
@@ -17,6 +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@")
+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 cf3a242..2f0b774 100644
--- a/Modules/CMakeCCompiler.cmake.in
+++ b/Modules/CMakeCCompiler.cmake.in
@@ -39,7 +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@")
+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 3d7d552..3c28c28 100644
--- a/Modules/CMakeCUDACompiler.cmake.in
+++ b/Modules/CMakeCUDACompiler.cmake.in
@@ -30,7 +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_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/CMakeCXXCompiler.cmake.in b/Modules/CMakeCXXCompiler.cmake.in
index 2052e7f..8b6f82b 100644
--- a/Modules/CMakeCXXCompiler.cmake.in
+++ b/Modules/CMakeCXXCompiler.cmake.in
@@ -50,7 +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@")
+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/CMakeDetermineCompilerABI.cmake b/Modules/CMakeDetermineCompilerABI.cmake
index 3fd54cc..13bfeec 100644
--- a/Modules/CMakeDetermineCompilerABI.cmake
+++ b/Modules/CMakeDetermineCompilerABI.cmake
@@ -42,7 +42,7 @@
     __TestCompiler_setTryCompileTargetType()
 
     # Avoid failing ABI detection on warnings.
-    string(REGEX REPLACE "(^| )-Werror([= ][^ ]*)?( |$)" " " CMAKE_${lang}_FLAGS "${CMAKE_${lang}_FLAGS}")
+    string(REGEX REPLACE "(^| )-Werror([= ][^-][^ ]*)?( |$)" " " CMAKE_${lang}_FLAGS "${CMAKE_${lang}_FLAGS}")
 
     # Save the current LC_ALL, LC_MESSAGES, and LANG environment variables
     # and set them to "C" that way GCC's "search starts here" text is in
@@ -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/CMakeFindBinUtils.cmake b/Modules/CMakeFindBinUtils.cmake
index 604d25c..461839a 100644
--- a/Modules/CMakeFindBinUtils.cmake
+++ b/Modules/CMakeFindBinUtils.cmake
@@ -135,7 +135,7 @@
   elseif("${CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER_ARCHITECTURE_ID}" IN_LIST _CMAKE_IAR_XTOOLS)
     __append_IAR_tool(AR "xar")
     if("${CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER_ARCHITECTURE_ID}" STREQUAL "AVR" AND
-      (CMAKE_${_CMAKE_PROCESSING_LANGUAGE}_COMPILER_VERSION VERSION_GREATER 7))
+      (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()
diff --git a/Modules/CMakeFortranCompiler.cmake.in b/Modules/CMakeFortranCompiler.cmake.in
index a7caf2b..6a2be28 100644
--- a/Modules/CMakeFortranCompiler.cmake.in
+++ b/Modules/CMakeFortranCompiler.cmake.in
@@ -29,7 +29,7 @@
 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@")
+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 32c1223..c94153b 100644
--- a/Modules/CMakeHIPCompiler.cmake.in
+++ b/Modules/CMakeHIPCompiler.cmake.in
@@ -26,7 +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_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@")
diff --git a/Modules/CMakeOBJCCompiler.cmake.in b/Modules/CMakeOBJCCompiler.cmake.in
index 0ceb804..de73645 100644
--- a/Modules/CMakeOBJCCompiler.cmake.in
+++ b/Modules/CMakeOBJCCompiler.cmake.in
@@ -37,7 +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@")
+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 f087ec3..94d24ff 100644
--- a/Modules/CMakeOBJCXXCompiler.cmake.in
+++ b/Modules/CMakeOBJCXXCompiler.cmake.in
@@ -54,7 +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@")
+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/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/Compiler/Clang-HIP.cmake b/Modules/Compiler/Clang-HIP.cmake
index 7e3c99c..92925f1 100644
--- a/Modules/Compiler/Clang-HIP.cmake
+++ b/Modules/Compiler/Clang-HIP.cmake
@@ -1,13 +1,5 @@
 include(Compiler/Clang)
 
-#
-# For now, deactivate globally linker dependency file support because
-# HIP compiler is based on Clang which provides support of other languages
-#
-foreach (lang IN ITEMS "C" "CXX" "OBJC" "OBJCXX" "Fortran" "ASM")
-  set(CMAKE_${lang}_LINKER_DEPFILE_SUPPORTED FALSE)
-endforeach()
-
 __compiler_clang(HIP)
 __compiler_clang_cxx_standards(HIP)
 
diff --git a/Modules/Compiler/GNU.cmake b/Modules/Compiler/GNU.cmake
index f140208..251e05a 100644
--- a/Modules/Compiler/GNU.cmake
+++ b/Modules/Compiler/GNU.cmake
@@ -53,7 +53,7 @@
   endif()
 
   # define flags for linker depfile generation
-  if (NOT DEFINED CMAKE_${lang}_LINKER_DEPFILE_SUPPORTED)
+  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
@@ -71,7 +71,7 @@
 
     if (NOT DEFINED CMAKE_${lang}_LINKER_DEPFILE_SUPPORTED)
       ## check if this feature is supported by the linker
-      execute_process(COMMAND "${CMAKE_LINKER}" --help
+      execute_process(COMMAND "${CMAKE_${lang}_COMPILER}" -Wl,--help
         OUTPUT_VARIABLE _linker_capabilities
         ERROR_VARIABLE _linker_capabilities)
       if(_linker_capabilities MATCHES "--dependency-file")
@@ -82,6 +82,7 @@
       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)
diff --git a/Modules/ExternalProject.cmake b/Modules/ExternalProject.cmake
index 1fdd754..bac126c 100644
--- a/Modules/ExternalProject.cmake
+++ b/Modules/ExternalProject.cmake
@@ -448,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
@@ -3216,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)
@@ -3230,6 +3240,7 @@
   set(work_dir)
   set(comment)
   set(always)
+  set(file_deps)
 
   if(cmd_set)
     set(work_dir ${source_dir})
@@ -3291,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}
@@ -3344,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}"
@@ -3356,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)
@@ -3364,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}
@@ -3389,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
@@ -3425,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)
@@ -3445,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
@@ -3466,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)
@@ -3481,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)
@@ -3695,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
@@ -3724,7 +3805,7 @@
       INDEPENDENT FALSE
       COMMAND ${__cmdQuoted}
       WORKING_DIRECTORY \${binary_dir}
-      DEPENDEES patch
+      DEPENDEES \${dependees}
       DEPENDS \${file_deps}
       ${log}
       ${uses_terminal}
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/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/FetchContent.cmake b/Modules/FetchContent.cmake
index 74ac8aa..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`:
 
diff --git a/Modules/FindCUDA.cmake b/Modules/FindCUDA.cmake
index 220b9ab..0d7f1a4 100644
--- a/Modules/FindCUDA.cmake
+++ b/Modules/FindCUDA.cmake
@@ -1942,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/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 e111b79..3ab6bc1 100644
--- a/Modules/FindMatlab.cmake
+++ b/Modules/FindMatlab.cmake
@@ -951,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 ""))
diff --git a/Modules/FindOpenGL.cmake b/Modules/FindOpenGL.cmake
index a773601..843f787 100644
--- a/Modules/FindOpenGL.cmake
+++ b/Modules/FindOpenGL.cmake
@@ -160,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``
diff --git a/Modules/FindOpenSSL.cmake b/Modules/FindOpenSSL.cmake
index 45dc9ac..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()
@@ -230,13 +230,15 @@
   set(_OPENSSL_FIND_PATH_SUFFIX "include")
 endif()
 
-if (MSVC)
+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/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/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/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/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 2354f3d..bcaf890 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -762,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(
diff --git a/Source/CMakeVersion.cmake b/Source/CMakeVersion.cmake
index dd759da..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 20230519)
+set(CMake_VERSION_PATCH 20230531)
 #set(CMake_VERSION_RC 0)
 set(CMake_VERSION_IS_DIRTY 0)
 
diff --git a/Source/CPack/cmCPackInnoSetupGenerator.cxx b/Source/CPack/cmCPackInnoSetupGenerator.cxx
index d8825d4..5d2c208 100644
--- a/Source/CPack/cmCPackInnoSetupGenerator.cxx
+++ b/Source/CPack/cmCPackInnoSetupGenerator.cxx
@@ -277,7 +277,7 @@
     return false;
   }
 
-  const std::string& architecture = GetOption("CPACK_INNOSETUP_ARCHITECTURE");
+  cmValue const architecture = GetOption("CPACK_INNOSETUP_ARCHITECTURE");
   if (architecture != "x86" && architecture != "x64" &&
       architecture != "arm64" && architecture != "ia64") {
     cmCPackLogger(cmCPackLog::LOG_ERROR,
diff --git a/Source/CTest/cmCTestBuildAndTestHandler.cxx b/Source/CTest/cmCTestBuildAndTestHandler.cxx
index cece98e..5feb953 100644
--- a/Source/CTest/cmCTestBuildAndTestHandler.cxx
+++ b/Source/CTest/cmCTestBuildAndTestHandler.cxx
@@ -246,7 +246,6 @@
         return 1;
       }
     }
-    std::string output;
     const char* config = nullptr;
     if (!this->CTest->GetConfigType().empty()) {
       config = this->CTest->GetConfigType().c_str();
@@ -259,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/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/Modules/CMakeBuildUtilities.cmake b/Source/Modules/CMakeBuildUtilities.cmake
index d6e3e88..c891fe9 100644
--- a/Source/Modules/CMakeBuildUtilities.cmake
+++ b/Source/Modules/CMakeBuildUtilities.cmake
@@ -376,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/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/cmCPluginAPI.cxx b/Source/cmCPluginAPI.cxx
index abec968..c2c5bdb 100644
--- a/Source/cmCPluginAPI.cxx
+++ b/Source/cmCPluginAPI.cxx
@@ -615,7 +615,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/cmCacheManager.cxx b/Source/cmCacheManager.cxx
index d95dcc4..8633de1 100644
--- a/Source/cmCacheManager.cxx
+++ b/Source/cmCacheManager.cxx
@@ -84,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.
@@ -102,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;
@@ -186,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;
     }
@@ -541,10 +541,11 @@
       cmSystemTools::ConvertToUnixSlashes(e.Value);
     }
   }
-  e.SetProperty("HELPSTRING",
-                helpString
-                  ? helpString
-                  : "(This variable does not exist and should not be used)");
+  e.SetProperty(
+    "HELPSTRING",
+    helpString ? std::string{ helpString }
+               : std::string{
+                   "(This variable does not exist and should not be used)" });
 }
 
 void cmCacheManager::CacheEntry::SetValue(cmValue value)
@@ -580,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);
   }
@@ -593,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..a2da0b5 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);
     }
   }
 
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 2615494..e635dd9 100644
--- a/Source/cmCommonTargetGenerator.cxx
+++ b/Source/cmCommonTargetGenerator.cxx
@@ -236,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 +
diff --git a/Source/cmConditionEvaluator.cxx b/Source/cmConditionEvaluator.cxx
index 288e107..6f9f541 100644
--- a/Source/cmConditionEvaluator.cxx
+++ b/Source/cmConditionEvaluator.cxx
@@ -741,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);
     }
diff --git a/Source/cmConfigure.cmake.h.in b/Source/cmConfigure.cmake.h.in
index 3f19a11..de74716 100644
--- a/Source/cmConfigure.cmake.h.in
+++ b/Source/cmConfigure.cmake.h.in
@@ -20,6 +20,7 @@
 
 #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
diff --git a/Source/cmCustomCommandGenerator.cxx b/Source/cmCustomCommandGenerator.cxx
index 7623ccf..2c1480a 100644
--- a/Source/cmCustomCommandGenerator.cxx
+++ b/Source/cmCustomCommandGenerator.cxx
@@ -332,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/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/cmDependsFortran.cxx b/Source/cmDependsFortran.cxx
index aede3fe..d038db7 100644
--- a/Source/cmDependsFortran.cxx
+++ b/Source/cmDependsFortran.cxx
@@ -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 f45d81b..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(
diff --git a/Source/cmExportBuildFileGenerator.cxx b/Source/cmExportBuildFileGenerator.cxx
index 437ae69..a3637d8 100644
--- a/Source/cmExportBuildFileGenerator.cxx
+++ b/Source/cmExportBuildFileGenerator.cxx
@@ -542,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/cmExportInstallFileGenerator.cxx b/Source/cmExportInstallFileGenerator.cxx
index 51c91f3..df119ae 100644
--- a/Source/cmExportInstallFileGenerator.cxx
+++ b/Source/cmExportInstallFileGenerator.cxx
@@ -752,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/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/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 4a8716f..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);
@@ -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/cmFindPackageCommand.cxx b/Source/cmFindPackageCommand.cxx
index 98b085c..1c2a937 100644
--- a/Source/cmFindPackageCommand.cxx
+++ b/Source/cmFindPackageCommand.cxx
@@ -1804,11 +1804,11 @@
     notFoundContents.push_back(this->Name);
   }
 
-  this->Makefile->GetState()->SetGlobalProperty(
-    "PACKAGES_FOUND", foundContents.to_string().c_str());
+  this->Makefile->GetState()->SetGlobalProperty("PACKAGES_FOUND",
+                                                foundContents.to_string());
 
-  this->Makefile->GetState()->SetGlobalProperty(
-    "PACKAGES_NOT_FOUND", notFoundContents.to_string().c_str());
+  this->Makefile->GetState()->SetGlobalProperty("PACKAGES_NOT_FOUND",
+                                                notFoundContents.to_string());
 }
 
 void cmFindPackageCommand::AppendSuccessInformation()
@@ -1845,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");
diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx
index 32f0cbd..f8455c8 100644
--- a/Source/cmGeneratorTarget.cxx
+++ b/Source/cmGeneratorTarget.cxx
@@ -59,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";
@@ -2434,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/")) {
@@ -7036,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.
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/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 040f500..7e6b16a 100644
--- a/Source/cmGlobalGenerator.cxx
+++ b/Source/cmGlobalGenerator.cxx
@@ -47,6 +47,7 @@
 #include "cmMSVC60LinkLineComputer.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
+#include "cmOutputConverter.h"
 #include "cmPolicies.h"
 #include "cmRange.h"
 #include "cmSourceFile.h"
@@ -73,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
 {
@@ -268,7 +286,7 @@
       changeVars += ";";
       changeVars += *cname;
       this->GetCMakeInstance()->GetState()->SetGlobalProperty(
-        "__CMAKE_DELETE_CACHE_CHANGE_VARS_", changeVars.c_str());
+        "__CMAKE_DELETE_CACHE_CHANGE_VARS_", changeVars);
     }
   }
 }
@@ -2058,9 +2076,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>
@@ -2085,7 +2106,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)
@@ -2096,16 +2117,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;
@@ -2134,9 +2152,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 "
@@ -2149,27 +2166,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)) {
@@ -2177,21 +2200,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;
   }
 
@@ -2598,14 +2624,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;
     }
@@ -2614,11 +2640,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) {
diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h
index 79fe52c..01afabd 100644
--- a/Source/cmGlobalGenerator.h
+++ b/Source/cmGlobalGenerator.h
@@ -85,6 +85,7 @@
   }
 
   std::string Printable() const { return cmJoin(this->PrimaryCommand, " "); }
+  std::string QuotedPrintable() const;
 
   std::vector<std::string> PrimaryCommand;
   bool RequiresOutputForward = false;
@@ -233,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,
@@ -384,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))
     {
@@ -400,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()
@@ -412,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;
   };
diff --git a/Source/cmGlobalGhsMultiGenerator.cxx b/Source/cmGlobalGhsMultiGenerator.cxx
index 578e805..2453bfc 100644
--- a/Source/cmGlobalGhsMultiGenerator.cxx
+++ b/Source/cmGlobalGhsMultiGenerator.cxx
@@ -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;
   }
diff --git a/Source/cmGlobalVisualStudio7Generator.cxx b/Source/cmGlobalVisualStudio7Generator.cxx
index 694698e..b254777 100644
--- a/Source/cmGlobalVisualStudio7Generator.cxx
+++ b/Source/cmGlobalVisualStudio7Generator.cxx
@@ -716,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/cmMakefile.cxx b/Source/cmMakefile.cxx
index 01afc44..92ba2d4 100644
--- a/Source/cmMakefile.cxx
+++ b/Source/cmMakefile.cxx
@@ -68,6 +68,10 @@
 #  include "cmVariableWatch.h"
 #endif
 
+#ifdef CMake_ENABLE_DEBUGGER
+#  include "cmDebuggerAdapter.h"
+#endif
+
 #ifndef __has_feature
 #  define __has_feature(x) 0
 #endif
@@ -424,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()
@@ -434,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;
@@ -663,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();
@@ -764,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();
@@ -791,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();
@@ -1658,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
@@ -3769,6 +3858,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,
@@ -4044,10 +4139,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);
diff --git a/Source/cmMakefile.h b/Source/cmMakefile.h
index d1f5be5..6fdadab 100644
--- a/Source/cmMakefile.h
+++ b/Source/cmMakefile.h
@@ -425,7 +425,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
   {
@@ -801,8 +801,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));
diff --git a/Source/cmMakefileLibraryTargetGenerator.cxx b/Source/cmMakefileLibraryTargetGenerator.cxx
index b07a74b..fc3caa1 100644
--- a/Source/cmMakefileLibraryTargetGenerator.cxx
+++ b/Source/cmMakefileLibraryTargetGenerator.cxx
@@ -785,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();
diff --git a/Source/cmMessageCommand.cxx b/Source/cmMessageCommand.cxx
index baf40f8..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>
@@ -19,6 +20,10 @@
 #include "cmSystemTools.h"
 #include "cmake.h"
 
+#ifdef CMake_ENABLE_DEBUGGER
+#  include "cmDebuggerAdapter.h"
+#endif
+
 namespace {
 
 enum class CheckingType
@@ -202,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/cmOutputConverter.cxx b/Source/cmOutputConverter.cxx
index 53cb21e..02981ae 100644
--- a/Source/cmOutputConverter.cxx
+++ b/Source/cmOutputConverter.cxx
@@ -243,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()) {
@@ -283,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);
 }
 
diff --git a/Source/cmOutputConverter.h b/Source/cmOutputConverter.h
index 625d897..0ee7afb 100644
--- a/Source/cmOutputConverter.h
+++ b/Source/cmOutputConverter.h
@@ -107,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/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/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/cmSourceFile.cxx b/Source/cmSourceFile.cxx
index 6224d0e..3403745 100644
--- a/Source/cmSourceFile.cxx
+++ b/Source/cmSourceFile.cxx
@@ -278,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();
@@ -304,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 9308af4..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));
diff --git a/Source/cmState.cxx b/Source/cmState.cxx
index f12f91f..a72f830 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;
 }
@@ -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 0a42df0..d9d2c21 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);
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/cmTarget.cxx b/Source/cmTarget.cxx
index 0fbe430..b55554d 100644
--- a/Source/cmTarget.cxx
+++ b/Source/cmTarget.cxx
@@ -1810,26 +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(
@@ -1975,7 +1956,7 @@
 
     std::string reusedFrom = reusedTarget->GetSafeProperty(prop);
     if (reusedFrom.empty()) {
-      reusedFrom = ConvertToString(value);
+      reusedFrom = *value;
     }
 
     this->impl->Properties.SetProperty(prop, reusedFrom);
@@ -2091,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,
diff --git a/Source/cmTarget.h b/Source/cmTarget.h
index 24f6fcd..5fe5a28 100644
--- a/Source/cmTarget.h
+++ b/Source/cmTarget.h
@@ -180,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));
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/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/cmake.cxx b/Source/cmake.cxx
index 0fd7461..3792791 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"
@@ -411,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;
 }
@@ -617,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
@@ -1233,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)
@@ -2138,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);
@@ -2371,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(
@@ -2401,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;
     }
@@ -2420,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;
@@ -2436,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;
@@ -2620,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)
 {
@@ -2709,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)
@@ -3263,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);
@@ -3625,7 +3732,6 @@
       return 1;
     }
   }
-  std::string output;
   std::string projName;
   cmValue cachedProjectName =
     this->State->GetCacheEntryValue("CMAKE_PROJECT_NAME");
@@ -3699,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)
diff --git a/Source/cmake.h b/Source/cmake.h
index 0f8f642..d394a3e 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;
@@ -404,8 +411,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));
@@ -659,6 +669,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);
@@ -799,6 +826,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 ad27443..ced83dc 100644
--- a/Source/cmakemain.cxx
+++ b/Source/cmakemain.cxx
@@ -392,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;
 }
 
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/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/Tests/CMakeLib/CMakeLists.txt b/Tests/CMakeLib/CMakeLists.txt
index 0fc3deb..5c14de2 100644
--- a/Tests/CMakeLib/CMakeLists.txt
+++ b/Tests/CMakeLib/CMakeLists.txt
@@ -32,11 +32,22 @@
   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>)
@@ -77,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/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/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/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/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/ExternalProjectUpdate/CMakeLists.txt b/Tests/ExternalProjectUpdate/CMakeLists.txt
index 1b84ff3..e8c67b8 100644
--- a/Tests/ExternalProjectUpdate/CMakeLists.txt
+++ b/Tests/ExternalProjectUpdate/CMakeLists.txt
@@ -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/FindOpenAL/Test/main.cxx b/Tests/FindOpenAL/Test/main.cxx
index 1396c60..27204fb 100644
--- a/Tests/FindOpenAL/Test/main.cxx
+++ b/Tests/FindOpenAL/Test/main.cxx
@@ -10,9 +10,10 @@
 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/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/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/Tests.cmake b/Tests/QtAutogen/Tests.cmake
index 3e4f04d..7dd9c84 100644
--- a/Tests/QtAutogen/Tests.cmake
+++ b/Tests/QtAutogen/Tests.cmake
@@ -2,6 +2,7 @@
 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)
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/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/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/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/RunCMakeTest.cmake b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
index 205949b..45b4c0e 100644
--- a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
@@ -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)
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/FileAPI/codemodel-v2-check.py b/Tests/RunCMake/FileAPI/codemodel-v2-check.py
index eb52975..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":
@@ -926,6 +972,21 @@
                             ],
                         },
                         {
+                            "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,
@@ -1070,6 +1131,7 @@
                                 "^codemodel-v2\\.cmake$",
                                 "^custom/CMakeLists\\.txt$",
                                 "^cxx/CMakeLists\\.txt$",
+                                "^framework/CMakeLists\\.txt$",
                                 "^dir/CMakeLists\\.txt$",
                                 "^dir/dir/CMakeLists\\.txt$",
                                 "^fileset/CMakeLists\\.txt$",
@@ -1144,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_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/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/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/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/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/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/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/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/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/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/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/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/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/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/IWYU/mapping.imp b/Utilities/IWYU/mapping.imp
index 366c517..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 ] },
@@ -101,6 +102,7 @@
   { 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/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/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/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/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/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_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_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 5a94ec5..c964d3f 100644
--- a/Utilities/cmlibarchive/libarchive/archive_read_disk_posix.c
+++ b/Utilities/cmlibarchive/libarchive/archive_read_disk_posix.c
@@ -92,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"
@@ -742,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,
@@ -2347,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;
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_format_7zip.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_7zip.c
index 722edf1..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"
@@ -757,6 +761,9 @@
 				return (ARCHIVE_FATAL);
 			}
 			symname = mem;
+			#ifdef __clang_analyzer__
+			assert(buff);
+			#endif
 			memcpy(symname+symsize, buff, size);
 			symsize += size;
 		}
@@ -2500,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);
diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_iso9660.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_iso9660.c
index 380cbb8..91b9187 100644
--- a/Utilities/cmlibarchive/libarchive/archive_read_support_format_iso9660.c
+++ b/Utilities/cmlibarchive/libarchive/archive_read_support_format_iso9660.c
@@ -3015,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;
@@ -3042,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_rar.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_rar.c
index 1c9a057..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"
@@ -3215,6 +3217,7 @@
     num = filters->lastfilternum;
 
   prog = filters->progs;
+  assert(num <= numprogs);
   for (i = 0; i < num; i++)
     prog = prog->next;
   if (prog)
@@ -3320,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)
diff --git a/Utilities/cmlibarchive/libarchive/archive_read_support_format_rar5.c b/Utilities/cmlibarchive/libarchive/archive_read_support_format_rar5.c
index 548da4e..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;
 }
@@ -2942,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;
 }
 
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/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 a056edf..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=""
@@ -697,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
@@ -962,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" ;;
@@ -1987,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)